NotificationControllerV2实现了ReleaseMessageListener,具体什么时候注册的监听器请参考:ConfigServiceAutoConfiguration
/**
* 监听器注册,namespaces变更后接收消息
* @return
*/
@Bean
public ReleaseMessageScanner releaseMessageScanner() {
ReleaseMessageScanner releaseMessageScanner = new ReleaseMessageScanner();
//0. handle release message cache
releaseMessageScanner.addMessageListener(releaseMessageServiceWithCache);
//1. handle gray release rule
releaseMessageScanner.addMessageListener(grayReleaseRulesHolder);
//2. handle server cache
releaseMessageScanner.addMessageListener(configService);
releaseMessageScanner.addMessageListener(configFileController);
//3. notify clients
releaseMessageScanner.addMessageListener(notificationControllerV2);
releaseMessageScanner.addMessageListener(notificationController);
return releaseMessageScanner;
}
进入主流程2.1客户端请求,ConfigSerivce通知配置变更,这里是利用Spring DeferredResult实现长轮循
/**
* 客户端请求,configservice通知配置变更
* 利用Spring DeferredResult(长轮训)
* @param appId
* @param cluster
* @param notificationsAsString
* @param dataCenter
* @param clientIp
* @return 实际变更的namespaces对应的ApolloConfigNotification
*/
@GetMapping
public DeferredResult>> pollNotification(
@RequestParam(value = "appId") String appId,
@RequestParam(value = "cluster") String cluster,
@RequestParam(value = "notifications") String notificationsAsString,
@RequestParam(value = "dataCenter", required = false) String dataCenter,
@RequestParam(value = "ip", required = false) String clientIp) {
List notifications = null;
try {
//因为一个客户端可以订阅过个namespace所以是list
notifications =
gson.fromJson(notificationsAsString, notificationsTypeReference);
} catch (Throwable ex) {
Tracer.logError(ex);
}
if (CollectionUtils.isEmpty(notifications)) {
throw new BadRequestException("Invalid format of notifications: " + notificationsAsString);
}
DeferredResultWrapper deferredResultWrapper = new DeferredResultWrapper(bizConfig.longPollingTimeoutInMilli());
//namespaces集合
Set namespaces = Sets.newHashSet();
//客户端通知的map,key:namespaces名;value:通知编号
Map clientSideNotifications = Maps.newHashMap();
//key:namespaces名
//过滤ApolloConfigNotification
Map filteredNotifications = filterNotifications(appId, notifications);
for (Map.Entry notificationEntry : filteredNotifications.entrySet()) {
String normalizedNamespace = notificationEntry.getKey();
ApolloConfigNotification notification = notificationEntry.getValue();
//添加到namespaces里
namespaces.add(normalizedNamespace);
//添加通知客户端map
clientSideNotifications.put(normalizedNamespace, notification.getNotificationId());
// 记录名字被归一化的 Namespace 。因为,最终返回给客户端,使用原始的 Namespace 名字,否则客户端无法识别。
if (!Objects.equals(notification.getNamespaceName(), normalizedNamespace)) {
deferredResultWrapper.recordNamespaceNameNormalizedResult(notification.getNamespaceName(), normalizedNamespace);
}
}
if (CollectionUtils.isEmpty(namespaces)) {
throw new BadRequestException("Invalid format of notifications: " + notificationsAsString);
}
//组装Watch key集合
Multimap watchedKeysMap =
watchKeysUtil.assembleAllWatchKeys(appId, cluster, namespaces, dataCenter);
//生成watch key 集合
Set watchedKeys = Sets.newHashSet(watchedKeysMap.values());
/**
* 1、set deferredResult before the check, for avoid more waiting
* If the check before setting deferredResult,it may receive a notification the next time
* when method handleMessage is executed between check and set deferredResult.
*/
//注册超时事件
deferredResultWrapper
.onTimeout(() -> logWatchedKeys(watchedKeys, "Apollo.LongPoll.TimeOutKeys"));
//注册结束事件
deferredResultWrapper.onCompletion(() -> {
//unregister all keys
//移除watch key+deferredResultWrapper出deferredResults
for (String key : watchedKeys) {
deferredResults.remove(key, deferredResultWrapper);
}
logWatchedKeys(watchedKeys, "Apollo.LongPoll.CompletedKeys");
});
//register all keys
//注册watch key+deferredResultWrapper到deferredResult,等待配置发生变化时通知
for (String key : watchedKeys) {
this.deferredResults.put(key, deferredResultWrapper);
}
logWatchedKeys(watchedKeys, "Apollo.LongPoll.RegisteredKeys");
logger.debug("Listening {} from appId: {}, cluster: {}, namespace: {}, datacenter: {}",
watchedKeys, appId, cluster, namespaces, dataCenter);
/**
* 2、check new release
* 获取watch key集合中每个watch key对应的releaseMessage集合
*/
List latestReleaseMessages =
releaseMessageService.findLatestReleaseMessagesGroupByMessages(watchedKeys);
/**
* Manually close the entity manager.
* 手动关闭Entity manager
* Since for async request, Spring won't do so until the request is finished,
* 对于异步请求来说,Spring在请求完毕之前不会关闭
* which is unacceptable since we are doing long polling - means the db connection would be hold
* 这个是不能被接受的,,这就意味着我们在做长轮训的时候db连接一直被保留很长时间
* for a very long time
* 实际上我们后面操作不需要db连接,所以可以关闭
*
*/
entityManagerUtil.closeEntityManager();
//获取最新的ApolloConfigNotification集合
List newNotifications =
getApolloConfigNotifications(namespaces, clientSideNotifications, watchedKeysMap,
latestReleaseMessages);
// 若有更新直接设置结果
if (!CollectionUtils.isEmpty(newNotifications)) {
deferredResultWrapper.setResult(newNotifications);
}
return deferredResultWrapper.getResult();
}
我们重点看一下getApolloConfigNotifications():获取最新的ApolloConfigNotification集合
/**
* 获取新的ApolloConfigNotification集合
* 用客户端通知编号和服务端通知编号对比过滤
* @param namespaces
* @param clientSideNotifications
* @param watchedKeysMap
* @param latestReleaseMessages
* @return
*/
private List getApolloConfigNotifications(Set namespaces,
Map clientSideNotifications,
Multimap watchedKeysMap,
List latestReleaseMessages) {
List newNotifications = Lists.newArrayList();
if (!CollectionUtils.isEmpty(latestReleaseMessages)) {
//创建最新的通知map,key:watch key(message)
Map latestNotifications = Maps.newHashMap();
for (ReleaseMessage releaseMessage : latestReleaseMessages) {
latestNotifications.put(releaseMessage.getMessage(), releaseMessage.getId());
}
//循环namespaces判断是否有更新配置
for (String namespace : namespaces) {
long clientSideId = clientSideNotifications.get(namespace);
long latestId = ConfigConsts.NOTIFICATION_ID_PLACEHOLDER;
Collection namespaceWatchedKeys = watchedKeysMap.get(namespace);
//获取最大的通知编号
for (String namespaceWatchedKey : namespaceWatchedKeys) {
long namespaceNotificationId =
latestNotifications.getOrDefault(namespaceWatchedKey, ConfigConsts.NOTIFICATION_ID_PLACEHOLDER);
if (namespaceNotificationId > latestId) {
latestId = namespaceNotificationId;
}
}
//如果服务器的通知编号>客户端的通知编号,说明服务器配置有更新
if (latestId > clientSideId) {
ApolloConfigNotification notification = new ApolloConfigNotification(namespace, latestId);
//循环添加通知编号到ApolloConfigNotification中
namespaceWatchedKeys.stream().filter(latestNotifications::containsKey).forEach(namespaceWatchedKey ->
notification.addMessage(namespaceWatchedKey, latestNotifications.get(namespaceWatchedKey)));
newNotifications.add(notification);
}
}
}
return newNotifications;
}
代码解释:循环判断是否有配置更新,首先获取最大通知编号,然后跟客户端传过来的做对比,如果最新的>客户端编号,说明配置有更新,然后将通知编号设置到ApolloConfigNotification中并返回
我们再来看DeferredResultWrapper
public class DeferredResultWrapper {
private static final ResponseEntity>
NOT_MODIFIED_RESPONSE_LIST = new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
/**
* 归一化和原始的Namespace的名字map
*/
private Map normalizedNamespaceNameToOriginalNamespaceName;
/**
* 响应DeferredResult对象
*/
private DeferredResult>> result;
public DeferredResultWrapper(long timeoutInMilli) {
result = new DeferredResult<>(timeoutInMilli, NOT_MODIFIED_RESPONSE_LIST);
}
public void recordNamespaceNameNormalizedResult(String originalNamespaceName, String normalizedNamespaceName) {
if (normalizedNamespaceNameToOriginalNamespaceName == null) {
normalizedNamespaceNameToOriginalNamespaceName = Maps.newHashMap();
}
normalizedNamespaceNameToOriginalNamespaceName.put(normalizedNamespaceName, originalNamespaceName);
}
public void onTimeout(Runnable timeoutCallback) {
result.onTimeout(timeoutCallback);
}
public void onCompletion(Runnable completionCallback) {
result.onCompletion(completionCallback);
}
public void setResult(ApolloConfigNotification notification) {
setResult(Lists.newArrayList(notification));
}
/**
* The namespace name is used as a key in client side, so we have to return the original one instead of the correct one
*/
public void setResult(List notifications) {
if (normalizedNamespaceNameToOriginalNamespaceName != null) {
notifications.stream().filter(notification -> normalizedNamespaceNameToOriginalNamespaceName.containsKey
(notification.getNamespaceName())).forEach(notification -> notification.setNamespaceName(
normalizedNamespaceNameToOriginalNamespaceName.get(notification.getNamespaceName())));
}
result.setResult(new ResponseEntity<>(notifications, HttpStatus.OK));
}
public DeferredResult>> getResult() {
return result;
}
}
解释:我们看到其实就是对 spring 的DeferredResult做了一层包装
总结:
1、容器启动时ConfigServiceAutoConfiguration配置类注册监听器,等namespace发生变化后进行通知
2、客户端发起长轮循基于Spring 的DeferredResult实现
3、服务端对请求的ApolloCOnfigNotification进行各种过滤,然后生成watch key(appId, cluster, namespace三个连接)
4、给deferredResult注册超时事件、结束事件(移除对应的Watch key)
5、注册watch key+deferredResultWrapper到defrredResults,等待配置发生变更时通知
6、获取watch key集合中每个watch key对应的releaseMessage集合,获取有变更的
7、关闭DB连接,如果6有变更直接setResult结束长轮循
公众号主要记录各种源码、面试题、微服务技术栈,帮忙关注一波,非常感谢