NotificationControllerV2:notifications/v2:实现长轮循,监听配置变更、返回,结束长轮循

4、NotificationControllerV2:notifications/v2:实现长轮循,监听配置变更、返回,结束长轮循

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结束长轮循

 

 

公众号主要记录各种源码、面试题、微服务技术栈,帮忙关注一波,非常感谢

你可能感兴趣的:(Apollo,Apollo)