java性能調優記錄(線程阻塞)

1. 問題

spring-cloud-gateway 做爲統一的請求入口,負責轉發請求到相應的微服務中去。java

採用的 Spring Cloud 的版本爲 Finchley SR2。react

測試一個接口的性能,發現 tps 只有 1000 req/s 左右就上不去了。web

[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 內容以下:spring

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 字段去內存的路由表中匹配路由,而後轉發請求到對應的微服務中去。shell

2. 排查

  1. 測試接口自己的性能:
[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+。編程

  1. 經過 spring-boot-admin 查看網關的 cpu、內存等佔用狀況,發現都沒有用滿;查看線程情況,發現 reactor-http-nio 線程組存在阻塞狀況。對於響應式編程來講,reactor-http-nio 線程出現阻塞結果是災難性的。
  2. 經過 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.<init>(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.<init>(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)

最終定位到問題代碼爲:json

DefaultServerRequest req = new DefaultServerRequest(exchange);    // 這行代碼存在性能問題
return req.bodyToMono(JSONObject.class).flatMap(body -> {
    ...
});

這裏的邏輯是我須要讀取請求中 body 的值,並轉化爲 json,以後根據其中的特定字段去匹配路由,而後進行轉發。這裏選擇了先把 exchange 轉化爲 DefaultServerRequest,目的是爲了使用該類的 bodyToMono 方法,能夠方便的進行轉換。api

3. 解決

改寫代碼以實現一樣的功能:bash

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 -> {
        ...
    });

以後進行測試,app

[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+,問題解決。

相關文章
相關標籤/搜索