Nacos服務(wù)注冊(cè)與發(fā)現(xiàn)原理解讀
Nacos是阿里巴巴開(kāi)源的服務(wù)注冊(cè)與發(fā)現(xiàn)組件,同時(shí)也提供配置管理功能。它支持基于DNS和RPC的服務(wù)發(fā)現(xiàn),致力于幫助開(kāi)發(fā)人員發(fā)現(xiàn)、配置和管理微服務(wù)。
下面將深入解析其服務(wù)注冊(cè)與發(fā)現(xiàn)的核心原理,并提供關(guān)鍵源碼示例。
核心原理架構(gòu)
Nacos的服務(wù)注冊(cè)與發(fā)現(xiàn)架構(gòu)主要包含三個(gè)核心組件:
- 服務(wù)提供者:負(fù)責(zé)將自身服務(wù)實(shí)例注冊(cè)到Nacos Server
- 服務(wù)消費(fèi)者:負(fù)責(zé)從Nacos Server獲取服務(wù)列表并進(jìn)行服務(wù)調(diào)用
- Nacos Server:負(fù)責(zé)維護(hù)服務(wù)實(shí)例信息,處理注冊(cè)、發(fā)現(xiàn)、健康檢查等請(qǐng)求
服務(wù)注冊(cè)原理
服務(wù)注冊(cè)是指服務(wù)提供者將自身服務(wù)實(shí)例的信息注冊(cè)到Nacos Server的過(guò)程。
其核心流程如下:
- 1. 服務(wù)實(shí)例啟動(dòng)時(shí),通過(guò)SDK向Nacos Server發(fā)送注冊(cè)請(qǐng)求
- 2. Nacos Server接收請(qǐng)求并驗(yàn)證信息,將服務(wù)實(shí)例信息存儲(chǔ)到內(nèi)存和持久化存儲(chǔ)中
- 3. 服務(wù)實(shí)例定期向Nacos Server發(fā)送心跳包維持注冊(cè)狀態(tài)
服務(wù)注冊(cè)核心源碼
下面是Nacos服務(wù)注冊(cè)的核心源碼示例:
// ServiceRegistration接口定義服務(wù)注冊(cè)的基本方法
public interface ServiceRegistration<T> {
void register();
void deregister();
T getRegistration();
}
// NacosServiceRegistry實(shí)現(xiàn)了服務(wù)注冊(cè)邏輯
@Service
public class NacosServiceRegistry implements ServiceRegistry<NacosRegistration> {
private final NacosServiceManager nacosServiceManager;
private final NacosRegistrationProperties registrationProperties;
public NacosServiceRegistry(NacosServiceManager nacosServiceManager,
NacosRegistrationProperties registrationProperties) {
this.nacosServiceManager = nacosServiceManager;
this.registrationProperties = registrationProperties;
}
@Override
public void register(NacosRegistration registration) {
if (StringUtils.isEmpty(registration.getServiceId())) {
log.warn("No service to register for nacos client...");
return;
}
// 構(gòu)建服務(wù)實(shí)例注冊(cè)信息
Instance instance = getNacosInstanceFromRegistration(registration);
try {
// 調(diào)用Nacos客戶端進(jìn)行服務(wù)注冊(cè)
namingService().registerInstance(
registration.getServiceId(),
registration.getGroupName(),
instance);
log.info("nacos registry, {} {}:{} register finished",
registration.getServiceId(),
instance.getIp(), instance.getPort());
} catch (Exception e) {
log.error("nacos registry error", e);
}
}
// 獲取Nacos命名服務(wù)客戶端
private NamingService namingService() throws NacosException {
return nacosServiceManager.getNamingService(registrationProperties);
}
// 從注冊(cè)信息構(gòu)建Nacos實(shí)例對(duì)象
private Instance getNacosInstanceFromRegistration(NacosRegistration registration) {
Instance instance = new Instance();
// 設(shè)置實(shí)例基本信息
instance.setIp(registration.getIp());
instance.setPort(registration.getPort());
instance.setWeight(registration.getWeight() == null ? 1.0F : registration.getWeight());
instance.setClusterName(registration.getClusterName());
instance.setHealthy(registration.isHealthy());
instance.setServiceName(registration.getServiceId());
instance.setInstanceId(registration.getInstanceId());
instance.setEphemeral(registration.isEphemeral());
// 設(shè)置元數(shù)據(jù)
if (registration.getMetadata() != null) {
instance.setMetadata(registration.getMetadata());
}
return instance;
}
}
// NacosNamingService是Nacos命名服務(wù)的核心實(shí)現(xiàn)類(lèi)
public class NacosNamingService implements NamingService {
private final NacosServiceFactory serviceFactory;
private final NamingProxy namingProxy;
private final ClientWorker clientWorker;
public NacosNamingService(Properties properties) throws NacosException {
// 初始化相關(guān)組件
this.serviceFactory = new NacosServiceFactory(properties);
this.namingProxy = serviceFactory.createNamingProxy();
this.clientWorker = new ClientWorker(namingProxy, properties);
}
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
// 檢查服務(wù)名是否合法
if (StringUtils.isEmpty(groupName)) {
groupName = Constants.DEFAULT_GROUP;
}
// 調(diào)用客戶端工作類(lèi)處理注冊(cè)
clientWorker.registerInstance(serviceName, groupName, instance);
}
// 客戶端工作類(lèi)處理注冊(cè)邏輯
public class ClientWorker {
private final NamingProxy namingProxy;
private final ServiceInfoHolder serviceInfoHolder;
private final ScheduledExecutorService executorService;
public ClientWorker(NamingProxy namingProxy, Properties properties) {
this.namingProxy = namingProxy;
this.serviceInfoHolder = new ServiceInfoHolder();
this.executorService = Executors.newScheduledThreadPool(1,
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("com.alibaba.nacos.naming.client.Worker");
t.setDaemon(true);
return t;
}
});
// 啟動(dòng)心跳任務(wù)
scheduleHeartbeat();
}
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
// 構(gòu)建注冊(cè)請(qǐng)求參數(shù)
Map<String, String> params = new HashMap<>(16);
params.put("serviceName", serviceName);
params.put("groupName", groupName);
params.put("ip", instance.getIp());
params.put("port", String.valueOf(instance.getPort()));
params.put("weight", String.valueOf(instance.getWeight()));
params.put("clusterName", instance.getClusterName());
params.put("ephemeral", String.valueOf(instance.isEphemeral()));
params.put("serviceId", instance.getServiceId());
params.put("metadata", JSON.toJSONString(instance.getMetadata()));
// 發(fā)送注冊(cè)請(qǐng)求到Nacos Server
namingProxy.registerInstance(params);
// 加入到服務(wù)信息持有者中
serviceInfoHolder.processServiceJson(
serviceName, groupName,
JSON.toJSONString(Collections.singletonList(instance))
);
}
// 啟動(dòng)心跳任務(wù),定期發(fā)送心跳維持注冊(cè)狀態(tài)
private void scheduleHeartbeat() {
executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
// 發(fā)送心跳包
clientWorker.sendHeartbeat();
} catch (Exception e) {
log.error("Exception when sending heartbeat", e);
}
}
}, 5000, 5000, TimeUnit.MILLISECONDS);
}
// 發(fā)送心跳方法
public void sendHeartbeat() throws NacosException {
for (Map.Entry<String, List<Instance>> entry : serviceInfoHolder.getServices().entrySet()) {
String serviceName = entry.getKey();
List<Instance> instances = entry.getValue();
for (Instance instance : instances) {
if (!instance.isHealthy() || !instance.isEphemeral()) {
continue;
}
// 構(gòu)建心跳請(qǐng)求參數(shù)
Map<String, String> params = new HashMap<>(16);
params.put("serviceName", serviceName);
params.put("ip", instance.getIp());
params.put("port", String.valueOf(instance.getPort()));
params.put("clusterName", instance.getClusterName());
params.put("serviceId", instance.getServiceId());
// 發(fā)送心跳請(qǐng)求
namingProxy.sendHeartbeat(params);
}
}
}
}
}服務(wù)發(fā)現(xiàn)原理
服務(wù)發(fā)現(xiàn)是指服務(wù)消費(fèi)者從Nacos Server獲取服務(wù)列表,并根據(jù)一定的負(fù)載均衡策略選擇具體服務(wù)實(shí)例進(jìn)行調(diào)用的過(guò)程。
核心流程如下:
- 1. 服務(wù)消費(fèi)者啟動(dòng)時(shí),向Nacos Server訂閱所需服務(wù)
- 2. Nacos Server推送服務(wù)列表給消費(fèi)者
- 3. 消費(fèi)者本地緩存服務(wù)列表,并定期更新
- 4. 服務(wù)調(diào)用時(shí),根據(jù)負(fù)載均衡策略選擇具體實(shí)例
服務(wù)發(fā)現(xiàn)核心源碼
下面是Nacos服務(wù)發(fā)現(xiàn)的核心源碼示例:
// NacosServiceDiscovery實(shí)現(xiàn)了服務(wù)發(fā)現(xiàn)邏輯
@Service
public class NacosServiceDiscovery implements ServiceDiscovery<ServiceInstance> {
private final NacosServiceManager nacosServiceManager;
private final NacosDiscoveryProperties discoveryProperties;
public NacosServiceDiscovery(NacosServiceManager nacosServiceManager,
NacosDiscoveryProperties discoveryProperties) {
this.nacosServiceManager = nacosServiceManager;
this.discoveryProperties = discoveryProperties;
}
@Override
public List<ServiceInstance> getInstances(String serviceId) throws NacosException {
return getInstances(serviceId, "");
}
@Override
public List<ServiceInstance> getInstances(String serviceId, String group) throws NacosException {
if (StringUtils.isEmpty(serviceId)) {
throw new NacosException(NacosException.INVALID_PARAM, "serviceId is empty");
}
if (StringUtils.isEmpty(group)) {
group = discoveryProperties.getGroup();
}
// 調(diào)用Nacos命名服務(wù)獲取實(shí)例列表
List<Instance> instances = namingService().getInstances(serviceId, group);
// 轉(zhuǎn)換為標(biāo)準(zhǔn)ServiceInstance格式
return instances.stream()
.map(instance -> new NacosServiceInstance(instance, serviceId))
.collect(Collectors.toList());
}
@Override
public List<String> getServices() throws NacosException {
// 獲取所有服務(wù)列表
return namingService().getServicesOfServer(1000, 0).stream()
.map(serviceInfo -> serviceInfo.getName())
.collect(Collectors.toList());
}
// 獲取Nacos命名服務(wù)客戶端
private NamingService namingService() throws NacosException {
return nacosServiceManager.getNamingService(discoveryProperties.getNacosProperties());
}
}
// NacosNamingService中的服務(wù)發(fā)現(xiàn)相關(guān)方法
public class NacosNamingService implements NamingService {
// 獲取服務(wù)實(shí)例列表
@Override
public List<Instance> getInstances(String serviceName, String groupName, List<String> clusters)
throws NacosException {
if (StringUtils.isEmpty(groupName)) {
groupName = Constants.DEFAULT_GROUP;
}
// 調(diào)用客戶端工作類(lèi)獲取實(shí)例
return clientWorker.getInstances(serviceName, groupName, clusters);
}
// 客戶端工作類(lèi)處理服務(wù)發(fā)現(xiàn)邏輯
public class ClientWorker {
// 服務(wù)信息持有者,緩存服務(wù)列表
private final ServiceInfoHolder serviceInfoHolder;
public List<Instance> getInstances(String serviceName, String groupName, List<String> clusters)
throws NacosException {
// 構(gòu)建服務(wù)標(biāo)識(shí)
String serviceId = ServiceIdBuilder.buildServiceId(groupName, serviceName);
// 從服務(wù)信息持有者獲取服務(wù)信息
ServiceInfo serviceInfo = serviceInfoHolder.getServiceInfo(serviceId);
// 如果服務(wù)信息為空或已過(guò)期,主動(dòng)拉取
if (serviceInfo == null || serviceInfo.isExpired()) {
serviceInfo = refreshServiceInfo(serviceName, groupName, clusters);
}
// 返回可用實(shí)例列表
return serviceInfo == null ? Collections.emptyList() : serviceInfo.getHosts();
}
// 刷新服務(wù)信息
public ServiceInfo refreshServiceInfo(String serviceName, String groupName, List<String> clusters)
throws NacosException {
String serviceId = ServiceIdBuilder.buildServiceId(groupName, serviceName);
// 構(gòu)建請(qǐng)求參數(shù)
Map<String, String> params = new HashMap<>(16);
params.put("serviceName", serviceName);
params.put("groupName", groupName);
if (clusters != null && !clusters.isEmpty()) {
params.put("clusters", StringUtils.join(clusters, ","));
}
// 調(diào)用Nacos Server獲取服務(wù)信息
String result = namingProxy.queryList(serviceId, params);
// 處理服務(wù)信息
return serviceInfoHolder.processServiceJson(serviceId, result);
}
// 服務(wù)信息持有者類(lèi),負(fù)責(zé)緩存和管理服務(wù)信息
public class ServiceInfoHolder {
// 服務(wù)信息緩存
private final Map<String, ServiceInfo> services = new ConcurrentHashMap<>();
// 上次更新時(shí)間
private final Map<String, Long> lastRefTime = new ConcurrentHashMap<>();
public ServiceInfo processServiceJson(String serviceId, String json) {
if (StringUtils.isEmpty(json)) {
return null;
}
ServiceInfo serviceInfo = JSON.parseObject(json, ServiceInfo.class);
if (serviceInfo != null) {
// 更新服務(wù)信息
services.put(serviceId, serviceInfo);
lastRefTime.put(serviceId, System.currentTimeMillis());
// 注冊(cè)監(jiān)聽(tīng)器,當(dāng)服務(wù)信息變化時(shí)通知
if (null != listeners.get(serviceId)) {
for (EventListener listener : listeners.get(serviceId)) {
executorService.execute(new Runnable() {
@Override
public void run() {
try {
listener.onEvent(new NamingEvent(serviceId, serviceInfo));
} catch (Exception e) {
log.error("EventListener execute error.", e);
}
}
});
}
}
}
return serviceInfo;
}
// 獲取服務(wù)信息
public ServiceInfo getServiceInfo(String serviceId) {
ServiceInfo serviceInfo = services.get(serviceId);
if (serviceInfo != null) {
return serviceInfo;
}
// 如果服務(wù)信息不存在,主動(dòng)拉取
try {
return refreshServiceInfo(serviceId.split(ServiceIdBuilder.SERVICE_ID_SEPARATOR)[0],
serviceId.split(ServiceIdBuilder.SERVICE_ID_SEPARATOR)[1], null);
} catch (NacosException e) {
log.error("getServiceInfo error, serviceId: {}", serviceId, e);
}
return null;
}
// 服務(wù)信息是否過(guò)期
public boolean isExpired(String serviceId) {
Long lastRef = lastRefTime.get(serviceId);
if (lastRef == null) {
return true;
}
// 默認(rèn)15秒更新一次
return (System.currentTimeMillis() - lastRef) > 15 * 1000;
}
}
}
}服務(wù)健康檢查機(jī)制
Nacos的服務(wù)健康檢查是保證服務(wù)可用性的關(guān)鍵機(jī)制,主要包含兩種檢查方式:
1. 客戶端主動(dòng)上報(bào):服務(wù)實(shí)例定期向Nacos Server發(fā)送心跳包
2. 服務(wù)端主動(dòng)檢查:Nacos Server定期向服務(wù)實(shí)例發(fā)送健康檢查請(qǐng)求
健康檢查的核心源碼涉及到ClientWorker類(lèi)中的心跳機(jī)制和Server端的檢查邏輯,上述源碼中已包含客戶端心跳相關(guān)部分。
服務(wù)配置與同步機(jī)制
Nacos采用了長(zhǎng)輪詢和推送相結(jié)合的方式實(shí)現(xiàn)服務(wù)配置的實(shí)時(shí)同步:
1. 客戶端發(fā)起長(zhǎng)輪詢請(qǐng)求到服務(wù)端
2. 服務(wù)端有變更時(shí)立即響應(yīng),無(wú)變更則等待一段時(shí)間后響應(yīng)
3. 客戶端收到響應(yīng)后立即發(fā)起新的長(zhǎng)輪詢請(qǐng)求
4. 服務(wù)端也可以主動(dòng)推送變更到客戶端
這種機(jī)制保證了服務(wù)信息的實(shí)時(shí)性和一致性,同時(shí)減少了客戶端與服務(wù)端的通信開(kāi)銷(xiāo)。
總結(jié)
Nacos的服務(wù)注冊(cè)與發(fā)現(xiàn)機(jī)制通過(guò)簡(jiǎn)潔而高效的設(shè)計(jì),實(shí)現(xiàn)了微服務(wù)的自動(dòng)注冊(cè)、發(fā)現(xiàn)和健康管理。其核心原理包括:
- 基于客戶端的服務(wù)注冊(cè)與心跳維持
- 基于長(zhǎng)輪詢和推送的服務(wù)信息同步
- 靈活的服務(wù)發(fā)現(xiàn)與負(fù)載均衡策略
- 可靠的服務(wù)健康檢查機(jī)制
通過(guò)上述源碼可以看到,Nacos通過(guò)NamingService接口封裝了核心功能,ClientWorker處理具體的注冊(cè)、發(fā)現(xiàn)和心跳邏輯,ServiceInfoHolder負(fù)責(zé)服務(wù)信息的緩存和管理,整體架構(gòu)清晰且易于擴(kuò)展。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
jar命令修改jar包中的application.yml配置文件
本文主要介紹了jar命令修改jar包中的application.yml配置文件,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-08-08
java實(shí)現(xiàn)通過(guò)綁定郵箱找回密碼功能
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)通過(guò)綁定郵箱找回密碼功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-02-02
Mybatis-plus自動(dòng)填充不生效或自動(dòng)填充數(shù)據(jù)為null原因及解決方案
本文主要介紹了Mybatis-plus自動(dòng)填充不生效或自動(dòng)填充數(shù)據(jù)為null原因及解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05
解決Idea的選擇文件后定位瞄準(zhǔn)器"Select Opened File"的功能
使用IntelliJ IDEA時(shí),可能會(huì)發(fā)現(xiàn)"SelectOpenedFile"功能不見(jiàn)了,這個(gè)功能允許用戶快速定位到當(dāng)前打開(kāi)文件的位置,若要找回此功能,只需在IDEA的標(biāo)題欄上右鍵,然后選擇"Always Select Opened File",這樣就可以重新啟用這個(gè)便捷的功能2024-11-11
Java打亂ArrayList生成一個(gè)隨機(jī)序列列表
有時(shí)候會(huì)需要將一個(gè)ArrayList或者數(shù)組中的數(shù)字打亂,方便后續(xù)使用,比如隨機(jī)出題、答案選項(xiàng)打亂、連線題打亂、抽獎(jiǎng)號(hào)碼打亂等等,把我自己寫(xiě)的一段代碼貼出來(lái)分享給大家。2016-08-08
JavaWeb實(shí)體類(lèi)轉(zhuǎn)為json對(duì)象的實(shí)現(xiàn)方法
這篇文章主要介紹了JavaWeb實(shí)體類(lèi)轉(zhuǎn)為json對(duì)象的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
解決阿里云OSS使用URL無(wú)法訪問(wèn)圖片的兩種方法
這篇文章主要介紹了解決阿里云OSS使用URL無(wú)法訪問(wèn)圖片的兩種方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
基于resty orm的ActiveRecord操作數(shù)據(jù)指南
這篇文章主要為大家介紹了基于resty orm的ActiveRecord操作數(shù)據(jù)指南,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-03-03

