『Naocs 2.x』(六) 写一个丐中丐版注册中心-实现篇

296 阅读4分钟

前言

上一节设计了一个丐中丐版本的注册中心。这一节顺理成章,该去实现它了。
代码仓库在文章结尾处。

Mung

这一块主要是,注册中心服务端的逻辑。

服务实体

主要是客户端名称、IP、端口、最后一次通信时间等等。

@Data
public class Client {
    private String clientId;
    private String clientName;
    private String clientIp;
    private Integer port;
    private Long lastTime;
    private LocalDateTime registerTime;
}

业务逻辑

目前客户端信息,就保存在内存中。丐版先不进行持久化了。

实现类主要就是针对 client_Map的增删改查。

业务接口

public interface ClientService {
 
    /** 客户端Map. key:clientName value:Client */
    Map<String, Client> client_Map = new ConcurrentHashMap<>(16);
 
    /**
     * 添加客户端
     * @apiNote [附加描述]
     * @param client 客户端信息
     * @author DaMai
     * @date 2021/9/7 20:53
     */
    void addClient(Client client);
 
    /**
     * 删除客户端
     * @apiNote [附加描述]
     * @param clientName 客户端名称
     * @author DaMai
     * @date 2021/9/7 20:53
     */
    void delClient(String clientName);
 
    /**
     * 心跳
     * @apiNote [附加描述]
     * @param clientName 客户端名称
     * @author DaMai
     * @date 2021/9/8 12:27
     */
    void beat(String clientName);
 
    /**
     * 服务端触发客户端主动心跳请求
     * @apiNote 一般用于服务端要剔除客户端时,进行客户端探测。客户端接受到此请求时,立即发送心跳。
     * @param clientName 客户端名称
     * @return com.ruiruan.pre.common.core.vo.ResultVO<java.lang.Void>
     * @author DaMai
     * @date 2021/9/12 14:08
     */
    ResultVO<Void> initiativeHealthCheck(String clientName);
 
    /**
     * 获取客户端列表
     * @apiNote [附加描述]
     * @return java.util.List<pri.damai.model.Client>
     * @author DaMai
     * @date 2021/9/8 12:38
     */
    List<Client> getClientList();
 
    /**
     * 获取客户端
     * @apiNote [附加描述]
     * @param clientName 客户端名称
     * @return pri.damai.model.Client
     * @author DaMai
     * @date 2021/9/7 21:06
     */
    Client getClientByClientName(String clientName);

业务实现

这里主要就拿出来 客户端过期 的逻辑。

@Slf4j
@Service
public class TempServiceImpl implements ClientService {
 
    ……………………………………………………………………
 
    @PostConstruct
    public void init() {
        GlobalExecutor.MUNG_SERVER_CLIENT_CLEAR.scheduleAtFixedRate(new ExpiredClientCleaner(), 6, 5, TimeUnit.SECONDS);
    }
 
    public static class ExpiredClientCleaner implements Runnable {
        @Override
        public void run() {
            long millis = System.currentTimeMillis();
            for (String key : client_Map.keySet()) {
                Client client = client_Map.get(key);
                if (Objects.nonNull(client) && client.ifExpired(millis)) {
                    client_Map.remove(key);
                    log.debug("移除客户端:{}", client);
                }
            }
        }
    }
}
 

HTTP 接口层

就是暴露业务的HTTP 接口,没有其他复杂操作。

@RuiRuanLog
@RestController
@RequestMapping(ClientConst.BASE_URL)
public class ClientController {
 
    @Resource
    ClientService clientService;
 
    @PostMapping(ClientConst.ADD_URL)
    public ResultVO<Void> addClient(@RequestBody Client client) {
        clientService.addClient(client);
        return ResultVO.success();
    }
 
    @GetMapping(ClientConst.DEL_CLIENT_URL)
    public ResultVO<Void> delClient(String clientName) {
        clientService.delClient(clientName);
        return ResultVO.success();
    }
 
    @GetMapping(ClientConst.CLIENT_LIST_URL)
    public ResultVO<List<Client>> getClientList() {
        return ResultVO.success(clientService.getClientList());
    }
 
