1、服务注册与发现
基于1.4版本的nacos
1.1、服务注册
1、客户端初始化
读取配置获取nacos服务中心地址,命名空间,group等信息
通过http请求建立连接。
2、发生注册请求
通过/nacos/v1/ns/instance接口提交请求
3、服务端处理
根据实例类型(配置参数:ephemeral)决定存储方式:
临时实例(true):存储内存中,通过AP模式(Distro 协议)快速同步集群节点
持久化实例(false),写入数据库中,通过CP模式(Raft 协议)保证一致性。
class NacosNamingService {
// 初始化配置数据,并拉取服务器信息
private void init(Properties properties) throws NacosException {
ValidatorUtils.checkInitParam(properties);
this.namespace = InitUtils.initNamespaceForNaming(properties);
InitUtils.initSerialization();
initServerAddr(properties);
InitUtils.initWebRootContext(properties);
initCacheDir();
initLogName(properties);
this.serverProxy = new NamingProxy(this.namespace, this.endpoint, this.serverList, properties);
// 初始化心跳检查的任务
this.beatReactor = new BeatReactor(this.serverProxy, initClientBeatThreadCount(properties));
// 初始化更新服务的任务
this.hostReactor = new HostReactor(this.serverProxy, beatReactor, this.cacheDir, isLoadCacheAtStart(properties),
isPushEmptyProtect(properties), initPollingThreadCount(properties));
}
// 实例化对象
@Override
public void registerInstance(String serviceName, String groupName, String ip, int port, String clusterName)
throws NacosException {
Instance instance = new Instance();
instance.setIp(ip);
instance.setPort(port);
instance.setWeight(1.0);
instance.setClusterName(clusterName);
registerInstance(serviceName, groupName, instance);
}
// 1、判断心跳时间(5s),服务器超时时间(30s)是否超过
// 2、启动线程每过5s调用一次心跳接口
// 3、调用服务端注册接口
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
NamingUtils.checkInstanceIsLegal(instance);
String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
if (instance.isEphemeral()) {
BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
beatReactor.addBeatInfo(groupedServiceName, beatInfo);
}
serverProxy.registerService(groupedServiceName, groupName, instance);
}
}
1.2、心跳与健康检查
服务注册之后,nacos会持续监控实例健康状态:
1、客户端心跳(临时实例Ephemeral才有)
主动上报:临时实例每5秒向nacos发送健康检查(HTTP POST /nacos/v1/ns/instance/beat
)
服务端处理:收到心跳刷新最近的活跃时间,若15秒没有收到心跳,则标记为不健康状态
30秒未收到,则剔除实例(ClientBeatCheckTask#run方法)
2、服务端健康检查
被动检查:持久化实例依赖服务端主动探测。
探测配置:通过控制台配置健康检查路径(如:/health),nacos根据配置频次判断是否存活。
服务端处理:服务器启动会创建一个健康检查任务,这个任务每隔2000-7000毫秒之间执行健康检查,主要的健康检查方式有TCP(TcpSuperSenseProcessor),HTTP(HttpHealthCheckProcessor),MYSQL(MysqlHealthCheckProcessor)。
tcp方式:根据服务ip地址和端口判断是否连接成功。(默认方式)com.alibaba.nacos.naming.healthcheck.TcpSuperSenseProcessor.TaskProcessor
http方式:根据服务ip地址和端口发送http请求,请求路径需要配置。
com.alibaba.nacos.naming.healthcheck.HttpHealthCheckProcessor#process
mysql方式:根据服务ip地址和端口发送mysql连接。
com.alibaba.nacos.naming.healthcheck.MysqlHealthCheckProcessor.MysqlCheckTask
1.3、服务发现
服务发现就是指当有服务实例注册成功之后,其它服务可以发现这些服务实例。
nacos提供2种订阅方式:
1、主动查询:指客户端主动向服务端发生查询请求获取实例,也就是拉(Pull)的方式
2、被动查询(服务订阅):客户端向服务端发送一个订阅服务的请求,当被订阅的服务有信息变更,nacos就会主动把服务实例的消息推送给客户端,也就是推(Push)的方式
nacos整合springCloud的时候默认是订阅方式。
订阅方式的实现:
第一步,客户端在启动的时候,会去构建一个叫PushReceiver
的类,这个类会去创建一个UDP Socket
,端口是随机的
第二步,调用NamingService#subscribe
来发起订阅时,会先去服务端查询需要订阅服务的所有实例信息,之后会将所有服务实例数据存到客户端的一个内部缓存中。并且在查询的时候,会将这个UDP Socket
的端口作为一个参数传到服务端,服务端接收到这个UDP
端口后,后续就通过这个端口给客户端推送服务实例数据
第三步,会为这次订阅开启一个不定时执行的任务(com.alibaba.nacos.common.notify.DefaultPublisher#openEventHandler),这个任务会去从服务端查询订阅的服务实例信息,然后更新内部缓存(com.alibaba.cloud.nacos.discovery.NacosWatch#start)
之所以不定时,是因为这个当执行异常的时候,下次执行的时间间隔就会变长,但是最多不超过60s
,正常是10s
,这个10s
是查询服务实例是服务端返回的
那么既然有了服务变动推送的功能,为什么还要定时去查询更新服务实例信息呢?
其实很简单,那就是因为UDP通信不稳定
导致的
虽然有Push
,但是由于UDP
通信自身的不确定性,有可能会导致客户端接收变动信息失败
所以这里就加了一个定时任务,弥补这种可能性,属于一个兜底的方案。
public class NacosNamingService implements NamingService {
private void init(Properties properties) throws NacosException {
// 参数校验
ValidatorUtils.checkInitParam(properties);
// 命名空间初始
this.namespace = InitUtils.initNamespaceForNaming(properties);
// 序列化初始化
InitUtils.initSerialization();
// 服务器地址初始化
initServerAddr(properties);
// Web上下文初始化
InitUtils.initWebRootContext(properties);
// 缓存目录初始化
initCacheDir();
// 日志名称初始化
initLogName(properties);
// 用于与Nacos服务器通信
this.serverProxy = new NamingProxy(this.namespace, this.endpoint, this.serverList, properties