背景
手写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=8091nacos-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=8091serviceB
项目结构

代码内容
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获取内容



