    @GetMapping(ClientConst.GET_SERVICE_LIST_URL)
    public ResultVO<List<String>> getServiceIds() {
        List<String> result = clientService.getClientList().stream().map(Client::getClientId).collect(Collectors.toList());
        return ResultVO.success(result);
    }
 
    @GetMapping(ClientConst.GET_CLIENT_URL)
    public ResultVO<Client> getClient(String clientName) {
        return ResultVO.success(clientService.getClientByClientName(clientName));
    }
 
    @GetMapping(ClientConst.BEAT)
    public ResultVO<Void> beat(String clientName) {
        clientService.beat(clientName);
        return ResultVO.success();
    }
 
    @GetMapping(ClientConst.INITIATIVE_HEALTH_CHECK)
    public ResultVO<Void> initiativeHealthCheck(String clientName) {
        clientService.initiativeHealthCheck(clientName);
        return ResultVO.success();
    }
}

Mung-Client

这里主要就是对 HTTP 接口层做一层包装,方便使用者进行调用。

不再粘贴代码了,有兴趣的话,看一眼代码。

Mung-Starter

这里就需要将 Mung-Client 与 Spring Cloud 结合起来了,所以 POM 里应该依赖 Mung-Clientspring-cloud-commons

还需要进行 Feign 调用,所以需要依赖 Ribbon

其他的 Spring Boot 项目基础依赖就不赘述了,有兴趣看代码就行。

服务注册

Registration 实现

public class MungRegistration implements Registration {
    private String clientName;
    private String clientIp;
    private Integer port;
 
    public MungRegistration(String clientName, String clientIp, Integer port) {
        this.clientName = clientName;
        this.clientIp = clientIp;
        this.port = port;
    }
 
    @Override
    public String getServiceId() {return this.clientName;}
    @Override
    public String getHost() {return this.clientIp;}
 
    @Override
    public int getPort() {return this.port;}
 
    @Override
    public boolean isSecure() {return false;}
 
    @Override
    public URI getUri() {return null;}
 
    @Override
    public Map<String, String> getMetadata() {return null;}
}

ServiceRegistry 实现

这里的重点方法两个:注册、注销。

public class MungServiceRegistry implements ServiceRegistry<MungRegistration> {
 
    private final String clientName;
    private final String clientIp;
    private final Integer clientPort;
    private final MungClient mungClient;
 
    public MungServiceRegistry(String serverIp, Integer serverPort, String clientName, String clientIp, Integer clientPort) {
        this.clientName = clientName;
        this.clientIp = clientIp;
        this.clientPort = clientPort;
        this.mungClient = new MungClient(serverIp, serverPort);
    }
 
    @Override
    public void register(MungRegistration registration) {
        if (Objects.isNull(registration)) {
            return;
        }
        Client client = new Client();
        client.setClientId(clientName);
        client.setClientName(clientName);
        client.setClientIp(clientIp);
        client.setPort(clientPort);
        ResultVO<Void> resultVO = mungClient.addClient(client);
        this.checkSuccess(resultVO);
    }
 
    @Override
    public void deregister(MungRegistration registration) {
        if (Objects.isNull(registration)) {
            return;
        }
        ResultVO<Void> resultVO = mungClient.delClient(registration.getInstanceId());
        this.checkSuccess(resultVO);
    }
 
}
 

AbstractAutoServiceRegistration 实现

这里的重点方法一个:getRegistration()

public class MungAutoServiceRegistration extends AbstractAutoServiceRegistration<MungRegistration> {
 
    private MungRegistration registration;
 
    @Override
    protected void register() {
        super.register();
    }
 
    @Override
    protected boolean isEnabled() {
        return true;
    }
 
    @Override
    protected MungRegistration getRegistration() {
        return this.registration;
    }
}
 

自动配置类

最后就是 Spring Boot 的自动配置类了。

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled",
        matchIfMissing = true)
@AutoConfigureAfter({AutoServiceRegistrationConfiguration.class,
        AutoServiceRegistrationAutoConfiguration.class,
        MungProperties.class,
})
public class MungRegistryAutoConfiguration {
 
