前言
上一篇我们说到Nacos作为动态配置中心,那么这篇来聊聊Nacos作为服务注册中心。注册中心其实就类似于企查查这种平台,把公司信息汇合到这个平台方便别人使用。我们把服务注册到Nacos也就是为了让别人发现我们的服务并且使用它。
本文基于Cloud Alibaba 2021.0.1.0 + Springboot 2.6.3
搭建服务
首先还是以单机模式启动nacos-serve
startup.cmd -m standalone
服务提供者与服务消费者的pom中均添加以下场景启动器
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId><version>2021.0.1.0</version></dependency><!--https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-nacos-discovery --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId><version>2021.0.1.0</version></dependency><!--https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-bootstrap --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bootstrap</artifactId><version>3.1.2</version></dependency>
我这里基于【Nacos手摸手教学一】来做的,服务提供者的bootstrap.yml 注意修改namespace、ip
spring: cloud: nacos: config: server-addr: ip:8848file-extension: yamlnamespace: 434e7801-0b5d-4fd2-b96a-acbac849fd84discovery: server-addr: ip:8848application: name: provider-serveprofiles: active: dev
服务消费者的配置文件一样的,不一样的也就是服务名consumer-serve
接下来我们启动两个服务,回到nacos控制台
原理
服务确实注册到Nacos了,表面来看我们相当于登记了姓名,那么如果我把某个服务停了,nacos又是如何知道该服务还能不能正常使用的呢?我们不仅要知其然,也要知其所以然。
大致流程:每个服务都会有一个nacos client,它用来和nacos server打交道,用来具体的服务注册、查询等操作,服务提供者在启动的时候会向nacos server注册自己,服务消费者在启动的时候订阅nacos server上的服务提供者。
NacosNamingService 中的 registerInstance 方法用于向Nacos注册实例,Nacos 中的实例分为临时和永久2种类型,注册前先判断实例是否是临时实例(默认都是临时实例),如果是Instance是临时实例,则创建一个 BeatTask 心跳线程定期调用 HTTP PUT /instance/beat 向 Nacos 服务器发送心跳,然后调用 HTTP POST /instance向 Nacos 服务器注册实例。
BeatTask 线程通过 HTTP PUT /instance/beat 向 Nacos 发送心跳请求,如果当前实例在服务器上不存在,则重新注册实例,否则等待执行下一次心跳
publicvoidrun() { if (beatInfo.isStopped()) { return; } //下一次心跳时间longnextTime=beatInfo.getPeriod(); try { //发送心跳JsonNoderesult=serverProxy.sendBeat(beatInfo, BeatReactor.this.lightBeatEnabled); longinterval=result.get("clientBeatInterval").asLong(); booleanlightBeatEnabled=false; if (result.has(CommonParams.LIGHT_BEAT_ENABLED)) { lightBeatEnabled=result.get(CommonParams.LIGHT_BEAT_ENABLED).asBoolean(); } BeatReactor.this.lightBeatEnabled=lightBeatEnabled; //使用服务器返回的心跳时间if (interval>0) { nextTime=interval; } intcode=NamingResponseCode.OK; if (result.has(CommonParams.CODE)) { code=result.get(CommonParams.CODE).asInt(); } //服务器返回实例不存在,重新注册实例if (code==NamingResponseCode.RESOURCE_NOT_FOUND) { Instanceinstance=newInstance(); instance.setPort(beatInfo.getPort()); instance.setIp(beatInfo.getIp()); instance.setWeight(beatInfo.getWeight()); instance.setMetadata(beatInfo.getMetadata()); instance.setClusterName(beatInfo.getCluster()); instance.setServiceName(beatInfo.getServiceName()); instance.setInstanceId(instance.getInstanceId()); instance.setEphemeral(true); try { //重新注册实例serverProxy.registerService(beatInfo.getServiceName(), NamingUtils.getGroupName(beatInfo.getServiceName()), instance); } catch (Exceptionignore) { } } } catch (NacosExceptionex) { NAMING_LOGGER.error("[CLIENT-BEAT] failed to send beat: {}, code: {}, msg: {}", JacksonUtils.toJson(beatInfo), ex.getErrCode(), ex.getErrMsg()); } //开启下一次心跳executorService.schedule(newBeatTask(beatInfo), nextTime, TimeUnit.MILLISECONDS); }
服务端也会为每个 Service 启动一个 ClientBeatCheckTask 线程用于检测该 Service 下所有的实例的健康状态,如果实例的 lastBeat 最后心跳时间超过了心跳超时时间(默认15秒),则设置 healthy 健康状态为 false,并通过 UDP 向客户端 push 最新的数据,如果 lastBeat 超过30秒,表示此实例已不可用,通过调用 HTTP DELETE /instance 从实例列表中删除该实例。