易盾sdk引起项目的整体耗时问题?

news/2023/6/6 5:03:52

  大家好:

    我是烤鸭。今年年初的时候,项目接入易盾sdk之后,随着接口调用次数增多(用到易盾sdk的接口),项目整体性能变差。写篇文章做个复盘记录,其实同事已经写过了,我借鉴部分再拓展一些。

问题描述

突然收到服务服务报警,整体服务性能下降。
在这里插入图片描述

问题排查

机器cpu有所上升,QPS、GC和内存均正常。人的压力也上来了=.=
在这里插入图片描述

CAT查看time_waiting线程数持续上升。

在这里插入图片描述

执行jstack 查看线程堆栈

jstack -l pid > 1.txt 

发现大量的time_waiting线程,其中90%的线程名字都是这个 idle-connection-evictor

在这里插入图片描述

可以看出这个线程来自 hc.client5 ,再找下易盾sdk和这个类的关系吧。

我们看下 AntispamRequester这个类,是易盾请求的一个实例化基类。

可以看到这个类里用到的 ClientProfile 是初始化的HttpClientConfig,并且创建 AntispamClient 对象的时候做了单例判断,看来是不想创建太多这个对象。

public class AntispamRequester {private ClientProfile clientProfile;private ConcurrentHashMap<String, Object> clientMap = new ConcurrentHashMap<>();public AntispamRequester(String secretId, String secretKey) {AssertUtils.notBlank(secretId, "secretId can not be null or empty");AssertUtils.notBlank(secretKey, "secretKey can not be null or empty");this.clientProfile = createDefaultProfile(secretId, secretKey);}//...public static ClientProfile createDefaultProfile(String secretId, String secretKey) {ClientProfile clientProfile = ClientProfile.defaultProfile(new Credentials(secretId, secretKey));HttpClientConfig clientConfig = new HttpClientConfig();clientConfig.setMaxConnectionCountPerRoute(100);clientProfile.setHttpClientConfig(clientConfig);return clientProfile;}//...private <T extends AntispamClient> T createIfAbsent(Class<T> clazz) {String name = clazz.getName();Object client = clientMap.get(name);if (client != null) {return (T) client;}return (T) clientMap.computeIfAbsent(name, k -> {try {return clazz.getDeclaredConstructor(ClientProfile.class).newInstance(clientProfile);} catch (Exception e) {throw new RuntimeException(e);}});}
}

再看下 AntispamClient 这个类:

