Springboot+Nacos项目
微服务
微服务(Microservices)是一种软件架构风格,他区别与单体架构,将拆分为多个小型的、独立的服务,每个服务都可以独立开发、部署和维护。这些服务通过轻量级的API进行通信。
Nacos简述
Nacos 用于发现、配置和管理微服务。nacos有2个核心功能,一个是注册中心,一个是配置中心。这里Nacos学习就是围绕着注册中心和配置中心2个核心功能展开的。
-
注册中心的重点
服务的注册:将服务注册到Nacos上
服务的发现:找到相关的服务,使用RPC组件完成服务的调用 -
配置中心的重点
发现配置
获取配置
当前是以Naocs2.4.3版本学习总结的。
Spring Boot整合Nacos步骤
服务版本说明:
官网链接:https://github.com/alibaba/spring-cloud-alibaba/wiki/版本说明
添加依赖:
// nacos依赖引入
implementation 'com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery:2021.0.5.0'
implementation 'com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-config:2021.0.5.0'
在resources目录下增加bootstrap.yml文件
spring:
application:
name: springboot_demo
cloud:
nacos:
config:
file-extension: yml
server-addr: 127.0.0.1:8848
enabled: true
discovery:
server-addr: 127.0.0.1:8848
enabled: true
启动服务后在浏览器访问http://127.0.0.1:8848/nacoss查看下
服务已经注册到Nacos上了

