Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] 性能优化--重构默认鉴权插件中的JWT相关功能 #9859

Closed
YunWZ opened this issue Jan 16, 2023 · 2 comments · Fixed by #9873 or #9889
Closed

[Feature] 性能优化--重构默认鉴权插件中的JWT相关功能 #9859

YunWZ opened this issue Jan 16, 2023 · 2 comments · Fixed by #9873 or #9889

Comments

@YunWZ
Copy link
Contributor

YunWZ commented Jan 16, 2023

[Feature] 性能优化--重构默认鉴权插件中的JWT相关功能

Is your feature request related to a problem? Please describe.

在多个压测中都出现开启鉴权后性能下降的情况.

默认鉴权插件支持基于用户名-密码/jwt令牌两种鉴权方式,在大部分情况下客户端-服务端交互时使用jwt令牌,且会针对每个请求进行鉴权.

因此有必要对jwt相关功能进行优化.

Describe the solution you'd like

默认鉴权插件中,其jwt令牌由其生成,同时也由其验证.因此, 没有必要使用jwt框架--尽管jjwt(当前默认鉴权插件使用的第三方依赖)支持的签名算法更加丰富, 但默认插件只使用HmacSHA256算法进行签名,因此完全可以自定义jwt验证逻辑,从而提供更好的性能.

以下是使用jjwt和自实现的生成/验证jwt令牌的性能测试(该性能测试基于jmh):

总览:

Benchmark                                                             Mode  Cnt       Score      Error   Units
JwtTokenManager32Benchmark.measureCreateNewTokenForNacosJwtParser32  thrpt   20      ≈ 10⁻⁴             ops/ns
JwtTokenManager32Benchmark.measureCreateNewTokenForNacosJwtParser48  thrpt   20      ≈ 10⁻⁴             ops/ns
JwtTokenManager32Benchmark.measureCreateNewTokenForNacosJwtParser64  thrpt   20      ≈ 10⁻⁴             ops/ns
JwtTokenManager32Benchmark.measureCreateToken                        thrpt   20      ≈ 10⁻⁵             ops/ns
JwtTokenManager32Benchmark.measureValidNewTokenForNacosJwtParser32   thrpt   20      ≈ 10⁻⁴             ops/ns
JwtTokenManager32Benchmark.measureValidNewTokenForNacosJwtParser48   thrpt   20      ≈ 10⁻⁴             ops/ns
JwtTokenManager32Benchmark.measureValidNewTokenForNacosJwtParser64   thrpt   20      ≈ 10⁻⁴             ops/ns
JwtTokenManager32Benchmark.measureValidOldToken                      thrpt   20      ≈ 10⁻⁴             ops/ns
JwtTokenManager32Benchmark.measureCreateNewTokenForNacosJwtParser32   avgt   20    3342.923 ±   20.370   ns/op
JwtTokenManager32Benchmark.measureCreateNewTokenForNacosJwtParser48   avgt   20    3436.895 ±   19.460   ns/op
JwtTokenManager32Benchmark.measureCreateNewTokenForNacosJwtParser64   avgt   20    3454.587 ±   31.885   ns/op
JwtTokenManager32Benchmark.measureCreateToken                         avgt   20  159066.727 ± 1396.910   ns/op
JwtTokenManager32Benchmark.measureValidNewTokenForNacosJwtParser32    avgt   20    3405.966 ±   24.494   ns/op
JwtTokenManager32Benchmark.measureValidNewTokenForNacosJwtParser48    avgt   20    3584.609 ±   49.003   ns/op
JwtTokenManager32Benchmark.measureValidNewTokenForNacosJwtParser64    avgt   20    3532.710 ±   18.615   ns/op
JwtTokenManager32Benchmark.measureValidOldToken                       avgt   20    4494.201 ±   49.278   ns/op
方法 目标方法
measureCreateToken JwtTokenManager#createToken , 使用jjwt
measureValidOldToken JwtTokenManager#validateToken , 使用jjwt
measureCreateNewTokenForNacosJwtParser32 NacosJwtParser,使用256bits的密钥
measureCreateNewTokenForNacosJwtParser48 NacosJwtParser,使用384bits的密钥
measureCreateNewTokenForNacosJwtParser64 NacosJwtParser,使用512bits的密钥
measureValidNewTokenForNacosJwtParser32 NacosJwtParser,使用256bits的密钥
measureValidNewTokenForNacosJwtParser48 NacosJwtParser,使用384bits的密钥
measureValidNewTokenForNacosJwtParser64 NacosJwtParser,使用256bits的密钥

