背景
手写NACOS的服务的部分核心功能,提高自身的编码能力
本篇文章设计的是单体NACOS后端服务,提供SDK给多个NACOS客户端使用
本文编写了注册与发现|心跳机制|轮询调用服务功能,可当做入门级阅读
nacos-service
项目结构
代码内容
pom配置文件,需要较高版本的maven
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.demo</groupId>
<artifactId>nacos-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>nacos-service</name>
<description>nacos-service</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
RestTemplateConfig 类
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.setConnectTimeout(Duration.ofSeconds(1000))
.setReadTimeout(Duration.ofSeconds(1000))
.build();
}
}
ServeController类
@RestController
@RequestMapping("/nacos-service")
public class ServeController {
/**
* 注册表
* 数据示例:order-service -> ["127.0.0.1:8092","127.0.0.1:8093"]
*/
private final Map<String, List<String>> registerMap = new HashMap<>();
// 调度线程连接池
private final ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);
// 调度任务Map
private final Map<String, ScheduledFuture> scheduleMap = new HashMap<>();
@Resource
RestTemplate restTemplate;
/**
* @param registerReq
* @return
*/
@PostMapping("/register")
public String register(@RequestBody Map<String, String> registerReq){
// 注册
List<String> serviceValue = registerMap.get(registerReq.get("serviceName"));
if (!CollectionUtils.isEmpty(serviceValue)){
serviceValue.add(registerReq.get("serviceValue"));
registerMap.put(registerReq.get("serviceName"), serviceValue);
} else {
List<String> list = new ArrayList<>();
list.add(registerReq.get("serviceValue"));
registerMap.put(registerReq.get("serviceName"), list);
}
// 通知在线的项目获取配置信息
List<String> ipAndPortList = new ArrayList<>();
for (List<String> value : registerMap.values()) {
ipAndPortList.addAll(value);
}
// 异步并发通知
for (String address : ipAndPortList) {
CompletableFuture.runAsync(() -> {
String url = "http://" + address + "/register-table/update";
restTemplate.getForObject(url, Boolean.class);
});
}
// 心跳
String ipAndPort = registerReq.get("serviceValue");
heartBeat(registerReq.get("serviceName"), ipAndPort);
return "注册成功";
}
@GetMapping("/register-service/get")
public List<String> getRegisterMap(@RequestParam String serviceName){
return registerMap.get(serviceName);
}
@GetMapping("/register-table/all")
public Map<String, List<String> > allRegisterMap(){
return registerMap;
}
/**
* 心跳使用调度连接池
* 多个心跳也可用同一个连接池
*/
private void heartBeat(String serviceName, String ipAndPort){
String url = "http://" + ipAndPort + "/heart-beat-check";
Runnable runnable = () -> {
try {
restTemplate.getForObject(url, Boolean.class);
System.out.println("心跳检测开始-" + ipAndPort);
} catch (Exception e) {
System.out.println("心跳检测发生错误: " + ipAndPort + "剔除该心跳定时任务!");
System.out.println(e);
// 当请求报错,就断掉心跳,并移除注册表中的地址
List<String> ipAndPortList = registerMap.get(serviceName);
ipAndPortList.remove(ipAndPort);
ScheduledFuture future = scheduleMap.get(ipAndPort);
future.cancel(true);
}
};
ScheduledFuture<?> scheduledFuture = scheduledThreadPool.scheduleAtFixedRate(runnable, 0, 5, TimeUnit.SECONDS);
scheduleMap.put(ipAndPort, scheduledFuture);
}
}
application.properties配置文件
spring.application.name=nacos-service
server.port=8091
nacos-sdk
项目结构
代码内容
pom配置文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.demo</groupId>
<artifactId>nacos-sdk</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>nacos-sdk</name>
<description>nacos-sdk</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
ServeConfig类
@Component
public class ServeConfig implements ApplicationRunner {
@Value("${service.name}")
private String serveName;
@Value("${remote.nacos.service.ip}")
private String remoteIp;
@Value("${remote.nacos.service.port}")
private Integer remotePort;
@Value("${server.port}")
private Integer port;
@Resource
private RestTemplate restTemplate;
@Override
public void run(ApplicationArguments args) throws Exception {
String hostAddress = InetAddress.getLocalHost().getHostAddress();
String url = "http://" + remoteIp + ":" + remotePort + "/nacos-service/register";
Map<String, Object> registerReq = new HashMap<>();
registerReq.put("serviceName", serveName);
String ipToPort = hostAddress + ":" + port;
registerReq.put("serviceValue", ipToPort);
String s = restTemplate.postForObject(url, registerReq, String.class);
System.out.println("已经注册到nacos中心" + s);
}
}
TemplateConfig类
@Configuration
public class TemplateConfig {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.setConnectTimeout(Duration.ofSeconds(1000))
.setReadTimeout(Duration.ofSeconds(1000))
.build();
}
}
ConfigController类
@RestController
public class ConfigController {
// 缓存一份注册信息保存在本地
private final static Map<String, List<String> > registerMap = new HashMap<>();
// 服务轮训控制器
private final static Map<String, Integer> servicePolling = new HashMap<>();
@Value("${remote.nacos.service.ip}")
private String remoteIp;
@Value("${remote.nacos.service.port}")
private Integer remotePort;
@Resource
private RestTemplate restTemplate;
@GetMapping("/heart-beat-check")
public boolean heartBeatAccess(){
return true;
}
// 接收服务端的通知
@GetMapping("/register-table/update")
public void updateRegisterTable(){
String url = "http://" + remoteIp + ":" + remotePort + "/nacos-service/register-table/all";
Map result = restTemplate.getForObject(url, Map.class);
registerMap.putAll(result);
}
/**
* 轮训调用服务
* 根据服务名获取具体的ip地址
*/
public String getAddressByServiceName(String serviceName){
List<String> services = registerMap.get(serviceName);
if (services.size() == 1){
return services.get(0);
} else {
Integer index = servicePolling.get(serviceName);
if (Objects.isNull(index)){
index = 0;
} else {
index = index + 1;
if (index > services.size() - 1) {
index = 0;
}
}
servicePolling.put(serviceName, index);
return services.get(index);
}
}
}
serviceA
项目结构
代码内容
pom配置文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.demo</groupId>
<artifactId>serviceA</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>serviceA</name>
<description>serviceA</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.demo</groupId>
<artifactId>nacos-sdk</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
HelloController类
@RestController
public class HelloController {
@Resource
ConfigController controller;
@Resource
RestTemplate restTemplate;
/**
* 调用B服务获取资源
*/
@GetMapping("/test")
public String test(){
String address = controller.getAddressByServiceName("order-service");
String url = "http://" + address + "/hello";
String template = restTemplate.getForObject(url, String.class);
System.out.println("我从B服务:" + address + "获取的数据:" + template);
return "我从B服务获取的数据" + template;
}
}
ServiceAApplication类
@SpringBootApplication
@ComponentScan(value = {"com.demo.nacossdk","com.demo.servicea"})
public class ServiceAApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceAApplication.class, args);
}
}
application.properties配置类
spring.application.name=serviceA
server.port=8093
service.name=game-service
# nacos服务器IP地址
remote.nacos.service.ip=192.xxx.xxx.xxx
remote.nacos.service.port=8091
serviceB
项目结构
代码内容
pom.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.demo</groupId>
<artifactId>serviceB</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>serviceB</name>
<description>serviceB</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.demo</groupId>
<artifactId>nacos-sdk</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
HelloController类
@RestController
public class HelloController {
/**
* 调用B服务获取资源
*/
@GetMapping("/hello")
public String test(){
return "黑神话-悟空";
}
}
ServiceBApplication类
@SpringBootApplication
@ComponentScan(value = {"com.demo.nacossdk","com.demo.serviceb"})
public class ServiceBApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceBApplication.class, args);
}
}
application.properties配置类
spring.application.name=serviceB
server.port=8095
service.name=order-service
remote.nacos.service.ip=192.xxx.xxx.xxx
remote.nacos.service.port=8091
运行结果展示
起nacos-service服务,serviceA服务,两个serviceB服务
心跳结果
三个服务的心跳
停掉其中一个B服务
轮询调用结果
A服务调用B服务
总结
1、开发nacos-servic、nacos-sdk,打包nacos-sdk
2、serviceA、serviceB 引入 nacos-sdk依赖
3、serviceA通过注册中心调用serviceB获取内容