增加测试用例直接调用接口注册服务
@SpringBootTest
class SpringbootDemo2ApplicationTests {
@Test
void contextLoads() throws Exception {
NamingService namingService = NamingFactory.createNamingService("127.0.0.1:8848");
// 将服务注册到nacos上
namingService.registerInstance("nacos.test.1", "11.11.11.11", 8888, "test");
Thread.sleep(Integer.MAX_VALUE);
}
Nacos的服务注册源码
客户端注册服务到Nacos上逻辑:
是使用这个方法
namingService.registerInstance("nacos.test.1", "11.11.11.11", 8888, "test");
将当前服务信息存到Map集合里,然后调用接口(/nacos/v1/ns/instance),将服务信息发给nacos
public class NacosNamingService implements NamingService {
@Override
public void registerInstance(String serviceName, String groupName, String ip, int port, String clusterName) throws NacosException {
// 生成实例对象,填充ip,端口,和服务名
Instance instance = new Instance();
instance.setIp(ip);
instance.setPort(port);
instance.setWeight(1.0);
instance.setClusterName(clusterName);
registerInstance(serviceName, groupName, instance);
}
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
// 判断是否是临时实例
if (instance.isEphemeral()) {
BeatInfo beatInfo = new BeatInfo();
// 拼接服务名 groupName@@serviceName
beatInfo.setServiceName(NamingUtils.getGroupedName(serviceName, groupName));
beatInfo.setIp(instance.getIp());
beatInfo.setPort(instance.getPort());
beatInfo.setCluster(instance.getClusterName());
beatInfo.setWeight(instance.getWeight());
beatInfo.setMetadata(instance.getMetadata());
beatInfo.setScheduled(false);
beatInfo.setPeriod(instance.getInstanceHeartBeatInterval());
// 心跳检测服务
beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);
}
// 调用api注册服务信息
serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);
}
}
public class NamingProxy {
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}",
namespaceId, serviceName, instance);
final Map<String, String> params = new HashMap<String, String>(9);
params.put(CommonParams.NAMESPACE_ID, namespaceId);
params.put(CommonParams.SERVICE_NAME, serviceName);
params.put(CommonParams.GROUP_NAME, groupName);
params.put(CommonParams.CLUSTER_NAME, instance.getClusterName());
params.put("ip", instance.getIp());
params.put("port", String.valueOf(instance.getPort()));
params.put("weight", String.valueOf(instance.getWeight()));
params.put("enable", String.valueOf(instance.isEnabled()));
params.put("healthy", String.valueOf(instance.isHealthy()));
params.put("ephemeral", String.valueOf(instance.isEphemeral()));
params.put("metadata", JSON.toJSONString(instance.getMetadata()));
reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, HttpMethod.POST);
}
}
服务端Nacos接收到注册请求的逻辑
是/nacos/v1/ns/instance接口提供服务的
是在InstanceController这个类里处理实例相关的请求。
@RestController
@RequestMapping(UtilsAndCommons.NACOS_NAMING_CONTEXT + UtilsAndCommons.NACOS_NAMING_INSTANCE_CONTEXT)
@ExtractorManager.Extractor(httpExtractor = NamingDefaultHttpParamExtractor.class)
public class InstanceController {
/**
* Register new instance.
* 注册一个新实例
* @param request http request
* @return 'ok' if success
* @throws Exception any error during register
*/
@CanDistro
@PostMapping
@TpsControl(pointName = "NamingInstanceRegister", name = "HttpNamingInstanceRegister")
@Secured(action = ActionTypes.WRITE)
public String register(HttpServletRequest request) throws Exception {
// 获取命名空间namespaceId
final String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID,
Constants.DEFAULT_NAMESPACE_ID);
// 获取服务提供者的服务名称,从请求里
final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
// 检查服务名称是否合法default@@serviceName
NamingUtils.checkServiceNameFormat(serviceName);
// 构建instance,服务提供者相关信息封装成Instance对象
final Instance instance = HttpRequestInstanceBuilder.newBuilder()
.setDefaultInstanceEphemeral(switchDomain.isDefaultInstanceEphemeral()).setRequest(request).build();
// 完成服务的注册功能
getInstanceOperator().registerInstance(namespaceId, serviceName, instance);
// 服务注册完成,发布相关事件,触发相关监听器
NotifyCenter.publishEvent(new RegisterInstanceTraceEvent(System.currentTimeMillis(),
NamingRequestUtil.getSourceIpForHttpRequest(request), false, namespaceId,
NamingUtils.getGroupName(serviceName), NamingUtils.getServiceName(serviceName), instance.getIp(),
instance.getPort()));
return "ok";
}
}
@org.springframework.stereotype.Service
public class InstanceOperatorClientImpl implements InstanceOperator {
/**
* This method creates {@code IpPortBasedClient} if it doesn't exist.
*/
@Override
public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
// 校验Instance对象是否合法
NamingUtils.checkInstanceIsLegal(instance);
// 判断是否是临时实例
boolean ephemeral = instance.isEphemeral();
// 获取客户端ID address + ID_DELIMITER + ephemeral
// 每个客户端都有一个clientID,是nacos2.0之后提供的,一个grpc长连接对应一个client
String clientId = IpPortBasedClient.getClientId(instance.toInetAddr(), ephemeral);
// clientId不存在处理,创建client对象,和clientId绑定,并且把client对象存储到client集合中
createIpPortClientIfAbsent(clientId);
// 获取创建的服务
Service service = getService(namespaceId, serviceName, ephemeral);
// 完成具体的服务注册
clientOperationService.registerInstance(service, instance, clientId);
}
// clientManager用于客户端的管理
private void createIpPortClientIfAbsent(String clientId) {
if (!clientManager.contains(clientId)) {
ClientAttributes clientAttributes = ClientAttributesFilter.getCurrentClientAttributes()
.orElse(new ClientAttributes());
// 设置客户端连接
clientManager.clientConnected(clientId, clientAttributes);
}
}
}
@DependsOn({"clientServiceIndexesManager", "namingMetadataManager"})
@Component("clientManager")
public class ClientManagerDelegate implements ClientManager {
// 基于TCP连接的客户端管理器,负责管理长连接client和clientID的映射关系
private final ConnectionBasedClientManager connectionBasedClientManager;
// 临时实例,基于IP和PORT的客户端管理器
private final EphemeralIpPortClientManager ephemeralIpPortClientManager;
// 持久化实例,基于IP和PORT的客户端管理器
private final PersistentIpPortClientManager persistentIpPortClientManager;
public ClientManagerDelegate(ConnectionBasedClientManager connectionBasedClientManager,
EphemeralIpPortClientManager ephemeralIpPortClientManager,
PersistentIpPortClientManager persistentIpPortClientManager) {
this.connectionBasedClientManager = connectionBasedClientManager;
this.ephemeralIpPortClientManager = ephemeralIpPortClientManager;
this.persistentIpPortClientManager = persistentIpPortClientManager;
}
}
@DependsOn("clientServiceIndexesManager")
@Component("ephemeralIpPortClientManager")
public class EphemeralIpPortClientManager implements ClientManager {
// 存储所有客户端信息
private final ConcurrentMap<String, IpPortBasedClient> clients = new ConcurrentHashMap<>();
private final DistroMapper distroMapper;
private final ClientFactory<IpPortBasedClient> clientFactory;
public EphemeralIpPortClientManager(DistroMapper distroMapper, SwitchDomain switchDomain) {
this.distroMapper = distroMapper;
GlobalExecutor.scheduleExpiredClientCleaner(new ExpiredClientCleaner(this, switchDomain), 0,
Constants.DEFAULT_HEART_BEAT_INTERVAL, TimeUnit.MILLISECONDS);
clientFactory = ClientFactoryHolder.getInstance().findClientFactory(ClientConstants.EPHEMERAL_IP_PORT);
}
@Override
public boolean clientConnected(String clientId, ClientAttributes attributes) {
// 根据clinetId,创建了新client对象,然后把client对象加到clients集合中
return clientConnected(clientFactory.newClient(clientId, attributes));
}
@Override
public boolean clientConnected(final Client client) {
// 如果clients集合中没有client,就重新创建一个并添加到集合中
clients.computeIfAbsent(client.getClientId(), s -> {
Loggers.SRV_LOG.info("Client connection {} connect", client.getClientId());
IpPortBasedClient ipPortBasedClient = (IpPortBasedClient) client;
// 设置心跳检测的定时任务
ipPortBasedClient.init();
return ipPortBasedClient;
});
return true;
}
}
@DependsOn("namingSubscriberServiceV2Impl")
@SuppressWarnings("PMD.ServiceOrDaoClassShouldEndWithImplRule")
@Component
public class ClientOperationServiceProxy implements ClientOperationService {
@Override
public void registerInstance(Service service, Instance instance, String clientId) throws NacosException {
// 根据是否临时实例,选择需要的service实现
final ClientOperationService operationService = chooseClientOperationService(instance);
operationService.registerInstance(service, instance, clientId);
}
}
@Component("ephemeralClientOperationService")
public class EphemeralClientOperationServiceImpl implements ClientOperationService {
private final ClientManager clientManager;
@Override
public void registerInstance(Service service, Instance instance, String clientId) throws NacosException {
// 检查实例对象
NamingUtils.checkInstanceIsLegal(instance);
// 一个服务对应一个Service对象,有2个集合存储了所有Service信息,所以用了单例
Service singleton = ServiceManager.getInstance().getSingleton(service);
if (!singleton.isEphemeral()) {
throw new NacosRuntimeException(NacosException.INVALID_PARAM,
String.format("Current service %s is persistent service, can't register ephemeral instance.",
singleton.getGroupedServiceName()));
}
// 根据clientId获取到Client对象
Client client = clientManager.getClient(clientId);
// 检查client是否合法
checkClientIsLegal(client, clientId);
// InstancePublishInfo需要注册的实例信息
InstancePublishInfo instanceInfo = getPublishInfo(instance);
// 服务注册实例,就是Service和Instace的关系
// 保存了Service和Instance的映射关系,在publishers;通过事件发布机制,发布了ClientChangedEvent事件,完成Nacos节点信息的同步
// 完成了Client Service Instance的关联
// client对象保存了publishers对象
client.addServiceInstance(singleton, instanceInfo);
// 记录最后更新时间
client.setLastUpdatedTime();
// 重新计算版本
client.recalculateRevision();
// 发布服务注册的事件,完成客户端订阅的通知
NotifyCenter.publishEvent(new ClientOperationEvent.ClientRegisterServiceEvent(singleton, clientId));
// 元数据处理
NotifyCenter
.publishEvent(new MetadataEvent.InstanceMetadataEvent(singleton, instanceInfo.getMetadataId(), false));
}
}
public class ServiceManager {
private static final ServiceManager INSTANCE = new ServiceManager();
private final ConcurrentHashMap<Service, Service> singletonRepository;
private final ConcurrentHashMap<String, Set<Service>> namespaceSingletonMaps;
/**
* Get singleton service. Put to manager if no singleton.
*
* @param service new service
* @return if service is exist, return exist service, otherwise return new service
*/
public Service getSingleton(Service service) {
// singletonRepository中没有service时候,新建放进去
Service result = singletonRepository.computeIfAbsent(service, key -> {
// 发布了ServiceMetadataEvent事件,干啥的?
NotifyCenter.publishEvent(new MetadataEvent.ServiceMetadataEvent(service, false));
return service;
});
// 将Service对象放到namespaceSingletonMaps里
namespaceSingletonMaps.computeIfAbsent(result.getNamespace(), namespace -> new ConcurrentHashSet<>()).add(result);
return result;
}
}
public abstract class AbstractClient implements Client {
protected final ConcurrentHashMap<Service, InstancePublishInfo> publishers = new ConcurrentHashMap<>(16, 0.75f, 1);
protected final ConcurrentHashMap<Service, Subscriber> subscribers = new ConcurrentHashMap<>(16, 0.75f, 1);
protected volatile long lastUpdatedTime;
protected final AtomicLong revision;
protected ClientAttributes attributes;
@Override
public boolean addServiceInstance(Service service, InstancePublishInfo instancePublishInfo) {
if (instancePublishInfo instanceof BatchInstancePublishInfo) {
InstancePublishInfo old = publishers.put(service, instancePublishInfo);
MetricsMonitor.incrementIpCountWithBatchRegister(old, (BatchInstancePublishInfo) instancePublishInfo);
} else {
if (null == publishers.put(service, instancePublishInfo)) {
MetricsMonitor.incrementInstanceCount();
}
}
// 发布ClientChangedEvent事件
// 做同步的,客户端在Nacos的某个节点上注册了服务的信息,如果Nacos是集群部署,完成Nacos节点信息的同步
NotifyCenter.publishEvent(new ClientEvent.ClientChangedEvent(this));
Loggers.SRV_LOG.info("Client change for service {}, {}", service, getClientId());
return true;
}
}
心跳服务原理
客户端向Nacos发起心跳检测请求
当前服务默认是临时实例,其中是临时实例才做发送心跳功能。心跳发送间隔时间默认5秒,延迟5秒执行发送心跳服务。调用接口/nacos/v1/ns/instance/beat 发送服务信息到Nacos。然后检查响应信息,如果响应状态码是20404,则重新发起服务注册。这个发送心跳服务是每隔5秒执行一次的。
客户端源码
客户端每隔5秒发送一次心跳服务到Nacos
public class NacosNamingService implements NamingService {
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
if (instance.isEphemeral()) {
BeatInfo beatInfo = new BeatInfo();
beatInfo.setServiceName(NamingUtils.getGroupedName(serviceName, groupName));
beatInfo.setIp(instance.getIp());
beatInfo.setPort(instance.getPort());
beatInfo.setCluster(instance.getClusterName());
beatInfo.setWeight(instance.getWeight());
beatInfo.setMetadata(instance.getMetadata());
beatInfo.setScheduled(false);
// 默认发生心跳检测的时间,5秒
beatInfo.setPeriod(instance.getInstanceHeartBeatInterval());
beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);
}
serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);
}
}
BeatReactor 发送心跳代码
public class BeatReactor {
public void addBeatInfo(String serviceName, BeatInfo beatInfo) {
NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);
String key = buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort());
BeatInfo existBeat = null;
//fix #1733
if ((existBeat = dom2Beat.remove(key)) != null) {
existBeat.setStopped(true);
}
dom2Beat.put(key, beatInfo);
// 定时任务,是延迟5秒执行的,BeatTask是要执行的任务
executorService.schedule(new BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);
MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());
}
class BeatTask implements Runnable {
BeatInfo beatInfo;
public BeatTask(BeatInfo beatInfo) {
this.beatInfo = beatInfo;
}
@Override
public void run() {
if (beatInfo.isStopped()) {
return;
}
long nextTime = beatInfo.getPeriod();
try {
// 发送心跳服务
JSONObject result = serverProxy.sendBeat(beatInfo, BeatReactor.this.lightBeatEnabled);
// 获取心跳的间隔时间,是服务端返回的
long interval = result.getIntValue("clientBeatInterval");
boolean lightBeatEnabled = false;
if (result.containsKey(CommonParams.LIGHT_BEAT_ENABLED)) {
lightBeatEnabled = result.getBooleanValue(CommonParams.LIGHT_BEAT_ENABLED);
}
BeatReactor.this.lightBeatEnabled = lightBeatEnabled;
if (interval > 0) {
nextTime = interval;
}
int code = NamingResponseCode.OK;
if (result.containsKey(CommonParams.CODE)) {
code = result.getIntValue(CommonParams.CODE);
}
if (code == NamingResponseCode.RESOURCE_NOT_FOUND) {
Instance instance = new Instance();
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 {
// Nacos上没找到当前服务,重新注册本地服务
serverProxy.registerService(beatInfo.getServiceName(),
NamingUtils.getGroupName(beatInfo.getServiceName()), instance);
} catch (Exception ignore) {
}
}
} catch (NacosException ne) {
NAMING_LOGGER.error("[CLIENT-BEAT] failed to send beat: {}, code: {}, msg: {}",
JSON.toJSONString(beatInfo), ne.getErrCode(), ne.getErrMsg());
}
// 开启一个定时任务,延迟5秒
executorService.schedule(new BeatTask(beatInfo), nextTime, TimeUnit.MILLISECONDS);
}
}
}
Nacos接收心跳检测请求处理逻辑
@RestController
@RequestMapping(UtilsAndCommons.NACOS_NAMING_CONTEXT + UtilsAndCommons.NACOS_NAMING_INSTANCE_CONTEXT)
@ExtractorManager.Extractor(httpExtractor = NamingDefaultHttpParamExtractor.class)
public class InstanceController {
// Nacos提供心跳处理接口
@CanDistro
@PutMapping("/beat")
@TpsControl(pointName = "HttpHealthCheck", name = "HttpHealthCheck")
@Secured(action = ActionTypes.WRITE)
@ExtractorManager.Extractor(httpExtractor = NamingInstanceBeatHttpParamExtractor.class)
public ObjectNode beat(HttpServletRequest request) throws Exception {
ObjectNode result = JacksonUtils.createEmptyJsonNode();
// 设置响应里的心跳间隔时长,默认也是5秒
result.put(SwitchEntry.CLIENT_BEAT_INTERVAL, switchDomain.getClientBeatInterval());
// 从请求中获取心跳注册的相关信息
String beat = WebUtils.optional(request, "beat", StringUtils.EMPTY);
RsInfo clientBeat = null;
if (StringUtils.isNotBlank(beat)) {
clientBeat = JacksonUtils.toObj(beat, RsInfo.class);
}
String clusterName = WebUtils.optional(request, CommonParams.CLUSTER_NAME,
UtilsAndCommons.DEFAULT_CLUSTER_NAME);
String ip = WebUtils.optional(request, "ip", StringUtils.EMPTY);
int port = Integer.parseInt(WebUtils.optional(request, "port", "0"));
if (clientBeat != null) {
if (StringUtils.isNotBlank(clientBeat.getCluster())) {
clusterName = clientBeat.getCluster();
} else {
// fix #2533
clientBeat.setCluster(clusterName);
}
ip = clientBeat.getIp();
port = clientBeat.getPort();
}
String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
NamingUtils.checkServiceNameFormat(serviceName);
Loggers.SRV_LOG.debug("[CLIENT-BEAT] full arguments: beat: {}, serviceName: {}, namespaceId: {}", clientBeat,
serviceName, namespaceId);
BeatInfoInstanceBuilder builder = BeatInfoInstanceBuilder.newBuilder();
builder.setRequest(request);
// 处理心跳服务
int resultCode = getInstanceOperator().handleBeat(namespaceId, serviceName, ip, port, clusterName, clientBeat,
builder);
// 封装响应信息,心跳间隔时间,是否简化心跳信息
result.put(CommonParams.CODE, resultCode);
result.put(SwitchEntry.CLIENT_BEAT_INTERVAL,
getInstanceOperator().getHeartBeatInterval(namespaceId, serviceName, ip, port, clusterName));
// lightBeatEnabled 默认为true 意思是下一场请求可以简化心跳信息
result.put(SwitchEntry.LIGHT_BEAT_ENABLED, switchDomain.isLightBeatEnabled());
return result;
}
}
InstanceOperatorClientImpl.handleBeat 处理心跳检测请求
@org.springframework.stereotype.Service
public class InstanceOperatorClientImpl implements InstanceOperator {
@Override
public int handleBeat(String namespaceId, String serviceName, String ip, int port, String cluster,
RsInfo clientBeat, BeatInfoInstanceBuilder builder) throws NacosException {
Service service = getService(namespaceId, serviceName, true);
String clientId = IpPortBasedClient.getClientId(ip + InternetAddressUtil.IP_PORT_SPLITER + port, true);
IpPortBasedClient client = (IpPortBasedClient) clientManager.getClient(clientId);
if (null == client || !client.getAllPublishedService().contains(service)) {
// 说明接收心跳注册的服务不存在,之前没有注册过
if (null == clientBeat) {
return NamingResponseCode.RESOURCE_NOT_FOUND;
}
// 说明这个心跳服务是不存在,心跳服务提供的信息是完整的
Instance instance = builder.setBeatInfo(clientBeat).setServiceName(serviceName).build();
// 注册这个服务
registerInstance(namespaceId, serviceName, instance);
// 获取client对象
client = (IpPortBasedClient) clientManager.getClient(clientId);
}
if (!ServiceManager.getInstance().containSingleton(service)) {
throw new NacosException(NacosException.SERVER_ERROR,
"service not found: " + serviceName + "@" + namespaceId);
}
if (null == clientBeat) {
clientBeat = new RsInfo();
clientBeat.setIp(ip);
clientBeat.setPort(port);
clientBeat.setCluster(cluster);
clientBeat.setServiceName(serviceName);
}
ClientBeatProcessorV2 beatProcessor = new ClientBeatProcessorV2(namespaceId, clientBeat, client);
// 异步处理心跳服务
HealthCheckReactor.scheduleNow(beatProcessor);
client.setLastUpdatedTime();
return NamingResponseCode.OK;
}
}
异步任务处理心跳服务
public class ClientBeatProcessorV2 implements BeatProcessor {
@Override
public void run() {
if (Loggers.EVT_LOG.isDebugEnabled()) {
Loggers.EVT_LOG.debug("[CLIENT-BEAT] processing beat: {}", rsInfo.toString());
}
String ip = rsInfo.getIp();
int port = rsInfo.getPort();
String serviceName = NamingUtils.getServiceName(rsInfo.getServiceName());
String groupName = NamingUtils.getGroupName(rsInfo.getServiceName());
Service service = Service.newService(namespace, groupName, serviceName, rsInfo.isEphemeral());
// 根据心跳请求中的信息找到Nacos里注册的服务
HealthCheckInstancePublishInfo instance = (HealthCheckInstancePublishInfo) client.getInstancePublishInfo(service);
// 判断Nacos注册的服务和心跳请求的信息是否一致
if (instance.getIp().equals(ip) && instance.getPort() == port) {
// 信息一致,之前成功注册过
if (Loggers.EVT_LOG.isDebugEnabled()) {
Loggers.EVT_LOG.debug("[CLIENT-BEAT] refresh beat: {}", rsInfo);
}
// 设置最后一次心跳检查的时间
instance.setLastHeartBeatTime(System.currentTimeMillis());
if (!instance.isHealthy()) {
// 说明当前服务已经是不健康状态
// 更新服务为健康状态
instance.setHealthy(true);
Loggers.EVT_LOG.info("service: {} {POS} {IP-ENABLED} valid: {}:{}@{}, region: {}, msg: client beat ok",
rsInfo.getServiceName(), ip, port, rsInfo.getCluster(), UtilsAndCommons.LOCALHOST_SITE);
// 发布事件 向订阅者发起请求,同步Nacos节点信息,健康状态变化事件
NotifyCenter.publishEvent(new ServiceEvent.ServiceChangedEvent(service));
NotifyCenter.publishEvent(new ClientEvent.ClientChangedEvent(client));
NotifyCenter.publishEvent(new HealthStateChangeTraceEvent(System.currentTimeMillis(),
service.getNamespace(), service.getGroup(), service.getName(), instance.getIp(),
instance.getPort(), true, "client_beat"));
}
}
}
}
服务器端Nacos反向探测机制
@org.springframework.stereotype.Service
public class InstanceOperatorClientImpl implements InstanceOperator {
@Override
public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
NamingUtils.checkInstanceIsLegal(instance);
boolean ephemeral = instance.isEphemeral();
String clientId = IpPortBasedClient.getClientId(instance.toInetAddr(), ephemeral);
// 创建Client对象,同时和clientId绑定
//同时完成心跳服务的定时任务
createIpPortClientIfAbsent(clientId);
Service service = getService(namespaceId, serviceName, ephemeral);
clientOperationService.registerInstance(service, instance, clientId);
}
}
在注册实例的方法中createIpPortClientIfAbsent这个方法会完成相关的操作。
private void createIpPortClientIfAbsent(String clientId) {
// 如果当前注册的服务 之前没有记录信息
if (!clientManager.contains(clientId)) {
ClientAttributes clientAttributes;
if (ClientAttributesFilter.threadLocalClientAttributes.get() != null) {
clientAttributes = ClientAttributesFilter.threadLocalClientAttributes.get();
} else {
clientAttributes = new ClientAttributes();
}
// 测试客户端的连接
clientManager.clientConnected(clientId, clientAttributes);
}
}
最后的方法是测试客户端的连接测试,这里有区分是临时实例还是永久实例的处理
这里是临时实例的处理
@DependsOn("clientServiceIndexesManager")
@Component("ephemeralIpPortClientManager")
public class EphemeralIpPortClientManager implements ClientManager {
@Override
public boolean clientConnected(final Client client) {
clients.computeIfAbsent(client.getClientId(), s -> {
Loggers.SRV_LOG.info("Client connection {} connect", client.getClientId());
IpPortBasedClient ipPortBasedClient = (IpPortBasedClient) client;
// 客户端信息的初始化操作,在这里完成心跳检测的任务
ipPortBasedClient.init();
return ipPortBasedClient;
});
return true;
}
}
init方法里是重点
其中永久节点的健康检查是由服务端定时与客户端建立tcp连接做健康检查,是服务端主动发起的探测,服务器定时请求客户端判断是否健康。
public class IpPortBasedClient extends AbstractClient {
public void init() {
// 初始化客户端Client的
if (ephemeral) { // 临时实例,延迟5秒,开启一个间隔5秒的定时任务来做心跳健康检查
beatCheckTask = new ClientBeatCheckTaskV2(this);
HealthCheckReactor.scheduleCheck(beatCheckTask);
} else { // 持久化实例,客户端不发心跳检查请求
healthCheckTaskV2 = new HealthCheckTaskV2(this);
HealthCheckReactor.scheduleCheck(healthCheckTaskV2);
}
}
}
这里是临时实例:使用定时任务处理
public class HealthCheckReactor {
public static void scheduleCheck(BeatCheckTask task) {
Runnable wrapperTask =
task instanceof NacosHealthCheckTask ? new HealthCheckTaskInterceptWrapper((NacosHealthCheckTask) task)
: task;
// 开启一个定时任务,延迟5秒执行,间隔时间5秒
futureMap.computeIfAbsent(task.taskKey(),
k -> GlobalExecutor.scheduleNamingHealth(wrapperTask, 5000, 5000, TimeUnit.MILLISECONDS));
}
}
开启了一个延迟5秒执行。间隔5秒的定时任务,来执行心跳检测逻辑
public class ClientBeatCheckTaskV2 extends AbstractExecuteTask implements BeatCheckTask, NacosHealthCheckTask {
@Override
public void doHealthCheck() {
try {
// 获取所有的服务
Collection<Service> services = client.getAllPublishedService();
for (Service each : services) {
// 遍历每个服务
HealthCheckInstancePublishInfo instance = (HealthCheckInstancePublishInfo) client
.getInstancePublishInfo(each);
// 通过InstanceBeatCheckTask的检测器去检测实例的健康状态
interceptorChain.doInterceptor(new InstanceBeatCheckTask(client, each, instance));
}
} catch (Exception e) {
Loggers.SRV_LOG.warn("Exception while processing client beat time out.", e);
}
}
}
拦截器链执行的是InstanceBeatCheckTask中的方法,这里检查是否过期,服务是否健康
public class InstanceBeatCheckTask implements Interceptable {
private static final List<InstanceBeatChecker> CHECKERS = new LinkedList<>();
static {
CHECKERS.add(new UnhealthyInstanceChecker());
CHECKERS.add(new ExpiredInstanceChecker());
CHECKERS.addAll(NacosServiceLoader.load(InstanceBeatChecker.class));
}
@Override
public void passIntercept() {
for (InstanceBeatChecker each : CHECKERS) {
each.doCheck(client, service, instancePublishInfo);
}
}
}
非健康检查的逻辑如下,在isUnhealthy方法里完成判断
public class UnhealthyInstanceChecker implements InstanceBeatChecker {
@Override
public void doCheck(Client client, Service service, HealthCheckInstancePublishInfo instance) {
if (instance.isHealthy() && isUnhealthy(service, instance)) {
// 改变实例健康状态
changeHealthyStatus(client, service, instance);
}
}
// 具体实现
private boolean isUnhealthy(Service service, HealthCheckInstancePublishInfo instance) {
// 心跳检测的超时时间,默认是15秒
long beatTimeout = getTimeout(service, instance);
// 当前时间 - 上一次心跳检测时候, 大于15秒,则任务超时了,实例不健康
return System.currentTimeMillis() - instance.getLastHeartBeatTime() > beatTimeout;
}
private void changeHealthyStatus(Client client, Service service, HealthCheckInstancePublishInfo instance) {
// 设置当前实例为非健康
instance.setHealthy(false);
Loggers.EVT_LOG
.info("{POS} {IP-DISABLED} valid: {}:{}@{}@{}, region: {}, msg: client last beat: {}", instance.getIp(),
instance.getPort(), instance.getCluster(), service.getName(), UtilsAndCommons.LOCALHOST_SITE,
instance.getLastHeartBeatTime());
// 发布相关事件
NotifyCenter.publishEvent(new ServiceEvent.ServiceChangedEvent(service));
NotifyCenter.publishEvent(new ClientEvent.ClientChangedEvent(client));
NotifyCenter.publishEvent(new HealthStateChangeTraceEvent(System.currentTimeMillis(),
service.getNamespace(), service.getGroup(), service.getName(), instance.getIp(), instance.getPort(),
false, "client_beat"));
}
}
到这里临时实例的注册服务的健康检查完成。其实就是比较了上一次发送的心跳时间是否超过了间隔时间15秒。
这里是持久化的实例的处理如下:
public class ExpiredInstanceChecker implements InstanceBeatChecker {
@Override
public void doCheck(Client client, Service service, HealthCheckInstancePublishInfo instance) {
boolean expireInstance = ApplicationUtils.getBean(GlobalConfig.class).isExpireInstance();
// 如果实例过期了,就把节点移除掉,当前时间和上次心跳更新时间大于30秒
if (expireInstance && isExpireInstance(service, instance)) {
deleteIp(client, service, instance);
}
}
}
在HealthCheckTaskV2 处理的,这里是持久化实例的健康检查
public class HealthCheckTaskV2 extends AbstractExecuteTask implements NacosHealthCheckTask {
@Override
public void doHealthCheck() {
try {
initIfNecessary();
for (Service each : client.getAllPublishedService()) {
if (switchDomain.isHealthCheckEnabled(each.getGroupedServiceName())) {
InstancePublishInfo instancePublishInfo = client.getInstancePublishInfo(each);
ClusterMetadata metadata = getClusterMetadata(each, instancePublishInfo);
// getBean获取处理健康检查的对象
ApplicationUtils.getBean(HealthCheckProcessorV2Delegate.class).process(this, each, metadata);
if (Loggers.EVT_LOG.isDebugEnabled()) {
Loggers.EVT_LOG.debug("[HEALTH-CHECK] schedule health check task: {}", client.getClientId());
}
}
}
} catch (Throwable e) {
Loggers.SRV_LOG.error("[HEALTH-CHECK] error while process health check for {}", client.getClientId(), e);
} finally {
if (!cancelled) {
initCheckRT();
HealthCheckReactor.scheduleCheck(this);
// worst == 0 means never checked
if (this.getCheckRtWorst() > 0) {
// TLog doesn't support float so we must convert it into long
long checkRtLastLast = getCheckRtLastLast();
this.setCheckRtLastLast(this.getCheckRtLast());
if (checkRtLastLast > 0) {
long diff = ((this.getCheckRtLast() - this.getCheckRtLastLast()) * 10000) / checkRtLastLast;
if (Loggers.CHECK_RT.isDebugEnabled()) {
Loggers.CHECK_RT.debug("{}->normalized: {}, worst: {}, best: {}, last: {}, diff: {}",
client.getClientId(), this.getCheckRtNormalized(), this.getCheckRtWorst(),
this.getCheckRtBest(), this.getCheckRtLast(), diff);
}
}
}
}
}
}
}
进入process方法里
@Component
public class HttpHealthCheckProcessor implements HealthCheckProcessorV2 {
@Override
public void process(HealthCheckTaskV2 task, Service service, ClusterMetadata metadata) {
HealthCheckInstancePublishInfo instance = (HealthCheckInstancePublishInfo) task.getClient()
.getInstancePublishInfo(service);
if (null == instance) {
return;
}
try {
// TODO handle marked(white list) logic like v1.x.
if (!instance.tryStartCheck()) {
SRV_LOG.warn("http check started before last one finished, service: {} : {} : {}:{}",
service.getGroupedServiceName(), instance.getCluster(), instance.getIp(), instance.getPort());
healthCheckCommon
.reEvaluateCheckRT(task.getCheckRtNormalized() * 2, task, switchDomain.getHttpHealthParams());
return;
}
Http healthChecker = (Http) metadata.getHealthChecker();
int ckPort = metadata.isUseInstancePortForCheck() ? instance.getPort() : metadata.getHealthyCheckPort();
URL host = new URL(HTTP_PREFIX + instance.getIp() + ":" + ckPort);
URL target = new URL(host, healthChecker.getPath());
Map<String, String> customHeaders = healthChecker.getCustomHeaders();
Header header = Header.newInstance();
header.addAll(customHeaders);
// 发起服务提供者的异步请求,来检查服务是否可用,new HttpHealthCheckCallback(instance, task, service)是访问后的回调方法
ASYNC_REST_TEMPLATE.get(target.toString(), header, Query.EMPTY, String.class,
new HttpHealthCheckCallback(instance, task, service));
MetricsMonitor.getHttpHealthCheckMonitor().incrementAndGet();
} catch (Throwable e) {
instance.setCheckRt(switchDomain.getHttpHealthParams().getMax());
healthCheckCommon.checkFail(task, service, "http:error:" + e.getMessage());
healthCheckCommon.reEvaluateCheckRT(switchDomain.getHttpHealthParams().getMax(), task,
switchDomain.getHttpHealthParams());
}
}
}
我们可以发现作为持久化实例的处理。是通过http请求来访问服务提供方来检测是否可用访问来实现的。
心跳检测机制总结

