现象:
偶现点击某功能按钮,10秒后才有响应。大多数时间不会出现此现象,但是出现后不会恢复。
服务日志:
org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool
KongClient为公司内部UGC审核提供的Java SDK,依赖为Kong-sdk。
能力提供方需要在下个月才能排期修复,所以计划先行排查修复以解决问题。
查看相关代码:
请求报错位置
无法正常获取连接。
通过 this.httpClient = HttpClients.createDefault();创建httpClient。
其中连接池默认参数
最大连接数 maxTotal 20
每个域名最大并发连接数 maxPerRoute 2
修复:
基于源码修复,拷贝KongClient代码为DocsKongClient。
1、需要手动修改连接池配置:
自定义连接池配置,超时时间、最大连接数、每路最大连接数,增加闲置连接超时释放机制,便于异常时自动恢复。
具体数值按照业务场景和能力提供方具体情况进行调整。
2、手动释放请求连接和响应。
在默认情况下,Apache HttpClient 4.x 会自动将连接返回到连接池中,你不需要手动释放连接到连接池。HttpClient 会根据连接的 keep-alive 策略来确定何时可以关闭连接或将其返回到连接池。
为了避免异常情况影响,在使用连接后手动释放。
3.增加timeout、maxConnTotal和maxConnPerRoute配置项,可在nacos中调整连接池配置。
替换KongClient为DocsKongClient进行调用。
并发测试
原线程池配置并发测试,复现异常,多个请求处于pending状态。
修改配置后并发测试
增加配置
connectionManager.setMaxTotal(100);
connectionManager.setDefaultMaxPerRoute(50);
未发现并发异常,正常完成请求
上线观察
上线持续观察相关功能日志正常。
文本同步审核调用的优化:
受限于产品方案和交互问题,决策使用同步审核,审核未通过的内容无法进行分享、分发等。如何快速审核以不影响用户体验成为一个问题。
相关条件限制:
- 审核接口限制文本长度1万字。
- 审核1万字的文本耗时2600ms
- 90%的文档内容在五千字以下,部分文本长度达数万字。
如何优化?
- 需要对超长文本进行分割,以满足接口条件限制。
- 需要测试审核接口响应时间,选取合适的文本长度分批调用,在响应时间和请求次数之间做均衡,同时满足多数短文本需求和少量长文本需求。
- 使用并行的方式调用接口,降低长文本的审核耗时时间。
- 分割长文本时每段适当冗余,避免相邻两段文本内容的丢失,如每段长度为4千字,第二段从3990开始截取。
- 分段后有一段文本审核不通过即为全文不通过
相关处理代码
ThreadPoolExecutor executor = new ThreadPoolExecutor(
10,
10,
5,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>()
);
@Override
public boolean textCheck(String fid, String title, String content) {
//拆分长文本
String[] textArray = splitText(content, 4000);
List<Future<Boolean>> futures = new ArrayList<>();
for (String text : textArray) {
//分段处理, 审核接口content最多10000字
JSONObject jsonObject = new JSONObject();
jsonObject.put("name", title);
jsonObject.put("content", text);
JSONArray jsonArray = new JSONArray();
jsonArray.add(jsonObject);
JSONObject param = new JSONObject();
param.put("content_list", jsonArray);
param.put("bizId", ugcContentCheckProperties.getBizId());
param.put("productId", ugcContentCheckProperties.getProductId());
//透传签名参数
String requestBody = JSON.toJSONString(param);
Future<Boolean> checkFuture = executor.submit(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
// 此处仅展示了post方法,还有更多携带header、queryString的方法请查阅KongClient类
try {
KongResponse response = docsKongClient.post(ugcContentCheckProperties.getTextCheckApi(), requestBody);
//对response做处理
} catch (Exception e) {
log.error("UGC审核请求异常 body={}", requestBody, e);
}
return true;
}
});
futures.add(checkFuture);
}
for (Future<Boolean> future:futures) {
try {
//只要一个文本检测违规就算违规
if (!future.get()){
return false;
}
} catch (InterruptedException e) {
log.error("InterruptedException", e);
} catch (ExecutionException e) {
log.error("ExecutionException", e);
}
}
return true;
}