public abstract class AntispamClient {protected DefaultClient client;public AntispamClient(ClientProfile clientProfile) {// 初始化clientclient = new DefaultClient(clientProfile);//...}

再往下看 HttpClientFactory 的 client的初始化方法,从这得出的结论是易盾封装的hc.client5

public class HttpClientFactory {public static CloseableHttpClient create(HttpClientConfig config) {// ... 无关的先注释return HttpClients.custom().evictIdleConnections(TimeValue.of(config.maxIdleTimeMillis(), TimeUnit.MILLISECONDS)).evictExpiredConnections().setConnectionManager(connManager).setDefaultRequestConfig(requestConfig).useSystemProperties().build();}
}

SDK使用

猜测是引入了新的易盾sdk导致的,因为其他没那么改动,而且是在随着接口调用次数增多(用到易盾sdk的接口),项目整体性能变差。不过易盾的包和线程池等待有什么关系呢。

易盾给的官方demo的写法:

https://github.com/yidun/yidun-java-sdk/blob/b92c803c8c2c8f8d55db27ce3284bb1b6eb97c1f/yidun-java-sdk-demo/src/main/java/com/netease/yidun/sdk/antispam/AbstractDemo.java

package com.netease.yidun.sdk.antispam;import com.netease.yidun.sdk.core.client.ClientProfile;
import com.netease.yidun.sdk.core.endpoint.failover.FixedWindowBreakStrategy;
import com.netease.yidun.sdk.core.http.HttpClientConfig;public class AbstractDemo {protected static AntispamRequester createAntispamRequester(String secretId, String secretKey){// 实例化一个requester,入参需要传入易盾内容安全分配的secretId,secretKeyAntispamRequester antispamRequester = new AntispamRequester(secretId, secretKey);// 可选自定义请求器的参数,如果不需要自定义设置,可跳过,否则请参考如下注释内容:
//        ClientProfile clientProfile = AntispamRequester.createDefaultProfile("SecretId", "SecretKey");
//        // 设置http请求的相关配置
//        HttpClientConfig httpClientConfig = clientProfile.getHttpClientConfig();
//        httpClientConfig.socketTimeoutMillis(60000);
//
//        // 设置固定窗口的熔断配置
//        FixedWindowBreakStrategy.Config breakerConfig = clientProfile.getBreakerConfig();
//        breakerConfig.statWindowMillis(300000);
//
//        // 设置请求失败时的重试次数
//        clientProfile.setMaxRetryCount(2);
//        AntispamRequester antispamRequester = new AntispamRequester(clientProfile);return antispamRequester;}
}

项目里也是按照这个写法的,上面看源码 antispamRequester 里可以封装很多个client对象,而每个client对象相当于对http5封装,并且进行了单例判断,理论上不会出问题。

但是按照官方的demo,如果每次都 new AntispamRequester() 呢。

源码分析

回到最开始的地方,idle-connection-evictor 在哪用到的。是构建 HttpClient 的时候根据 evictExpiredConnections 或者 evictIdleConnections,判断是否开启当前线程。

public CloseableHttpClient build() {// ... if (!this.connManagerShared) {if (closeablesCopy == null) {closeablesCopy = new ArrayList<>(1);}if (evictExpiredConnections || evictIdleConnections) {if (connManagerCopy instanceof ConnPoolControl) {final IdleConnectionEvictor connectionEvictor = new IdleConnectionEvictor((ConnPoolControl<?>) connManagerCopy,maxIdleTime, maxIdleTime);closeablesCopy.add(new Closeable() {@Overridepublic void close() throws IOException {connectionEvictor.shutdown();try {connectionEvictor.awaitTermination(Timeout.ofSeconds(1));} catch (final InterruptedException interrupted) {Thread.currentThread().interrupt();}}});connectionEvictor.start();}}closeablesCopy.add(connManagerCopy);}return new InternalHttpClient(...);
}

IdleConnectionEvictor 初始化:

这个线程就是个死循环,用来关闭超过最大超时时间的线程的,可以理解为一个清扫线程。

public IdleConnectionEvictor(final ConnPoolControl<?> connectionManager, final ThreadFactory threadFactory,final TimeValue sleepTime, final TimeValue maxIdleTime) {Args.notNull(connectionManager, "Connection manager");this.threadFactory = threadFactory != null ? threadFactory : new DefaultThreadFactory("idle-connection-evictor", true);final TimeValue localSleepTime = sleepTime != null ? sleepTime : TimeValue.ofSeconds(5);this.thread = this.threadFactory.newThread(new Runnable() {@Overridepublic void run() {try {while (!Thread.currentThread().isInterrupted()) {localSleepTime.sleep();connectionManager.closeExpired();if (maxIdleTime != null) {connectionManager.closeIdle(maxIdleTime);}}} catch (final InterruptedException ex) {Thread.currentThread().interrupt();} catch (final Exception ex) {}}});
}

回到上面的问题,每new一次,就会多x个死循环线程(x取决于client个数)。

解决方案

如果使用易盾的sdk的话,只要保证 AntispamRequester 是单例的就行,如果使用spring,可以注入到ioc。

	/*** 易盾AntispamRequester对象*/@Bean("yiDunRequester")public AntispamRequester yiDunRequester(){//1.默认方式AntispamRequester antispamRequester = new AntispamRequester(yiDunUrlConfig.getSecretId(), yiDunUrlConfig.getSecretKey());return antispamRequester;}

如果使用http的sdk,无论是 http4还是http5 ,都需要考虑资源关闭。

  1. 不要把下面那两个设为true。 evictIdleConnections 和 evictExpiredConnections (这俩默认是false) 和 evictIdleConnections(设置这个值会把evictIdleConnections 变成true),设置的话会启动清扫线程。

    这时候再看易盾的 HttpClientFactory 这个类,如果不设置这俩参数 evictIdleConnections 和 evictExpiredConnections,其实也没事。但是你偷偷设置完了不通知,就有点说不过去了。

    public class HttpClientFactory {public static CloseableHttpClient create(HttpClientConfig config) {// ... 无关的先注释return HttpClients.custom().evictIdleConnections(TimeValue.of(config.maxIdleTimeMillis(), TimeUnit.MILLISECONDS)).evictExpiredConnections().setConnectionManager(connManager).setDefaultRequestConfig(requestConfig).useSystemProperties().build();}
    }
    
  2. 创建共享对象,不再持续创建HttpClient

        /*** 类实例对象,避免重复创建*/private static HttpClient httpClient = HttpClient4Utils.createHttpClient(100, 20, 10000, 2000, 2000);public static HttpClient createHttpClient(int maxTotal, int maxPerRoute, int socketTimeout, int connectTimeout,int connectionRequestTimeout) {RequestConfig defaultRequestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).setConnectionRequestTimeout(connectionRequestTimeout).build();PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();cm.setMaxTotal(maxTotal);cm.setDefaultMaxPerRoute(maxPerRoute);CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).setDefaultRequestConfig(defaultRequestConfig).build();return httpClient;}
    
  3. 通过try-with-resources的写法,自动关闭资源。或者自己写try-catch-finally。

public static JSONObject httpGet(String url) throws HttpException {String[] strings = url.split("\\?");HttpUriRequestBase request = new HttpGet(strings[0] + "?" + UriEncoder.encode(strings[1]));try (CloseableHttpClient httpClient = getHttpClient();CloseableHttpResponse response = httpClient.execute(request)) {// ...return JSONObject.parseObject(responseContent);} catch (IOException e) {throw new HttpException(String.format("请求接口失败, url: %s", url), e);}}private static CloseableHttpClient getHttpClient() {return HttpClientBuilder.create().build();}

总结

官方的SDK最好写清楚使用,如果使用官方demo的情况下,出现服务性能下降的话,属实是无法接受的。

无论使用哪种sdk(服务端的sdk还是客户端的sdk),最好看下代码。尤其是新接入的,有条件的做下性能压测。

算是个老问题,用新形式踩坑了,挺有意思的。

再看看竞品的百度AI的:

https://ai.baidu.com/ai-doc/ANTIPORN/ik3h6xdze

在这里插入图片描述

参考文章

https://blog.csdn.net/qq_41999004/article/details/109141177

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.exyb.cn/news/show-4556621.html

如若内容造成侵权/违法违规/事实不符,请联系郑州代理记账网进行投诉反馈,一经查实,立即删除!

相关文章

电脑长时间不关机 会对电脑有伤害么???

电脑长时间不关机 会对电脑有伤害么&#xff1f;&#xff1f;&#xff1f; 长期不关机对电脑的影响还是会有的&#xff0c;因为长时间睡眠状态不关机&#xff0c;内存不会断电重置&#xff0c;可能会造成内存冗余过多&#xff0c;导致运行速度变慢建议在保证电量充足的情况下&…

win10网络重置后果很严重(联想电脑)

1.win的网络重置功能。 2.重置后是这样的 &#xff0c;啥都没有&#xff0c;更别说联网了。 我不知道是不是只有联想才会这样。 本来在网络连接有好动东西&#xff0c;现在啥都没有&#xff0c;看了好多博客&#xff0c;都没有解决问题&#xff0c;好多人都重装系统了。 3.如果…

名悦集团老司机提醒汽车闲置一直不开有多伤,危害多大

当下&#xff0c;买车不再是一件难事儿&#xff0c;每家每户有一到两辆汽车已经是标配。也正是因为这样&#xff0c;汽车闲置的概率很高。买车后&#xff0c;用车养车的费用太高&#xff0c;也让很多车友放弃自驾而选择公共交通工具出行。然而他们却忽视了汽车闲置的成本&#…

方舟计算机丢失无法启动,方舟生存进化崩溃后无法进入,方舟生存进化一直错误...

视频驱动程序崩溃并被重置是ARK许多游戏玩家遇到的错误消息&#xff1a;生存进化[1]游戏。这是一款动作冒险生存游戏&#xff0c;由Studio Wildcard于2017年8月与Instinct Games&#xff0c;Efecto Studios和Virtual Basement合作开发。游戏与PlayStation 4&#xff0c;Xbox On…

centos7 DNS服务器的配置(含正向解析与反向解析,以及对重启后resolv.conf被重置的解决方案)

DNS服务器的搭建&#xff1a; 首先: yum install bind bind-utils bind-chroot -y<span> </span>(bind-utils和bind-chroot dns的常用工具&#xff0c;不下也可以&#xff0c;不影响服务) 配置文件&#xff1a; /etc/named.conf主配置文件 /etc/named/zone文件…

食住玩:快速GET电脑常识,你对自己的计算机好吗?

纵横食住玩&#xff0c;上次玩老师和大家提到有关电脑提升性能的技巧。玩老师有些上瘾&#xff0c;还想帮助大家提升在计算机方面的知识。那么玩老师这回要说些什么呢&#xff1f;他将要和大家讲述、分享些计算机硬件及其防护的知识。其实在入手新电脑之前&#xff0c;广大网友…

电脑方式,电脑的三种启动方式你知道吗?

可能你会说&#xff0c;电脑启动不就是按电源按钮启动吗?实际上&#xff0c;电脑有三种启动方式&#xff0c;而且三种启动方式会对应不同情况&#xff0c;虽然有些概念对现在的电脑略微有一点点落后&#xff0c;但是对于玩电脑的人来说&#xff0c;这些都应该懂&#xff0c;到…

DevOps利器之二(Git,Gitlab)

一、背景Git&#xff0c;Gitlab在DevOps中主要解决持续集成源码管控部分&#xff0c;本文主要从基本概念&#xff0c;实施部署两部分介绍。二、git概述https://git-scm.com/book/zh/v2 --推荐官方电子书 Git - 它是一个源代码分布式版本控制系统&#xff0c;可让开发人员在本地…