Spring Cloud 2.2.2 源碼之五十八nacos服務端處理實例心跳二
心跳處理基本流程
ClientBeatProcessor的run處理臨時實例心跳
這裏就體現出RsInfo
的用途啦,其實就是保存下實例的相關信息,IP
,端口,集羣。遍歷實例集羣的實例,找出對應的IP
和端口的實例進行狀態更新。如果發現有問題,還要用PushService
進行UDP
進行通知,UDP
端口是客戶端請求的時候刷新服務實例列表的使用傳上來的,客戶端也有個PushReceiver
就是來接受UDP
報文信息,具體可以看這篇文章。
@Override
public void run() {
Service service = this.service;
...
String ip = rsInfo.getIp();//IP
String clusterName = rsInfo.getCluster();//集羣名字
int port = rsInfo.getPort();//端口
Cluster cluster = service.getClusterMap().get(clusterName);//獲取集羣
List<Instance> instances = cluster.allIPs(true);//獲取集羣所有的臨時服務實例
//遍歷更新對應的狀態
for (Instance instance : instances) {
if (instance.getIp().equals(ip) && instance.getPort() == port) {
if (Loggers.EVT_LOG.isDebugEnabled()) {
Loggers.EVT_LOG.debug("[CLIENT-BEAT] refresh beat: {}", rsInfo.toString());
}
instance.setLastBeat(System.currentTimeMillis());//刷新心跳時間
if (!instance.isMarked()) {//沒被標記的
if (!instance.isHealthy()) {//不健康的
instance.setHealthy(true);//設置爲健康
Loggers.EVT_LOG.info("service: {} {POS} {IP-ENABLED} valid: {}:{}@{}, region: {}, msg: client beat ok",
cluster.getService().getName(), ip, port, cluster.getName(), UtilsAndCommons.LOCALHOST_SITE);
getPushService().serviceChanged(service);//UDP發送服務改變通知
}
}
}
}
}
PushService的serviceChanged服務有改變UDP客戶端
實現了監聽接口:
緩存裏沒有的話,服務有改變的時候用上下文來發送ServiceChangeEvent
事件。
UDP調度任務
其實就是遍歷需要推送的客戶端,然後封裝數據,推送。
@Override
public void onApplicationEvent(ServiceChangeEvent event) {
Service service = event.getService();
String serviceName = service.getName();
String namespaceId = service.getNamespaceId();
Future future = udpSender.schedule(new Runnable() {
@Override
public void run() {
try {
...
//獲取所有需要推送的PushClient
ConcurrentMap<String, PushClient> clients = clientMap.get(UtilsAndCommons.assembleFullServiceName(namespaceId, serviceName));
if (MapUtils.isEmpty(clients)) {
return;
}
Map<String, Object> cache = new HashMap<>(16);
long lastRefTime = System.nanoTime();
for (PushClient client : clients.values()) {
if (client.zombie()) {//超時的不刪除不處理
...
clients.remove(client.toString());
...
continue;
}
Receiver.AckEntry ackEntry;
...
String key = getPushCacheKey(serviceName, client.getIp(), client.getAgent());
byte[] compressData = null;
Map<String, Object> data = null;
//有壓縮數據
if (switchDomain.getDefaultPushCacheMillis() >= 20000 && cache.containsKey(key)) {
org.javatuples.Pair pair = (org.javatuples.Pair) cache.get(key);
compressData = (byte[]) (pair.getValue0());
data = (Map<String, Object>) pair.getValue1();
...
}
//準備UDP數據
if (compressData != null) {
ackEntry = prepareAckEntry(client, compressData, data, lastRefTime);
} else {
ackEntry = prepareAckEntry(client, prepareHostsData(client), lastRefTime);
if (ackEntry != null) {
cache.put(key, new org.javatuples.Pair<>(ackEntry.origin.getData(), ackEntry.data));
}
}
Loggers.PUSH.info("serviceName: {} changed, schedule push for: {}, agent: {}, key: {}",
client.getServiceName(), client.getAddrStr(), client.getAgent(), (ackEntry == null ? null : ackEntry.key));
//發送
udpPush(ackEntry);
}
} catch (Exception e) {
Loggers.PUSH.error("[NACOS-PUSH] failed to push serviceName: {} to client, error: {}", serviceName, e);
} finally {
//發送完刪除
futureMap.remove(UtilsAndCommons.assembleFullServiceName(namespaceId, serviceName));
}
}
}, 1000, TimeUnit.MILLISECONDS);
//放緩存,不會重複發送
futureMap.put(UtilsAndCommons.assembleFullServiceName(namespaceId, serviceName), future);
}
udpPush
判斷是否大於重試次數,因爲UDP
不可靠,可能發出去沒收到,也可能客戶端發來的沒收到,所以要嘗試,後面有開啓任務重試的。然後封裝好數據發送,再開啓一個Retransmitter
任務10
秒後看有沒有成功響應,沒有的話就重新發送。
private static Receiver.AckEntry udpPush(Receiver.AckEntry ackEntry) {
if (ackEntry == null) {
Loggers.PUSH.error("[NACOS-PUSH] ackEntry is null.");
return null;
}
//大於嘗試的次數
if (ackEntry.getRetryTimes() > MAX_RETRY_TIMES) {
Loggers.PUSH.warn("max re-push times reached, retry times {}, key: {}", ackEntry.retryTimes, ackEntry.key);
ackMap.remove(ackEntry.key);
udpSendTimeMap.remove(ackEntry.key);
failedPush += 1;
return ackEntry;
}
try {
if (!ackMap.containsKey(ackEntry.key)) {
totalPush++;
}
ackMap.put(ackEntry.key, ackEntry);
udpSendTimeMap.put(ackEntry.key, System.currentTimeMillis());
Loggers.PUSH.info("send udp packet: " + ackEntry.key);
udpSocket.send(ackEntry.origin);//發送UDP報文
ackEntry.increaseRetryTime();
//10秒沒應答就再嘗試一次
executorService.schedule(new Retransmitter(ackEntry), TimeUnit.NANOSECONDS.toMillis(ACK_TIMEOUT_NANOS),
TimeUnit.MILLISECONDS);
return ackEntry;
} catch (Exception e) {
Loggers.PUSH.error("[NACOS-PUSH] failed to push data: {} to client: {}, error: {}",
ackEntry.data, ackEntry.origin.getAddress().getHostAddress(), e);
ackMap.remove(ackEntry.key);
udpSendTimeMap.remove(ackEntry.key);
failedPush += 1;
return null;
}
}
Retransmitter
如果發現ackMap
中還有,說明沒收到客戶端響應。
public static class Retransmitter implements Runnable {
Receiver.AckEntry ackEntry;
public Retransmitter(Receiver.AckEntry ackEntry) {
this.ackEntry = ackEntry;
}
@Override
public void run() {
if (ackMap.containsKey(ackEntry.key)) {//沒接受到響應
Loggers.PUSH.info("retry to push data, key: " + ackEntry.key);
udpPush(ackEntry);
}
}
}
Receiver
如果收到客戶端響應的話會從ackMap
中刪除ackEntry.key
:
好了,心跳的接受,處理,如果有數據改變的通知和UDP
推送原理基本都講了,具體細節看源碼吧。
好了,今天就到這裏了,希望對學習理解有幫助,大神看見勿噴,僅爲自己的學習理解,能力有限,請多包涵。