详细测试结果:

Result "com.alibaba.nacos.microbench.plugin.auth.JwtTokenManager32Benchmark.measureCreateToken":
  ≈ 10⁻⁵ ops/ns

Result "com.alibaba.nacos.microbench.plugin.auth.JwtTokenManager32Benchmark.measureValidNewTokenForNacosJwtParser32":
  ≈ 10⁻⁴ ops/ns

Result "com.alibaba.nacos.microbench.plugin.auth.JwtTokenManager32Benchmark.measureValidNewTokenForNacosJwtParser48":
  ≈ 10⁻⁴ ops/ns

Result "com.alibaba.nacos.microbench.plugin.auth.JwtTokenManager32Benchmark.measureValidNewTokenForNacosJwtParser64":
  ≈ 10⁻⁴ ops/ns

Result "com.alibaba.nacos.microbench.plugin.auth.JwtTokenManager32Benchmark.measureValidOldToken":
  ≈ 10⁻⁴ ops/ns

Result "com.alibaba.nacos.microbench.plugin.auth.JwtTokenManager32Benchmark.measureCreateNewTokenForNacosJwtParser32":
  3342.923 ±(99.9%) 20.370 ns/op [Average]
  (min, avg, max) = (3303.892, 3342.923, 3400.073), stdev = 23.459
  CI (99.9%): [3322.552, 3363.293] (assumes normal distribution)
Result "com.alibaba.nacos.microbench.plugin.auth.JwtTokenManager32Benchmark.measureCreateNewTokenForNacosJwtParser48":
  3436.895 ±(99.9%) 19.460 ns/op [Average]
  (min, avg, max) = (3396.484, 3436.895, 3470.627), stdev = 22.410
  CI (99.9%): [3417.435, 3456.355] (assumes normal distribution)


Result "com.alibaba.nacos.microbench.plugin.auth.JwtTokenManager32Benchmark.measureCreateNewTokenForNacosJwtParser64":
  3454.587 ±(99.9%) 31.885 ns/op [Average]
  (min, avg, max) = (3409.146, 3454.587, 3499.195), stdev = 36.719
  CI (99.9%): [3422.702, 3486.473] (assumes normal distribution)

Result "com.alibaba.nacos.microbench.plugin.auth.JwtTokenManager32Benchmark.measureCreateToken":
  159066.727 ±(99.9%) 1396.910 ns/op [Average]
  (min, avg, max) = (157419.692, 159066.727, 163992.748), stdev = 1608.684
  CI (99.9%): [157669.817, 160463.638] (assumes normal distribution)

Result "com.alibaba.nacos.microbench.plugin.auth.JwtTokenManager32Benchmark.measureValidNewTokenForNacosJwtParser32":
  3405.966 ±(99.9%) 24.494 ns/op [Average]
  (min, avg, max) = (3354.423, 3405.966, 3458.500), stdev = 28.208
  CI (99.9%): [3381.471, 3430.460] (assumes normal distribution)

Result "com.alibaba.nacos.microbench.plugin.auth.JwtTokenManager32Benchmark.measureValidNewTokenForNacosJwtParser48":
  3584.609 ±(99.9%) 49.003 ns/op [Average]
  (min, avg, max) = (3496.270, 3584.609, 3708.392), stdev = 56.432
  CI (99.9%): [3535.605, 3633.612] (assumes normal distribution)

Result "com.alibaba.nacos.microbench.plugin.auth.JwtTokenManager32Benchmark.measureValidNewTokenForNacosJwtParser64":
  3532.710 ±(99.9%) 18.615 ns/op [Average]
  (min, avg, max) = (3499.101, 3532.710, 3586.782), stdev = 21.437
  CI (99.9%): [3514.096, 3551.325] (assumes normal distribution)

