资讯专栏INFORMATION COLUMN

java性能调优记录(线程阻塞)

AndroidTraveler / 1546人阅读

摘要:其中内容如下网关的逻辑是读取请求中的值,根据字段去内存的路由表中匹配路由,然后转发请求到对应的微服务中去。排查测试接口本身的性能发现接口的可以达到。解决改写代码以实现同样的功能之后进行测试,发现从提升到了,问题解决。

1. 问题

spring-cloud-gateway 作为统一的请求入口,负责转发请求到相应的微服务中去。

采用的 Spring Cloud 的版本为 Finchley SR2。

测试一个接口的性能,发现 tps 只有 1000 req/s 左右就上不去了。

[root@hystrix-dashboard wrk]# wrk -t 10 -c 200 -d 30s --latency -s post-test.lua "http://10.201.0.28:8888/api/v1/json"
Running 30s test @ http://10.201.0.28:8888/api/v1/json
  10 threads and 200 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   188.34ms  110.13ms   2.00s    78.43%
    Req/Sec   106.95     37.19   333.00     77.38%
  Latency Distribution
     50%  165.43ms
     75%  243.48ms
     90%  319.47ms
     99%  472.64ms
  30717 requests in 30.04s, 7.00MB read
  Socket errors: connect 0, read 0, write 0, timeout 75
Requests/sec:   1022.62
Transfer/sec:    238.68KB

其中 post-test.lua 内容如下:

request = function()
    local headers = {}
    headers["Content-Type"] = "application/json"
    local body = [[{
        "biz_code": "1109000001",
        "channel": "7",
        "param": {
            "custom_id": "ABCD",
            "type": "test",
            "animals": ["cat", "dog", "lion"],
            "retcode": "0"
        }
    }]]
    return wrk.format("POST", nil, headers, body)
end

网关的逻辑是读取请求中 body 的值,根据 biz_code 字段去内存的路由表中匹配路由,然后转发请求到对应的微服务中去。

2. 排查

测试接口本身的性能:

[root@hystrix-dashboard wrk]# wrk -t 10 -c 200 -d 30s --latency -s post-test.lua "http://10.201.0.32:8776/eeams-service/api/v1/json"
Running 30s test @ http://10.201.0.32:8776/eeams-service/api/v1/json
  10 threads and 200 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    26.72ms    8.59ms 260.23ms   89.66%
    Req/Sec   752.18    101.46     0.94k    78.67%
  Latency Distribution
     50%   23.52ms
     75%   28.02ms
     90%   35.58ms
     99%   58.25ms
  224693 requests in 30.02s, 50.83MB read
Requests/sec:   7483.88
Transfer/sec:      1.69MB

发现接口的 tps 可以达到 7000+。

通过 spring-boot-admin 查看网关的 cpu、内存等占用情况,发现都没有用满;查看线程状况,发现 reactor-http-nio 线程组存在阻塞情况。对于响应式编程来说,reactor-http-nio 线程出现阻塞结果是灾难性的。

通过 jstack 命令分析线程状态,定位阻塞的代码(第 19 行):

"reactor-http-nio-4" #19 daemon prio=5 os_prio=0 tid=0x00007fb784d7f240 nid=0x80b waiting for monitor entry [0x00007fb71befc000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:404)
    - waiting to lock <0x000000008b0cec30> (a java.lang.Object)
    at org.springframework.boot.loader.LaunchedURLClassLoader.loadClass(LaunchedURLClassLoader.java:93)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at org.springframework.util.ClassUtils.forName(ClassUtils.java:282)
    at org.springframework.http.converter.json.Jackson2ObjectMapperBuilder.registerWellKnownModulesIfAvailable(Jackson2ObjectMapperBuilder.java:753)
    at org.springframework.http.converter.json.Jackson2ObjectMapperBuilder.configure(Jackson2ObjectMapperBuilder.java:624)
    at org.springframework.http.converter.json.Jackson2ObjectMapperBuilder.build(Jackson2ObjectMapperBuilder.java:608)
    at org.springframework.http.codec.json.Jackson2JsonEncoder.(Jackson2JsonEncoder.java:54)
    at org.springframework.http.codec.support.AbstractCodecConfigurer$AbstractDefaultCodecs.getJackson2JsonEncoder(AbstractCodecConfigurer.java:177)
    at org.springframework.http.codec.support.DefaultServerCodecConfigurer$ServerDefaultCodecsImpl.getSseEncoder(DefaultServerCodecConfigurer.java:99)
    at org.springframework.http.codec.support.DefaultServerCodecConfigurer$ServerDefaultCodecsImpl.getObjectWriters(DefaultServerCodecConfigurer.java:90)
    at org.springframework.http.codec.support.AbstractCodecConfigurer.getWriters(AbstractCodecConfigurer.java:121)
    at org.springframework.http.codec.support.DefaultServerCodecConfigurer.getWriters(DefaultServerCodecConfigurer.java:39)
    at org.springframework.web.reactive.function.server.DefaultHandlerStrategiesBuilder.build(DefaultHandlerStrategiesBuilder.java:103)
    at org.springframework.web.reactive.function.server.HandlerStrategies.withDefaults(HandlerStrategies.java:90)
    at org.springframework.cloud.gateway.support.DefaultServerRequest.(DefaultServerRequest.java:81)
    at com.glsc.imf.dbg.route.RouteForJsonFilter.filter(RouteForJsonFilter.java:34)
    at org.springframework.cloud.gateway.handler.FilteringWebHandler$DefaultGatewayFilterChain.lambda$filter$0(FilteringWebHandler.java:115)
    at org.springframework.cloud.gateway.handler.FilteringWebHandler$DefaultGatewayFilterChain$$Lambda$800/1871561393.get(Unknown Source)
    at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:44)