启动当前Springboot程序如何将服务注册到Nacos上呢?
首先nacos-discovery依赖包里面有个文件spring.factories,内容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.cloud.nacos.discovery.NacosDiscoveryAutoConfiguration,\
com.alibaba.cloud.nacos.ribbon.RibbonNacosAutoConfiguration,\
com.alibaba.cloud.nacos.endpoint.NacosDiscoveryEndpointAutoConfiguration,\
com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration,\
com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration,\
com.alibaba.cloud.nacos.discovery.configclient.NacosConfigServerAutoConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.alibaba.cloud.nacos.discovery.configclient.NacosDiscoveryClientConfigServiceBootstrapConfiguration
启动过程中会加载这些配置类,实例化需要的bean
查看NacosServiceRegistryAutoConfiguration配置类,注册流程是在NacosServiceRegistry里开始做的
@Configuration
@EnableConfigurationProperties
@ConditionalOnNacosDiscoveryEnabled
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
@AutoConfigureAfter({ AutoServiceRegistrationConfiguration.class,
AutoServiceRegistrationAutoConfiguration.class,
NacosDiscoveryAutoConfiguration.class })
public class NacosServiceRegistryAutoConfiguration {
// 生成NacosServiceRegistry类型的bean,注入到容器中
@Bean
public NacosServiceRegistry nacosServiceRegistry(
NacosDiscoveryProperties nacosDiscoveryProperties) {
return new NacosServiceRegistry(nacosDiscoveryProperties);
}
......
}
注册本地服务
在com.alibaba.cloud.nacos.registry.NacosServiceRegistry#register里调用namingService.registerInstance方法,最后调用http接口,将服务信息注册到nacos上。
这个入口怎么进来呢?
是在启动springboot项目时,在其中spring生命周期里,getLifecycleProcessor().onRefresh();做的。
NacosAutoServiceRegistration继承了AbstractAutoServiceRegistration抽象类,在执行listener监听器那里做的,去注册本地的服务。实现了AbstractAutoServiceRegistration的register接口(org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration#register)
public abstract class AbstractAutoServiceRegistration<R extends Registration>
implements AutoServiceRegistration, ApplicationContextAware, ApplicationListener<WebServerInitializedEvent> {
@Override
@SuppressWarnings("deprecation")
public void onApplicationEvent(WebServerInitializedEvent event) {
bind(event);
}
@Deprecated
public void bind(WebServerInitializedEvent event) {
ApplicationContext context = event.getApplicationContext();
if (context instanceof ConfigurableWebServerApplicationContext) {
if ("management".equals(((ConfigurableWebServerApplicationContext) context).getServerNamespace())) {
return;
}
}
this.port.compareAndSet(0, event.getWebServer().getPort());
this.start();
}
public void start() {
if (!isEnabled()) {
if (logger.isDebugEnabled()) {
logger.debug("Discovery Lifecycle disabled. Not starting");
}
return;
}
if (!this.running.get()) {
this.context.publishEvent(new InstancePreRegisteredEvent(this, getRegistration()));
register();
if (shouldRegisterManagement()) {
registerManagement();
}
this.context.publishEvent(new InstanceRegisteredEvent<>(this, getConfiguration()));
this.running.compareAndSet(false, true);
}
}
protected void register() {
this.serviceRegistry.register(getRegistration());
}
}
public class NacosServiceRegistry implements ServiceRegistry<Registration> {
private static final Logger log = LoggerFactory.getLogger(NacosServiceRegistry.class);
private final NacosDiscoveryProperties nacosDiscoveryProperties;
private final NamingService namingService;
public NacosServiceRegistry(NacosDiscoveryProperties nacosDiscoveryProperties) {
this.nacosDiscoveryProperties = nacosDiscoveryProperties;
this.namingService = nacosDiscoveryProperties.namingServiceInstance();
}
@Override
public void register(Registration registration) {
if (StringUtils.isEmpty(registration.getServiceId())) {
log.warn("No service to register for nacos client...");
return;
}
String serviceId = registration.getServiceId();
String group = nacosDiscoveryProperties.getGroup();
Instance instance = getNacosInstanceFromRegistration(registration);
try {
// 这里将服务注册到nacos上
namingService.registerInstance(serviceId, group, instance);
log.info("nacos registry, {} {} {}:{} register finished", group, serviceId,
instance.getIp(), instance.getPort());
}
catch (Exception e) {
log.error("nacos registry, {} register failed...{},", serviceId,
registration.toString(), e);
// rethrow a RuntimeException if the registration is failed.
// issue : https://github.com/alibaba/spring-cloud-alibaba/issues/1132
rethrowRuntimeException(e);
}
}
}
总结:
- 在服务启动的时候,会调用api发起服务注册,通知nacos可以提供服务了
- 服务消费者在启动的时候会拉取自已要用的服务的列表
- 服务消费者每隔10秒去Nacos拉取一次服务实例,消费者根据返回的实例列表来发起请求。
- Nacos服务检测到服务实例发生变化(服务上下线)就会发送UDP协议通知服务消费者进行更新。
- 客户端会每5秒定时发送心跳到服务端,来维持他的一个健康的检查,
- Nacos定时任务检测:通过每5秒检查一下心跳信息来判断是否超时,就是用当前时间减去上次一心跳的时间,如果超过15秒则将节点设置为非健康状态并进行广播,如果超过30秒则将节点进行移除,说明节点不可用。

浙公网安备 33010602011771号