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

news2024/11/13 15:16:49

  大家好:

    我是烤鸭。今年年初的时候,项目接入易盾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) {
    	// 初始化client
        client = 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,secretKey
        AntispamRequester 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() {

                    @Override
                    public 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() {
        @Override
        public 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.coloradmin.cn/o/172251.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【JavaEE初阶】第五节.多线程 ( 基础篇 ) 线程安全问题(上篇)

目录 文章目录 前言 一、线程安全的概述 1.1 什么是线程安全问题 1.2 存在线程安全问题的实例 二、线程安全问题及其解决办法 2.1 案例分析 2.2 造成线程不安全的原因 2.3 线程加锁操作解决原子性 问题 &#xff1b; 2.3.1 什么是加锁 2.3.2 使用 synchronized关键字…

爆品跟卖商家必读:2023年快速入局TikTok选品5大关键

TikTok商业进程一直有在发展&#xff0c;开启东南亚小店&#xff0c;美国小店内邀……有吸引了不少外贸工厂和传统跨境电商卖家等玩家入局。2022年这一年&#xff0c;不管是直播带货&#xff0c;短视频带货&#xff0c;还是广告投流&#xff0c;数据都有新的变化。据报道&#…

Word 允许西文在单词中间换行,没用/无效 终极办法

有时在写论文中&#xff0c;英文的调整相当麻烦&#xff0c;为了节约版面&#xff0c;会设置允许西文在单词中间换行。但有时不希望这样&#xff0c;特别是在复制网上英文时&#xff0c;会出现单词分断换行情况&#xff0c;如何解决&#xff1a; 1.一般办法。 在Word选择要调整…

C规范编辑笔记(十)

往期文章&#xff1a; C规范编辑笔记(一) C规范编辑笔记(二) C规范编辑笔记(三) C规范编辑笔记(四) C规范编辑笔记(五) C规范编辑笔记(六) C规范编辑笔记(七) C规范编辑笔记(八) C规范编辑笔记(九) 正文&#xff1a; 又是新的一年&#xff0c;2023年的第一篇没想到隔了这么久…

MyBatis-Plus加密字段查询(密文检索)

MyBatis-Plus数据安全保护(加密解密)解释说明 1.字段加密后&#xff0c;数据库存储的字段内容为十六进制格式的密文2.条件查询时&#xff0c;若不对密文进行处理将无法匹配出想要的结果3.处理方式是借助SQL的AES_DECRYPT函数将密文解密后匹配4.SQL的解密函数只有AES_DECRYPT&am…

Java-流和IO

文章目录流InputStreamFileInputStream常用方法详情代码示例BufferInputStream常用方法详情代码示例OutputStreamFileOutputStream常用方法详情代码示例BufferedOutputStream常用方法详情代码示例ReadWriteJava的java.io库提供了IO接口&#xff0c;IO是以流为基础进行输入输出的…

云原生技能树-docker image 操作-练习篇

从Docker Hub 拉取已有镜像 一个Docker 镜像(image)包含了程序代码和程序运行所依赖的所有环境。 Docker 镜像一般存放在镜像仓库服务(Image Registry)里&#xff0c;默认的镜像仓库服务是Docker Hub。 用户可以制作、构建镜像、将镜像上传到镜像仓库服务&#xff0c;从而可以…

100w人在线的 弹幕 系统,是怎么架构的?

Shopee是东南亚及中国台湾地区的电商平台 。2015年于新加坡成立并设立总部&#xff0c;随后拓展至马来西亚、泰国、中国台湾地区、印度尼西亚、越南及菲律宾共七大市场。 Shopee拥有商品种类&#xff0c;包括电子消费品、家居、美容保健、母婴、服饰及健身器材等。 2022年第二…

【STM32学习】GPIO口的八种工作模式

GPIO口的八种工作模式一、参考资料二、施密特触发器1、电路2、电路计算一、参考资料 GPIO原理图详解 强烈建议观看&#xff1a;GPIO为什么这样设计&#xff1f; 施密特触发器—原理 施密特触发器—计算 什么是运放的虚短和虚断&#xff1f; 二、施密特触发器 关于GPIO的原理与…

JavaWeb-JSP

JavaWeb-JSP 1&#xff0c;JSP 概述 JSP&#xff08;全称&#xff1a;Java Server Pages&#xff09;&#xff1a;Java 服务端页面。是一种动态的网页技术&#xff0c;其中既可以定义 HTML、JS、CSS等静态内容&#xff0c;还可以定义 Java代码的动态内容&#xff0c;也就是 J…

设计模式 (二) 工厂模式 Java

目录 一、案例引出 二、简单工厂模式 二、抽象工厂 工厂设计模式&#xff0c;顾名思义类似一家工厂来制造各种产品&#xff0c;目的在于提高代码的可扩展性。 一、案例引出 通过接口来实现一类产品的功能&#xff0c;如目前有飞机、轮船、汽车这类产品的实体类&#xff0c…

Windows 安装 Android Studio

1、下载Android Studio https://r1—sn-2x3edn7s.gvt1.com/edgedl/android/studio/install/2022.1.1.19/android-studio-2022.1.1.19-windows.exe?cms_redirectyes&mhBy&mip175.146.144.124&mm28&mnsn-2x3edn7s&msnvh&mt1673878346&mvm&mvi1…

Pycharm社区版侧边栏没有database按钮

Pycharm有专业版和社区版这两个版本&#xff0c;普通人在大多数情况下我们都会选择社区版进行下载安装。为啥呢&#xff1f;因为社区版免费&#xff0c;专业版收费一般人用不起呀&#xff0c;而且社区版能够基本满足我们的日常需求&#xff08;但是这也就意味着社区版会比专业版…

LINUX学习之查看文件常用命令(四)

cat 命令描述 cat命令是一种用于查看文件内容的命令&#xff0c;它可以将文件的内容直接输出到标准输出 以下是cat命令常用参数&#xff1a; -A &#xff1a;显示所有字符&#xff0c;包括特殊字符-b &#xff1a;显示行号&#xff0c;仅针对非空白行-E &#xff1a;在每行…

【Java集合进阶】Collection 体系集合详解(ArrayList,LinkedList,HashSet,TreeSet...)

文章目录1. 概念2. 集合和数组的区别3. 集合的体系结构4. Collection父接口5. List 子接口6. List 实现类6.1 ArrayList 类6.2 Vector 类6.3 LinkedList 类6.4 ArrayList和LinkedList的区别7. Set 子接口8. Set 实现类8.1 HashSet 类8.2 TreeSet 类9. Collections 工具类Java编…

Vue9-数据代理

数据代理&#xff1a;通过一个对象代理对另一个对象中属性的操作&#xff08;读写&#xff09; 结论&#xff1a;Vue中通过vm&#xff08;实例对象&#xff09;来代理data中的所有数据 1.首先创建了一个vm对象 2.然后 vue就给vm准备了一些数据&#xff0c;当然也将data存放到…

Ubuntu下源码编译VirtualBox二 —— 源码编译(1)

先打个预防针&#xff1a;在Ubuntu下编译VirtualBox可以说相当复杂。 1. 编译指导 &#xff08;1&#xff09;进入技术文档页面 在VirtualBox主页&#xff08;Oracle VM VirtualBox&#xff09;中&#xff0c;鼠标左键点击“Documentation”下的“Technical docs”&#xff0…

【Android安全】安装mitmproxy Https抓包证书 | 安卓SSL抓包

安装mitmproxy Https抓包证书 macbook上 mitmproxy 抓取安卓手机https流量 重点是安装mitmproxy Https抓包证书 前提 手机需要root&#xff0c;macbook上需要安装好mitmproxy macbook安装mitmproxy 需要完成下文1-3&#xff1a; https://github.com/doug-leith/cydia &…

【内排序 -- 八大排序】

目录&#xff1a;前言算法实现&#xff08;一&#xff09;插入排序1.直接插入排序2.希尔排序&#xff08;缩小增量排序&#xff09;&#xff08;二&#xff09;选择排序1.选择排序2.堆排序&#xff08;三&#xff09;交换排序冒泡排序快速排序1&#xff08;hoare版&#xff09;…

ARM S5PV210的SD卡启动实战

一、S5PV210的SD卡启动实战1 1、任务&#xff1a;大于16KB的bin文件使用 SD 卡启动 (1) 总体思路&#xff1a;将我们的代码分为 2 部分&#xff1a;第一部分 BL1 ≤ 16KB&#xff0c;第二部分为任意大小。 iROM 代码执行完成后&#xff0c;从 SD 卡启动会自动读取 BL1 到 SRA…