最终定位到问题代码为:

DefaultServerRequest req = new DefaultServerRequest(exchange);    // 这行代码存在性能问题
return req.bodyToMono(JSONObject.class).flatMap(body -> {
    ...
});

这里的逻辑是我需要读取请求中 body 的值,并转化为 json,之后根据其中的特定字段去匹配路由,然后进行转发。这里选择了先把 exchange 转化为 DefaultServerRequest,目的是为了使用该类的 bodyToMono 方法,可以方便的进行转换。

3. 解决

改写代码以实现同样的功能:

return exchange.getRequest().getBody().collectList()
        .map(dataBuffers -> {
            ByteBuf byteBuf = Unpooled.buffer();
            dataBuffers.forEach(buffer -> {
                try {
                    byteBuf.writeBytes(IOUtils.toByteArray(buffer.asInputStream()));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
            return JSON.parseObject(new String(byteBuf.array()));
        })
    .flatMap(body -> {
        ...
    });

之后进行测试,

[root@hystrix-dashboard wrk]# wrk -t 10 -c 200 -d 30s --latency -s post-test.lua "http://10.201.0.28:8888/api/v1/json"
Running 30s test @ http://10.201.0.28:8888/api/v1/json
  10 threads and 200 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    48.47ms   45.85ms 325.87ms   88.55%
    Req/Sec   548.13    202.55   760.00     80.01%
  Latency Distribution
     50%   31.18ms
     75%   39.44ms
     90%  112.18ms
     99%  227.19ms
  157593 requests in 30.02s, 35.94MB read
Requests/sec:   5249.27
Transfer/sec:      1.20MB

发现 tps 从 1000 提升到了 5000+,问题解决。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/73052.html

相关文章

  • 一次logback多线程调优的经历

    摘要:由于不是线程安全的,故在方法上增加了同步操作,造成竞争等待。至此,整个多线程调优结束,通过充分优化同步竞争的方式,最终使得分线程记录日志的性能比最原始的多线程写同一文件提高了倍去锁提高到倍,替换提高倍 背景   在一次项目的性能调优中,发现出现竞争瓶颈,导致在资源未使用满的情况下,TPS已经无法提升。祭起JMC(JAVA MISSON CONTROL)飞行记录器大法后,发现线程集中等待...

    khs1994 评论0 收藏0
  • 【nginx网站性能优化篇(4)】理解nginx的高并发原理及其配置调优

    摘要:使用了多路复用技术的,就成了并发事件驱动的服务器。进程主要负责收集分发请求。同时进程也负责监控的状态,保证高可靠性进程一般设置为跟核心数一致。所以才使得支持更高的并发。配置调优调整指要生成的数量最佳实践是每个运行个工作进程。 Nginx 是如何实现高并发的? Nginx 采用的是多进程(单线程) & 多路IO复用模型。使用了 I/O 多路复用技术的 Nginx,就成了并发事件驱动的服务...

    CODING 评论0 收藏0
  • Java 性能调优指南之 Java 集合概览

    摘要:单线程集合本部分将重点介绍非线程安全集合。非线程安全集合框架的最新成员是自起推出的。这是标准的单线程阵营中唯一的有序集合。该功能能有效防止运行时造型。检查个集合之间不存在共同的元素。基于自然排序或找出集合中的最大或最小元素。 【编者按】本文作者为拥有十年金融软件开发经验的 Mikhail Vorontsov,文章主要概览了所有标准 Java 集合类型。文章系国内 ITOM 管理平台 O...

    gnehc 评论0 收藏0
  • jvm调优

    摘要:垃圾回收垃圾检测引用计数法和可达性分析算法。引用计数法给一个对象添加引用计数器,每当有个地方引用它,计数器就加,引用失效就减。通常会在老年代内存被占满时将会触发,回收整个堆内存。 基础知识 JVM - HotSpot内存布局(1.8之前版本) showImg(https://segmentfault.com/img/bVMdE6?w=1024&h=608); 1.8版本开始,持久区没有...

    jemygraw 评论0 收藏0

发表评论

0条评论

AndroidTraveler

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<