}
Transaction transaction = Tracer.newTransaction(“Apollo.ConfigService”, “queryConfig”);
transaction.addData(“Url”, url);
try {
HttpResponse response = m_httpUtil.doGet(request, ApolloConfig.class);
m_configNeedForceRefresh.set(false);
m_loadConfigFailSchedulePolicy.success();
transaction.addData(“StatusCode”, response.getStatusCode());
transaction.setStatus(Transaction.SUCCESS);
if (response.getStatusCode() == 304) {
logger.debug(“Config server responds with 304 HTTP status code.”);
return m_configCache.get();
}
ApolloConfig result = response.getBody();
logger.debug(“Loaded config for {}: {}”, m_namespace, result);
return result;
} catch (ApolloConfigStatusCodeException ex) {
ApolloConfigStatusCodeException statusCodeException = ex;
//config not found
if (ex.getStatusCode() == 404) {
String message = String.format(
"Could not find config for namespace - appId: %s, cluster: %s, namespace: %s, " +
“please check whether the configs are released in Apollo!”,
appId, cluster, m_namespace);
statusCodeException = new ApolloConfigStatusCodeException(ex.getStatusCode(),
message);
}
Tracer.logEvent(“ApolloConfigException”, ExceptionUtil.getDetailMessage(statusCodeException));
transaction.setStatus(statusCodeException);
exception = statusCodeException;
if(ex.getStatusCode() == 404) {
break retryLoopLabel;
}
} catch (Throwable ex) {
Tracer.logEvent(“ApolloConfigException”, ExceptionUtil.getDetailMessage(ex));
transaction.setStatus(ex);
exception = ex;
} finally {
transaction.complete();
}
// if force refresh, do normal sleep, if normal config load, do exponential sleep
onErrorSleepTime = m_configNeedForceRefresh.get() ? m_configUtil.getOnErrorRetryInterval() :
m_loadConfigFailSchedulePolicy.fail();
}
}
String message = String.format(
“Load Apollo Config failed - appId: %s, cluster: %s, namespace: %s, url: %s”,
appId, cluster, m_namespace, url);
throw new ApolloConfigException(message, exception);
}
配置实时生效的单靠短连接肯定是不能完成的,需要和长连接配合完成。下面将介绍长连接实现原理及长连接和短连接如何配合完成配置实时生效。
1. 长连接实现原理
长连接,顾名思义就是客户端与服务端建立连接后不断开,一个客户端就是一个长连接,,而不是一个Namespace一个长连接,如果想要实现动态关闭某个appId长连接,是可以通过修改下面代码实现,同时通过修改短连接请求,添加开与关标志位实现。Apollo配置中心长连接实现逻辑是具体是什么呢?
• 1.3.1. 客户端主要逻辑
private void doLongPollingRefresh(String appId, String cluster, String ataCenter, String secret) {
final Random random = new Random();
ServiceDTO lastServiceDto = null;
//循环发生请求,实现客户端长连接
while (!m_longPollingStopped.get() && !Thread.currentThread().isInterrupted()) {
if (!m_longPollRateLimiter.tryAcquire(5, TimeUnit.SECONDS)) {
//wait at most 5 seconds
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
}
}
Transaction transaction = Tracer.newTransaction(“Apollo.ConfigService”, “pollNotification”);
String url = null;
try {
if (lastServiceDto == null) {
List configServices = getConfigServices();
//随机获取一个可用的节点
lastServiceDto =configServices.get(random.nextInt(configServices.size()));
}
//拼接请求URL
url = assembleLongPollRefreshUrl(lastServiceDto.getHomepageUrl(), appId, cluster, dataCenter, m_notifications);
logger.debug(“Long polling from {}”, url);
HttpRequest request = new HttpRequest(url);
request.setReadTimeout(LONG_POLLING_READ_TIMEOUT);
if (!StringUtils.isBlank(secret)) {
Map<String, String> headers = Signature.buildHttpHeaders(url, appId, secret);
request.setHeaders(headers);
}
transaction.addData(“Url”, url);
//请求发送
final HttpResponse<List> response =
m_httpUtil.doGet(request, m_responseType);
logger.debug(“Long polling response: {}, url: {}”, response.getStatusCode(), url);
if (response.getStatusCode() == 200 && response.getBody() != null) {
updateNotifications(response.getBody());
updateRemoteNotifications(response.getBody());
transaction.addData(“Result”, response.getBody().toString());
notify(lastServiceDto, response.getBody());
}
//try to load balance
//304: NOT_MODIFIED(304, “Not Modified”),
//random.nextBoolean:伪均匀分布的布尔值,保证长连接均匀分布在各节点上
if (response.getStatusCode() == 304 && random.nextBoolean()) {
lastServiceDto = null;
}
m_longPollFailSchedulePolicyInSecond.success();
transaction.addData(“StatusCode”, response.getStatusCode());
transaction.setStatus(Transaction.SUCCESS);
} catch (Throwable ex) {
lastServiceDto = null;
Tracer.logEvent(“ApolloConfigException”, ExceptionUtil.getDetailMessage(ex));
transaction.setStatus(ex);
long sleepTimeInSecond = m_longPollFailSchedulePolicyInSecond.fail();
logger.warn(
“Long polling failed, will retry in {} seconds. appId: {}, cluster: {}, namespaces: {}, long polling url: {}, reason: {}”,
sleepTimeInSecond, appId, cluster, assembleNamespaces(), url, ExceptionUtil.getDetailMessage(ex));
try {
TimeUnit.SECONDS.sleep(sleepTimeInSecond);
} catch (InterruptedException ie) {
//ignore
}
} finally {
transaction.complete();
}
}
}
从上面代码可以看出,长连接实现逻辑是一个无限循环发请求的过程。返回200更新本地配置,返回304说明配置没有改变。
• 1.3.2. 服务端主要逻辑
Namespace在点击发布按钮时会项ReleaseMessage表里插入一条信息,AppId+集群+Namespace(apollo+default+application),代码如下
DeferredResultWrapper deferredResultWrapper = new DeferredResultWrapper();
Set namespaces = Sets.newHashSet();
Map<String, Long> clientSideNotifications = Maps.newHashMap();
Map<String, ApolloConfigNotification> filteredNotifications = filterNotifications(appId, notifications);
for (Map.Entry<String, ApolloConfigNotification> notificationEntry : filteredNotifications.entrySet()) {
String normalizedNamespace = notificationEntry.getKey();
ApolloConfigNotification notification = notificationEntry.getValue();
namespaces.add(normalizedNamespace);
clientSideNotifications.put(normalizedNamespace, notification.getNotificationId());
if (!Objects.equals(notification.getNamespaceName(), normalizedNamespace)) {
deferredResultWrapper.recordNamespaceNameNormalizedResult(notification.getNamespaceName(), normalizedNamespace);
}
}
if (CollectionUtils.isEmpty(namespaces)) {
throw new BadRequestException("Invalid format of notifications: " + notificationsAsString);
}
Multimap<String, String> watchedKeysMap =
watchKeysUtil.assembleAllWatchKeys(appId, cluster, namespaces, dataCenter);
Set watchedKeys = Sets.newHashSet(watchedKeysMap.values());
//获取ReleaseMessage的最新数据,然后通知客户端配置信息有修改
List latestReleaseMessages =
releaseMessageService.findLatestReleaseMessagesGroupByMessages(watchedKeys);
/**
* Manually close the entity manager.
* Since for async request, Spring won’t do so until the request is finished,
* which is unacceptable since we are doing long polling - means the db connection would be hold
* for a very long time
*/
entityManagerUtil.closeEntityManager();
List newNotifications = getApolloConfigNotifications(namespaces, clientSideNotifications, watchedKeysMap,latestReleaseMessages);
服务端保持长连接的核心就不得不提DeferredResultWrapper这个类,这是对DeferredResult-异步请求处理的一个包装类,使用DeferredResult的流程:
1.客户端发起异步请求
2. 请求到达ConfigService被挂起
3. 向客户端进行响应,分为两种情况:
 3.1 ReleaseMessage有新数据插入,说明配置发生改变,调用DeferredResult.setResult(),请求被唤醒,返回结果,response.getStatusCode()等于 200,此时客户端将发送一个短连接去获取最新配置信息,
 3.2 超时,超时时间为60秒,返回一个你设定的结果,response.getStatusCode()等于 3044. 客户端得到响应,while循环,在进行下一次的请求发送。