好久不見,最近工作一直被一堆事所捆綁铛楣,也沒有大塊的時間去看技術(shù)相關(guān)的東西,以至于部分伙伴的留言都沒有及時的回復明肮,先和大家分享一句話突然想起來的一句吧菱农,不變的可能是這個世界,不斷變化的可能是我們自己柿估。
微服務架構(gòu)在近幾年變的越來越流行循未,這可能會成為我們的一個必備技能(本人的建議是如果現(xiàn)有的系統(tǒng)沒有遇到什么運維、業(yè)務瓶頸的話不要為了單純的使用微服務技術(shù)而去進行改造)秫舌。
在微服務的早期隨著Spring Cloud的支撐的妖,好多都使用了Spring默認的Netflix全家桶,但是現(xiàn)在Netflix也不再進行維護且社區(qū)活躍度也比較低足陨,所以在注冊中心部分很多團隊都會在Nacos嫂粟、Zookeeper、Kubernetes中進行選擇墨缘,本文不對其中的優(yōu)缺點進行比較和分析赋元,直接來干貨Spring Cloud Alibaba Nacos 服務如何注冊。
一飒房、服務注冊初始化階段
其實對于服務的注冊基本上所有開源項目的實現(xiàn)思路基本一致搁凸,無非就是服務的注冊、心跳維持狠毯,看下圖直接找到服務注冊的源碼护糖,一般這種代碼我都是從AutoConfig開始看了,大家可以先大體看一下嚼松,這幾個AutoConfig功能其實就是初始化紅框內(nèi)的其他的的非AutoConfig類嫡良。
項目啟動以后首先會通過NacosDiscoveryAutoConfiguration根據(jù)配置文件初始化NacosDiscoveryProperties,NacosDiscoveryProperties為配置文件中配置的服務注冊相關(guān)參數(shù)(如serverAddr献酗、group等)寝受,然后緊接著初始化NacosServiceDiscovery,該類主要是通過的NacosDiscoveryProperties的namingServiceInstance屬性實現(xiàn)獲取注冊中心的相關(guān)信息罕偎。最后初始化NacosDiscoveryClient對NacosServiceDiscovery進行封裝很澄。
在服務注冊方面,會通過NacosServiceRegistryAutoConfiguration依次初始化NacosServiceRegistry颜及、NacosRegistration甩苛、NacosAutoServiceRegistration,在初始化NacosServiceRegistry時會對之前的NacosDiscoveryProperties的namingServiceInstance屬性進行實例化俏站,其中服務的注冊的核心代碼(com.alibaba.cloud.nacos.registry.NacosServiceRegistry#register)也在該類中讯蒲。在初始化AbstractAutoServiceRegistration時候,大家會發(fā)現(xiàn)它的父類實現(xiàn)了ApplicationListener肄扎,當發(fā)布事件時候會執(zhí)行onApplicationEvent中的代碼(可以參考我伙伴的文章)墨林,這也是服務注冊的入口赁酝。
二、服務注冊階段
在AbstractAutoServiceRegistration的onApplicationEvent方法中執(zhí)行了start方法旭等,start方法中會執(zhí)行register方法赞哗,最終調(diào)用的方法就是之前說到的com.alibaba.cloud.nacos.registry.NacosServiceRegistry#register,完成對服務進行注冊辆雾。
public void register(Registration registration) {
if (StringUtils.isEmpty(registration.getServiceId())) {
log.warn("No service to register for nacos client...");
} else {
String serviceId = registration.getServiceId();
String group = this.nacosDiscoveryProperties.getGroup();
Instance instance = this.getNacosInstanceFromRegistration(registration);
try {
this.namingService.registerInstance(serviceId, group, instance);
log.info("nacos registry, {} {} {}:{} register finished", new Object[]{group, serviceId, instance.getIp(), instance.getPort()});
} catch (Exception var6) {
log.error("nacos registry, {} register failed...{},", new Object[]{serviceId, registration.toString(), var6});
ReflectionUtils.rethrowRuntimeException(var6);
}
}
}
在服務注冊的代碼中肪笋,最核心的是this.namingService.registerInstance(serviceId, group, instance);這一行,最終實現(xiàn)注冊的方法是com.alibaba.nacos.client.naming.net.NamingProxy#registerService度迂,在這個方法中通過調(diào)用nacas的api包中的reqAPI來與注冊中心的服務端進行交互從而完成注冊藤乙。
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
LogUtils.NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}", new Object[]{this.namespaceId, serviceName, instance});
Map<String, String> params = new HashMap(9);
params.put("namespaceId", this.namespaceId);
params.put("serviceName", serviceName);
params.put("groupName", groupName);
params.put("clusterName", 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()));
this.reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, "POST");
}
public String callServer(String api, Map<String, String> params, String body, String curServer, String method) throws NacosException {
long start = System.currentTimeMillis();
long end = 0L;
this.injectSecurityInfo(params);
List<String> headers = this.builderHeaders();
String url;
if (!curServer.startsWith("https://") && !curServer.startsWith("http://")) {
if (!curServer.contains(":")) {
curServer = curServer + ":" + this.serverPort;
}
url = HttpClient.getPrefix() + curServer + api;
} else {
url = curServer + api;
}
HttpResult result = HttpClient.request(url, headers, params, body, "UTF-8", method);
end = System.currentTimeMillis();
MetricsMonitor.getNamingRequestMonitor(method, url, String.valueOf(result.code)).observe((double)(end - start));
if (200 == result.code) {
return result.content;
} else if (304 == result.code) {
return "";
} else {
throw new NacosException(result.code, result.content);
}
}
在reqAPI的最后部分的代碼com.alibaba.nacos.client.naming.net.NamingProxy#callServer(java.lang.String, java.util.Map<java.lang.String,java.lang.String>, java.lang.String, java.lang.String, java.lang.String)中,與服務端進行交互的過程中惭墓,會將自身的信息發(fā)送給服務端坛梁,服務端響應OK后完成注冊,請求服務端的信息如下腊凶。
地址:http://192.168.***.***:8848/nacos/v1/ns/instance
內(nèi)容:{app=nacos-provider, metadata={"preserved.register.source":"SPRING_CLOUD"}, ip=192.168.***.***, weight=1.0, ephemeral=true, serviceName=DEFAULT_GROUP@@nacos-provider, encoding=UTF-8, groupName=DEFAULT_GROUP, namespaceId=public, port=8081, enable=true, healthy=true, clusterName=DEFAULT}
三划咐、心跳的維持
服務注冊到注冊中心服務端后,服務端與客戶端會一直保持一個心跳钧萍,來對服務的可用性進行維護褐缠,在之前的部分我們有說過NacosServiceRegistry類中會對namingService進行實例化,同樣也是利用nacos的api包通過反射來進行初始化风瘦。
public static NamingService createNamingService(Properties properties) throws NacosException {
try {
Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.naming.NacosNamingService");
Constructor constructor = driverImplClass.getConstructor(Properties.class);
NamingService vendorImpl = (NamingService)constructor.newInstance(properties);
return vendorImpl;
} catch (Throwable var4) {
throw new NacosException(-400, var4);
}
}
在對NacosNamingService進行初始化的過程中队魏,我們會在構(gòu)造函數(shù)中發(fā)現(xiàn)他還會初始化com.alibaba.nacos.client.naming.NacosNamingService#beatReactor,而BeatReactor中存在一個線程池万搔,我們在服務注冊之前(com.alibaba.nacos.client.naming.NacosNamingService#registerInstance(java.lang.String, java.lang.String, com.alibaba.nacos.api.naming.pojo.Instance)方法)就會向這個線程池中發(fā)布Task胡桨。
public BeatReactor(NamingProxy serverProxy, int threadCount) {
this.lightBeatEnabled = false;
this.dom2Beat = new ConcurrentHashMap();
this.serverProxy = serverProxy;
this.executorService = new ScheduledThreadPoolExecutor(threadCount, new ThreadFactory() {
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setDaemon(true);
thread.setName("com.alibaba.nacos.naming.beat.sender");
return thread;
}
});
}
public void addBeatInfo(String serviceName, BeatInfo beatInfo) {
LogUtils.NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);
String key = this.buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort());
BeatInfo existBeat = null;
if ((existBeat = (BeatInfo)this.dom2Beat.remove(key)) != null) {
existBeat.setStopped(true);
}
this.dom2Beat.put(key, beatInfo);
this.executorService.schedule(new BeatReactor.BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);
MetricsMonitor.getDom2BeatSizeMonitor().set((double)this.dom2Beat.size());
}
下面我們在看一下這個定時任務中的內(nèi)容,其實與服務的注冊原理相似瞬雹,都是與服務端進行一個交互昧谊,如果服務端響應該服務不可用,則重新進行注冊酗捌,如果依舊可用(或已經(jīng)重新注冊)則繼續(xù)將新的Task(BeatTask)放入到線程池中呢诬,從而不斷的與服務端維持心跳。
地址:http://192.168.***.***:8848/nacos/v1/ns/instance/beat
內(nèi)容:{app=nacos-provider, namespaceId=public, port=8081, clusterName=DEFAULT, ip=192.168.***.***, serviceName=DEFAULT_GROUP@@nacos-provider, encoding=UTF-8}
class BeatTask implements Runnable {
BeatInfo beatInfo;
public BeatTask(BeatInfo beatInfo) {
this.beatInfo = beatInfo;
}
public void run() {
if (!this.beatInfo.isStopped()) {
long nextTime = this.beatInfo.getPeriod();
try {
JSONObject result = BeatReactor.this.serverProxy.sendBeat(this.beatInfo, BeatReactor.this.lightBeatEnabled);
long interval = (long)result.getIntValue("clientBeatInterval");
boolean lightBeatEnabled = false;
if (result.containsKey("lightBeatEnabled")) {
lightBeatEnabled = result.getBooleanValue("lightBeatEnabled");
}
BeatReactor.this.lightBeatEnabled = lightBeatEnabled;
if (interval > 0L) {
nextTime = interval;
}
int code = 10200;
if (result.containsKey("code")) {
code = result.getIntValue("code");
}
if (code == 20404) {
Instance instance = new Instance();
instance.setPort(this.beatInfo.getPort());
instance.setIp(this.beatInfo.getIp());
instance.setWeight(this.beatInfo.getWeight());
instance.setMetadata(this.beatInfo.getMetadata());
instance.setClusterName(this.beatInfo.getCluster());
instance.setServiceName(this.beatInfo.getServiceName());
instance.setInstanceId(instance.getInstanceId());
instance.setEphemeral(true);
try {
BeatReactor.this.serverProxy.registerService(this.beatInfo.getServiceName(), NamingUtils.getGroupName(this.beatInfo.getServiceName()), instance);
} catch (Exception var10) {
}
}
} catch (NacosException var11) {
LogUtils.NAMING_LOGGER.error("[CLIENT-BEAT] failed to send beat: {}, code: {}, msg: {}", new Object[]{JSON.toJSONString(this.beatInfo), var11.getErrCode(), var11.getErrMsg()});
}
BeatReactor.this.executorService.schedule(BeatReactor.this.new BeatTask(this.beatInfo), nextTime, TimeUnit.MILLISECONDS);
}
}
}
小結(jié)
上文介紹了Spring Cloud Alibaba Nacos 服務注冊的實現(xiàn)原理與源碼解析意敛,相信大家應該大體了解了服務是如何注冊到Nacos中馅巷,但是可能大家接下來還有疑惑,我們在服務調(diào)用的時候草姻,是從哪里獲取的所要調(diào)用的服務可用列表,以及這個列表是如何與服務端共同維護的稍刀,請聽下回分解撩独。