    @Bean
    public MungServiceRegistry mungServiceRegistry(MungProperties mungProperties) {
        String[] split = mungProperties.getServerAddress().split(":");
        return new MungServiceRegistry(split[0], Integer.parseInt(split[1]), mungProperties.getClientName(), mungProperties.getBusIp(), mungProperties.getBusPort());
    }
 
    @Bean
    @ConditionalOnBean(AutoServiceRegistrationProperties.class)
    public MungRegistration mungRegistration(MungProperties mungProperties) {
        return new MungRegistration(mungProperties.getClientName(), mungProperties.getBusIp(), mungProperties.getBusPort());
    }
 
    @Bean
    @ConditionalOnBean(AutoServiceRegistrationProperties.class)
    public MungAutoServiceRegistration mungAutoServiceRegistration(
            MungServiceRegistry registry,
            AutoServiceRegistrationProperties autoServiceRegistrationProperties,
            MungRegistration registration) {
        return new MungAutoServiceRegistration(registry, autoServiceRegistrationProperties, registration);
    }
}

Ribbon

Server 实现

这里的 Server 主要是对远程服务信息的抽象。

public class MungServer extends Server {
 
    private final MetaInfo metaInfo;
    private final Client client;
    private final Map<String, String> metadata;
 
    public MungServer(final Client client) {
        super(client.getClientIp(), client.getPort());
        this.client = client;
        this.metaInfo = new Server.MetaInfo() {
            @Override
            public String getAppName() { return client.getClientName(); }
 
            @Override
            public String getServerGroup() { return null; }
 
            @Override
            public String getServiceIdForDiscovery() { return null; }
 
            @Override
            public String getInstanceId() { return client.getClientId(); }
        };
        this.metadata = null;
    }
 
    @Override
    public Server.MetaInfo getMetaInfo() { return metaInfo; }
    public Client getInstance() { return client; }
    public Map<String, String> getMetadata() { return metadata; }
 
}

AbstractServerList 实现

Ribbon 内部会定时调用getUpdatedListOfServers()方法,来获取远程服务元信息。

public class MungServerList extends AbstractServerList<MungServer> {
 
    private MungClient mungClient;
 
    private String clientId;
 
    public MungServerList(MungProperties mungProperties, String clientId) {
        this.clientId = clientId;
        this.mungClient = new MungClient(mungProperties.getServerIp(), mungProperties.getServerPort());
    }
 
    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {this.clientId = iClientConfig.getClientName();}
 
    @Override
    public List<MungServer> getInitialListOfServers() {return getServerList();}
 
    @Override
    public List<MungServer> getUpdatedListOfServers() {return getServerList();}
 
    private List<MungServer> getServerList() {
        ResultVO<Client> resultVO = mungClient.getClient(clientId);
        MungServer mungServer = new MungServer(resultVO.getData());
        return Collections.singletonList(mungServer);
    }
}

其余的就是 Spring Boot 的配置类。详细可以看代码。

业务服务一

  • POM 文件依赖 mung-start
  • yml配置如下:
server:
  port: 8801
spring:
  application:
    name: bus-one
damai:
  mung:
    discovery:
      bus-ip: 127.0.0.1
      server-address: 127.0.0.1:8889
  • 写一个业务接口

    @RestController
    @RequestMapping("/busOne")
    public class HelloController {
        @GetMapping("/hi")
        public String hi() { return "hi, this is bus-one 8801";}
    }
    

业务服务二

  • 前面两步如业务服务一。

  • 写个 Feign 调用

    @FeignClient(name = "bus-one")
    public interface BusOneFeign {
        @GetMapping("/busOne/hi")
        String hi();
    }
    
  • Feign 调用

    @RestController
    @RequestMapping("/two")
    public class TwoController {
        @Resource
        BusOneFeign busOneFeign;
        @GetMapping("/hi")
        public String hi() {
            return busOneFeign.hi();
        }
    }
    
  • 服务调用

    image-20210923195926886

    这里可以看到,我们成功使用 Feign 进行了远程接口调用。

小节

这一节把上节的设计,做了一个简单实现。目前可以做到简单的调用了。

后续可以把注册的服务修改为集群(List),然后继承 Ribbon 的 IRule类,通过choose()方法,来进行负载均衡等功能。
Mung 项目代码地址