https://developer.aliyun.com/article/706613
Nacos Config模块通过一个长轮询任务LongPollingRunnable
不断向 Config Server询问自己感兴趣的配置是否更新,无论本次任务执行成功还是出现异常,都是直接再次放回Executor中再次执行;因此如果当 Config Server 出现无法服务时,会导致LongPollingRunnable
打印大量的错误日志。
为了改善 Config 客户端与 Config Server 出现连接异常时的大量错误日志打印以及频繁的请求重试,我们在1.0.1版本对 Config 客户端重连机制做了两方面的优化,第一个优化是当任务LongPollingRunnable
在执行时出现异常,将对任务采取延迟执行的惩罚策略,推迟该任务的下一次被调度执行的时间,主要逻辑代码如下
class LongPollingRunnable implements Runnable {
private int taskId;
public LongPollingRunnable(int taskId) {
this.taskId = taskId;
}
@Override
public void run() {
List<CacheData> cacheDatas = new ArrayList<CacheData>();
List<String> inInitializingCacheList = new ArrayList<String>();
try {
...
// check server config
List<String> changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList);
...
inInitializingCacheList.clear();
executorService.execute(this);
} catch (Throwable e) {
// If the rotation training task is abnormal, the next execution time of the task will be punished
LOGGER.error("longPolling error : ", e);
executorService.schedule(this, taskPenaltyTime, TimeUnit.MILLISECONDS);
}
}
}
第二个优化是ServerHttpAgent
中执行 Http 请求的重试机制优化,在1.0.1版本中,加入了 Config Server 地址列表轮训次数的限制;在1.0.1版本之前,认为 Http 请求遇到ConnectException
或者SocketTimeoutException
时,应在 timeout 时间内进行重试;但是可能会导致一个问题,如果 Config Server 不可用(宕机或者无法响应客户端请求),会导致在 timeout 时间内Config客户端因为重试发送大量的请求给 Config Server,但是这些请求其实都是无法成功的;因此为了避免此情况,加入了 Config Server 轮训次数限制,如果对 Config Server 地址列表轮训次数达到maxRetry
,则默认请求失败,抛出异常,主要逻辑代码如下
@Override
public HttpResult httpPost(String path, List<String> headers, List<String> paramValues, String encoding, long readTimeoutMs) throws IOException {
final long endTime = System.currentTimeMillis() + readTimeoutMs;
boolean isSSL = false;
String currentServerAddr = serverListMgr.getCurrentServerAddr();
int maxRetry = this.maxRetry;
do {
try {
List<String> newHeaders = getSpasHeaders(paramValues);
if (headers != null) {
newHeaders.addAll(headers);
}
...
} else {
// Update the currently available server addr
serverListMgr.updateCurrentServerAddr(currentServerAddr);
return result;
}
} catch (ConnectException ce) {
LOGGER.error("[NACOS ConnectException httpPost] currentServerAddr: {}", currentServerAddr);
} catch (SocketTimeoutException stoe) {
LOGGER.error("[NACOS SocketTimeoutException httpPost] currentServerAddr: {}, err : {}", currentServerAddr, stoe.getMessage());
} catch (IOException ioe) {
LOGGER.error("[NACOS IOException httpPost] currentServerAddr: " + currentServerAddr, ioe);
throw ioe;
}
if (serverListMgr.getIterator().hasNext()) {
currentServerAddr = serverListMgr.getIterator().next();
} else {
maxRetry --;
if (maxRetry < 0) {
throw new ConnectException("[NACOS HTTP-POST] The maximum number of tolerable server reconnection errors has been reached");
}
serverListMgr.refreshCurrentServerAddr();
}
} while (System.currentTimeMillis() <= endTime);
...
}
同时,为了方便使用者,以上优化点涉及的几个参数:taskPenaltyTime
——任务惩罚执行时间、timeout
——请求超时时间、maxRetry
——最大重试次数,都可以有使用者自行设置,配置的方法很简单,代码如下
Properties properties = new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, "127.0.0.1:" + port);
// 设置长轮询请求超时时间(最小为10s)
properties.put(PropertyKeyConst.CONFIG_LONG_POLL_TIMEOUT, "20000");
// 设置任务惩罚时间
properties.put(PropertyKeyConst.CONFIG_RETRY_TIME, 3000);
// 设置最大重试次数
properties.put(PropertyKeyConst.MAX_RETRY, 5);
configService = NacosFactory.createConfigService(properties);
具体的优化效果可以参考代码。