1 问题
在tomcat nio的框架下,每个请求都会有一个单独的tomcat 线程对请求进行处理。
通过Arthas观察发现,在请求中如果有jjwt的签名,有的请求耗时会非常高,高到3s ~ 4s.
查看源码,jjwt的签名方法compact(),有一处源码如下
// io/jsonwebtoken/impl/DefaultJwtBuilder.java
@Override
public String compact() {
if (this.serializer == null) {
// try to find one based on the services available
// TODO: This util class will throw a UnavailableImplementationException here to retain behavior of previous version, remove in v1.0
// use the previous commented out line instead
this.serializer = LegacyServices.loadFirst(Serializer.class);
}
....
}
loadFirst方法最终会调用 ServiceLoader.load
private static <T> T loadFirst(Class<T> spi, ClassLoader classLoader) {
ServiceLoader<T> serviceLoader = ServiceLoader.load(spi, classLoader);
if (serviceLoader.iterator().hasNext()) {
return serviceLoader.iterator().next();
}
return null;
}
而SPI的这种实现方式,会遍历服务的JAR包,可能由于种种原因(作者未确认本文中jjwt耗时高的根因。),导致ServiceLoader hasNext高到3s多。
SPI相关参考 Java SPI 使用及原理分析 | 董宗磊的博客--靡不有初,鲜克有终
2 解决
其实jjwt compact方法给了注释
try to find one based on the services available
就是说我们提前给jjwt的builder指定解析器
jjwt依赖包中本身有提供jackson、gson的解析器,根据需求指定即可。
Date expiredTime = new Date(System.currentTimeMillis() + TTL);
JwtBuilder builder = Jwts.builder()
.setHeaderParam("typ", "JWT")
.signWith(Keys.hmacShaKeyFor(SECRET.getBytes()))
.setIssuer(ISS)
// 这一行指定了解析器。 (可以引入jjwt的jackson解析器包)
.serializeToJsonWith(new JacksonSerializer<>());
return builder
.claim("yourClaim", "my personal claim")
.setExpiration(expiredTime)
.compact();
3 效果
本地测试
第一次使用jjwt的compact还是有300ms左右的耗时。
而后续使用的耗时为1ms以下
第一次的compact耗时跟进了一下源码。目前看是jac java安全相关的逻辑。
在第一次会加载相关的安全实例。
// jdk的依赖 javax/crypto/Mac.class
public static final Mac getInstance(String var0) throws NoSuchAlgorithmException {
List var1 = GetInstance.getServices("Mac", var0);
Iterator var2 = var1.iterator();
Service var3;
do {
if (!var2.hasNext()) {
throw new NoSuchAlgorithmException("Algorithm " + var0 + " not available");
}
var3 = (Service)var2.next();
} while(!JceSecurity.canUseProvider(var3.getProvider()));
return new Mac(var3, var2, var0);
}
PS:
跟社区提的ISSUE