Result "com.alibaba.nacos.microbench.plugin.auth.JwtTokenManager32Benchmark.measureValidOldToken":
  4494.201 ±(99.9%) 49.278 ns/op [Average]
  (min, avg, max) = (4409.962, 4494.201, 4614.472), stdev = 56.748
  CI (99.9%): [4444.924, 4543.479] (assumes normal distribution)

结果对比
从以上测试结果可以看到,jjwt生成令牌平均每个操作耗时159066.727ns左右;验签操作平均耗时4494.201ns.

而自定义解析器生成令牌平均耗时3436.895us左右;验签操作平均耗时3436.895ns.

完整测试结果参考 此处

Describe alternatives you've considered

当然也有必要对鉴权逻辑进行优化--但那是另一部分.

Additional context
生成jwt令牌:

自定义的jwt解析器支持HmacSHA256/HmacSHA384/HmacSHA512三种签名算法,具体使用哪种算法根据密钥nacos.core.auth.plugin.nacos.token.secret.key的长度决定:

  1. HmacSHA256: 256bits <= 密钥长度大于 < 384bits;
  2. HmacSHA256: 384bits <= 密钥长度大于 < 512bits;
  3. HmacSHA256: 512bits <= 密钥长度大于.

验证jwt令牌:

自定义的jwt解析器支持HmacSHA256/HmacSHA384/HmacSHA512三种签名算法, 可以对jwt令牌进行签名验证.如果不是这三种签名算法生成的jwt令牌则无法验签--但这没关系,因为jwt令牌完全由鉴权插件自己生成,它总是能对自己生成的jwt令牌进行验签!

@YunWZ
Copy link
Contributor Author

YunWZ commented Jan 16, 2023

另一份测试结果:

Benchmark                                                             Mode  Cnt    Score    Error   Units
JwtTokenManager32Benchmark.measureCreateNewTokenForNacosJwtParser32  thrpt   20  291.646 ±  9.276  ops/ms
JwtTokenManager32Benchmark.measureCreateNewTokenForNacosJwtParser48  thrpt   20  292.208 ±  2.863  ops/ms
JwtTokenManager32Benchmark.measureCreateNewTokenForNacosJwtParser64  thrpt   20  285.654 ±  6.371  ops/ms
JwtTokenManager32Benchmark.measureCreateToken                        thrpt   20    6.187 ±  0.149  ops/ms
JwtTokenManager32Benchmark.measureValidNewTokenForNacosJwtParser32   thrpt   20  296.135 ±  2.125  ops/ms
JwtTokenManager32Benchmark.measureValidNewTokenForNacosJwtParser48   thrpt   20  282.903 ±  5.055  ops/ms
JwtTokenManager32Benchmark.measureValidNewTokenForNacosJwtParser64   thrpt   20  278.714 ±  4.939  ops/ms
JwtTokenManager32Benchmark.measureValidOldToken                      thrpt   20  222.858 ±  1.463  ops/ms
JwtTokenManager32Benchmark.measureCreateNewTokenForNacosJwtParser32   avgt   20    0.003 ±  0.001   ms/op
JwtTokenManager32Benchmark.measureCreateNewTokenForNacosJwtParser48   avgt   20    0.003 ±  0.001   ms/op
JwtTokenManager32Benchmark.measureCreateNewTokenForNacosJwtParser64   avgt   20    0.003 ±  0.001   ms/op
JwtTokenManager32Benchmark.measureCreateToken                         avgt   20    0.162 ±  0.003   ms/op
JwtTokenManager32Benchmark.measureValidNewTokenForNacosJwtParser32    avgt   20    0.003 ±  0.001   ms/op
JwtTokenManager32Benchmark.measureValidNewTokenForNacosJwtParser48    avgt   20    0.004 ±  0.001   ms/op
JwtTokenManager32Benchmark.measureValidNewTokenForNacosJwtParser64    avgt   20    0.004 ±  0.001   ms/op
JwtTokenManager32Benchmark.measureValidOldToken                       avgt   20    0.004 ±  0.001   ms/op

@YunWZ
Copy link
Contributor Author

YunWZ commented Feb 1, 2023

做了一个测试,结果如下:

测试环境信息

服务端配置:

OS: Ubuntu22.04
CPU: Intel® Core™ i5-1135G7 Processor (8M Cache, up to 4.20 GHz), 8核
Memery: 16GB (jvm虚拟机只分配4G)

其他

测试接口: login / getConfig
测试工具: JMeter

Nacos服务端参数:

java \
-server -Xms4g -Xmx4g -Xmn1g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m \
-Xlog:gc*:file=/home/ywz/logs/nacos_gc.log:time,tags:filecount=10,filesize=102400 \
-Dnacos.standalone=true \
-Ddb.url.0="jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC" \
-Ddb.user="nacos" \
-Ddb.num=1 \
-Ddb.password="xxx" \
-Dnacos.core.auth.enabled=true \
-Dnacos.core.auth.plugin.nacos.token.secret.key="U2VjcmV0S2V5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=" \
-Dnacos.core.auth.system.type=nacos \
-Dnacos.core.auth.caching.enabled=true \
-Dspring.sql.init.platform=mysql \
-jar nacos-server.jar

Nacos服务端版本:

2.2.1-rc: 基于最新的develop分支, 也是本次测试的基准版本;
优化登陆鉴权逻辑: 在2.2.1-rc基础上优化登陆接口和AccessToken验证逻辑;
优化jwt: 在2.2.1-rc基础上移除jjwt, 使用自定义的生成和验证jwt令牌的逻辑;

测试结果

查询配置接口

2.2.1-rc:

线程数 平均值 TP90 TP95 TP99 吞吐量 异常%
10 71 73 144 1027 132.3/sec 0
50 263 1029 1043 1115 183.0/sec 0
100 359 1043 1073 1160 269.0/sec 0
200 260 1061 1145 1805 706.8/sec 43.66%

优化登陆鉴权逻辑:

线程数 平均值 TP90 TP95 TP99 吞吐量 异常%
10 71 104 558 637 135.0/sec 0
50 179 591 612 685 266.1/sec 0
100 232 609 649 730 404.1/sec 0
200 未测试

优化jwt:

线程数 平均值 TP90 TP95 TP99 吞吐量 异常%
10 67 64 137 1024 136.7/sec 0
50 244 1021 1030 1059 191.8/sec 0
100 339 1031 1048 1087 277.2/sec 0
200 261 1049 1121 1690 717.2/sec 39.88%

对比

  1. 与基准版本比, 优化登陆鉴权逻辑后,性能提升明显(参考50/100并发线程时的测试结果), 200并发线程时,由于测试机器限制,JMeter会出现网址分配异常,因此不考虑;
  2. 与基准版本比, 优化jwt后,性能有所提升(参考10/50/100并发线程时的测试结果),,这一点也可参考上面的JMH测试结果, 在JWT验证方面比jjwt的方式快900ns左右, 反映到接口上区别不大.

登陆接口(all with keep-alive)

2.2.1-rc:

线程数 平均值 TP90 TP95 TP99 吞吐量 异常%
10 140 192 217 281 68.5/sec 0
30 336 428 451 500 86.1/sec 0
50 552 693 729 808 87.5/sec 0
100 1088 1360 1429 1563 86.6/sec 0
200 2150 2728 2828 3034 87.7/sec 0

优化登陆鉴权逻辑:

线程数 平均值 TP90 TP95 TP99 吞吐量 异常%
10 135 187 213 281 70.5/sec 0
30 323 415 438 485 89.6/sec 0
50 533 680 718 798 89.6/sec 0
100 1092 1352 1418 1538 88.6/sec 0
200 2178 2610 2728 2968 88.9/sec 0

优化jwt:

线程数 平均值 TP90 TP95 TP99 吞吐量 异常%
10 125 149 159 192 76.3/sec 0
30 331 419 441 485 87.4/sec 0
50 539 693 727 804 87.3/sec 0
100 1076 1290 1342 1453 88.7/sec 0
200 2180 2694 2800 3027 88.8/sec 0

对比

测试过程中当并发线程达到100后,服务端cpu使用率达到90%以上,其响应时间由于等待时间过长,个人认为没有参考意义.

  1. 与基准版本比, 优化登陆鉴权逻辑后,性能有所提升(参考10/30/50/100并发线程时的测试结果);
  2. 与基准版本比, 优化jwt后,性能略有提升;

对于登陆接口, 更多的性能损耗在于使用Bcrypt算法加密密码.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants