alibaba / jetcache

JetCache is a Java cache framework.
Apache License 2.0
5.03k stars 1.05k forks source link

kryo5 decode error #835

Closed Jiiiiiin closed 9 months ago

Jiiiiiin commented 9 months ago

全局配置如下:


# --- alibaba-jetcache
# 下面配置中${area} default 对应@Cached和@CreateCache的area属性。注意如果注解上没有指定area
# ,默认值是"default"。
jetcache.statIntervalMinutes=15
# 是否把jetcache-anno把cacheName作为远程缓存key前缀,默认 false
jetcache.areaInCacheName=false
jetcache.hidePackages=com.xxx
jetcache.local.default.type=caffeine
# 默认,仅local类型的缓存需要指定。本地最大记录数支持 100 用户,请根据业务判断自行在注解设置每个缓存实例的最大元素的全局配置
jetcache.local.default.limit=100
jetcache.local.default.keyConvertor=jackson
#jetcache.local.default.valueEncoder=kryo5
#jetcache.local.default.valueDecoder=kryo5
# 关于缓存的超时时间,有多个地方指定,澄清说明一下:
#put等方法上指定了超时时间,则以此时间为准
#put等方法上未指定超时时间,使用Cache实例的默认超时时间
#Cache实例的默认超时时间,通过在@CreateCache和@Cached上的expire属性指定,如果没有指定,使用yml中定义的全局配置,例如@Cached(cacheType=local)使用jetcache.local.default.expireAfterWriteInMillis,如果仍未指定则是无穷大
# 默认,100秒,单位毫秒
jetcache.local.default.expireAfterWriteInMillis=100000
# 以毫秒为单位,指定多长时间没有访问,就让缓存失效,当前只有本地缓存支持。
jetcache.local.default.expireAfterAccessInMillis=100000
jetcache.remote.default.type=redis.lettuce
jetcache.remote.default.database=0
jetcache.remote.default.keyConvertor=jackson
jetcache.remote.default.broadcastChannel=${spring.application.name}
jetcache.remote.default.valueEncoder=kryo5
jetcache.remote.default.valueDecoder=kryo5
# lettuce使用Netty建立单个连接连redis,所以不需要配置连接池。
#jetcache.remote.default.poolConfig.minIdle=5
#jetcache.remote.default.poolConfig.maxIdle=20
#jetcache.remote.default.poolConfig.maxTotal=50
# --- alibaba-jetcache end

测试场景:


  @Test
  public void qryPayRecords() {
    final RecordsQryType qryType = new RecordsQryType(
        10,
        2,
        "20190101",
        "20190102",
        "00000000000000000000000000000"
    );
    // app-service 补充
    qryType.setMobilePhone("13800000001");
    qryType.setOrderNoType(OrderNoType.newInstance(UUID.randomUUID().toString(), ServiceConfig.xxx));

    BaseSecOrgRecordCmplxOdrType<RecordRespListOdrDtlType> odrType = this.paymentRecordsInfraclI.qryPayRecords(qryType);
    logger.info("odrType: {}", odrType);
  }

缓存成功了,但是因为 BaseSecOrgRecordCmplxOdrType 没有无参数构造器(我们认为领域值对象,应该最好使用构造器初始化,所以在之前为使用jetcache的时候,针对原始的 redis缓存,针对构造器反序列化都使用 如下:

  @JsonCreator
  public static SwitchStatus convert(@JsonProperty("value") final Integer value) {
    return ValEnumI.convert(SwitchStatus.class, value);
  }

但是在当前的 BaseSecOrgRecordCmplxOdrType 没有配置 @JsonCreator 的时候,在执行一遍测试用例就报,反序列化异常,直至构造器问题 cause by com.esotericsoftware.kryo.kryo5.KryoException: Class cannot be created (missing no-arg constructor): com.xxx.bizlib.types.secpaging.BaseSecOrgRecordCmplxOdrType

2023-11-19 13:28:12.496 ERROR 51991 --- [lettuce-kqueueEventLoop-6-4] c.a.jetcache.AbstractCache:54 -- jetcache(RedisLettuceCache) GET error. key=[13800000001]

com.alicp.jetcache.support.CacheEncodeException: decode error
    at com.alicp.jetcache.support.AbstractValueDecoder.apply(AbstractValueDecoder.java:48) ~[jetcache-core-2.7.4.jar:na]
    at com.alicp.jetcache.support.AbstractValueDecoder.apply(AbstractValueDecoder.java:11) ~[jetcache-core-2.7.4.jar:na]
    at com.alicp.jetcache.redis.lettuce.RedisLettuceCache.lambda$do_GET$6(RedisLettuceCache.java:172) ~[jetcache-redis-lettuce-2.7.4.jar:na]
    at java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:836) ~[na:1.8.0_382]
    at java.util.concurrent.CompletableFuture$UniHandle.tryFire(CompletableFuture.java:811) ~[na:1.8.0_382]
    at java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:488) ~[na:1.8.0_382]
    at java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:1975) ~[na:1.8.0_382]
    at io.lettuce.core.protocol.AsyncCommand.completeResult(AsyncCommand.java:122) ~[lettuce-core-6.1.10.RELEASE.jar:6.1.10.RELEASE]
    at io.lettuce.core.protocol.AsyncCommand.complete(AsyncCommand.java:111) ~[lettuce-core-6.1.10.RELEASE.jar:6.1.10.RELEASE]
    at io.lettuce.core.protocol.CommandWrapper.complete(CommandWrapper.java:63) ~[lettuce-core-6.1.10.RELEASE.jar:6.1.10.RELEASE]
    at io.lettuce.core.protocol.CommandHandler.complete(CommandHandler.java:747) ~[lettuce-core-6.1.10.RELEASE.jar:6.1.10.RELEASE]
    at io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:682) ~[lettuce-core-6.1.10.RELEASE.jar:6.1.10.RELEASE]
    at io.lettuce.core.protocol.CommandHandler.channelRead(CommandHandler.java:599) ~[lettuce-core-6.1.10.RELEASE.jar:6.1.10.RELEASE]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) ~[netty-transport-4.1.94.Final.jar:4.1.94.Final]
    at io.netty.channel.kqueue.AbstractKQueueStreamChannel$KQueueStreamUnsafe.readReady(AbstractKQueueStreamChannel.java:544) ~[netty-transport-classes-kqueue-4.1.94.Final.jar:4.1.94.Final]
    at io.netty.channel.kqueue.AbstractKQueueChannel$AbstractKQueueUnsafe.readReady(AbstractKQueueChannel.java:387) ~[netty-transport-classes-kqueue-4.1.94.Final.jar:4.1.94.Final]
    at io.netty.channel.kqueue.KQueueEventLoop.processReady(KQueueEventLoop.java:213) ~[netty-transport-classes-kqueue-4.1.94.Final.jar:4.1.94.Final]
    at io.netty.channel.kqueue.KQueueEventLoop.run(KQueueEventLoop.java:291) ~[netty-transport-classes-kqueue-4.1.94.Final.jar:4.1.94.Final]
    at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997) ~[netty-common-4.1.94.Final.jar:4.1.94.Final]
    at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.94.Final.jar:4.1.94.Final]
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.94.Final.jar:4.1.94.Final]
    at java.lang.Thread.run(Thread.java:750) ~[na:1.8.0_382]
Caused by: com.esotericsoftware.kryo.kryo5.KryoException: Class cannot be
created (missing no-arg constructor): com.xxx.bizlib.types.secpaging.BaseSecOrgRecordCmplxOdrType
Serialization trace:
value (com.alicp.jetcache.CacheValueHolder)
    at com.esotericsoftware.kryo.kryo5.util.DefaultInstantiatorStrategy.newInstantiatorOf(DefaultInstantiatorStrategy.java:111) ~[kryo5-5.3.0.jar:na]
    at com.esotericsoftware.kryo.kryo5.Kryo.newInstantiator(Kryo.java:1190) ~[kryo5-5.3.0.jar:na]
    at com.esotericsoftware.kryo.kryo5.Kryo.newInstance(Kryo.java:1199) ~[kryo5-5.3.0.jar:na]
    at com.esotericsoftware.kryo.kryo5.serializers.FieldSerializer.create(FieldSerializer.java:163) ~[kryo5-5.3.0.jar:na]
    at com.esotericsoftware.kryo.kryo5.serializers.CompatibleFieldSerializer.read(CompatibleFieldSerializer.java:117) ~[kryo5-5.3.0.jar:na]
    at com.esotericsoftware.kryo.kryo5.Kryo.readObject(Kryo.java:799) ~[kryo5-5.3.0.jar:na]
    at com.esotericsoftware.kryo.kryo5.serializers.ReflectField.read(ReflectField.java:134) ~[kryo5-5.3.0.jar:na]
    at com.esotericsoftware.kryo.kryo5.serializers.CompatibleFieldSerializer.read(CompatibleFieldSerializer.java:185) ~[kryo5-5.3.0.jar:na]
    at com.esotericsoftware.kryo.kryo5.Kryo.readClassAndObject(Kryo.java:880) ~[kryo5-5.3.0.jar:na]
    at com.alicp.jetcache.support.Kryo5ValueDecoder.doApply(Kryo5ValueDecoder.java:40) ~[jetcache-core-2.7.4.jar:na]
    at com.alicp.jetcache.support.AbstractValueDecoder.apply(AbstractValueDecoder.java:43) ~[jetcache-core-2.7.4.jar:na]
    ... 27 common frames omitted

但是就算加上 :

@JsonCreator
  public BaseSecOrgRecordCmplxOdrType(
      @JsonProperty(value = "externalChannel") @NonNull final ExternalChannel externalChannel,
      @JsonProperty(value = "totalCount") @NonNull final String totalCount,
      @JsonProperty(value = "odrDetails") final List<T> odrDetails) {
    InvokeAfterAssertUtil.isTrueForDataValidate(
        Validator.isNumber(totalCount),
        RecordInfo.newInstance(),
        externalChannel,
        "BaseSecOrgRecordCmplxOdrType 初始化断言失败,预期 totalCount 参数需要是一个数值类型 is "
    );
    this.totalCount = Integer.parseInt(totalCount);
    this.odrDetails = odrDetails;
    this.externalChannel = externalChannel;
    this.validate();
  }

kryo5 还是报需要一个无参数构造器,这种问题有更好的解决思路吗老师?

Jiiiiiin commented 9 months ago

目前解决方案,给对应类创建一个无参数构造器,但是对于字段均没有 setter,也就是说 kryo5 在构造对象的时候,应该使用的是强制反射,那这种序列化方案是否比默认的 java方式好呢?另外总觉得 com.fasterxml.jackson.annotation; 注解的方式是不是更优雅?或者老师能介绍一下针对spring-redis 默认的 jackson方式和其他方式的区别么?

areyouok commented 9 months ago

你这个问题是个关于 kryo 的问题,其实和 jetcache 没有关系,kryo 就是需要有反序列化的对象有默认的构造函数。

redis 不适合做通用的 java 反序列化方案,因为 json 字符串里面缺失类型信息,比如如果反射得到你的业务类有个 Object 类型的字段,反序列化时仅仅从 json 中的字符串值根本没有办法知道这是个什么类型的字段。

Jiiiiiin commented 9 months ago

嗯嗯 老师 您说的是,目前用下来的体验就是无论是jackson还是fastjson涉及到redis序列化的地方,都比较容易产生某种“不确定性”,您这个意思一语道破,其实不确定性的根源是json字符串,我在详细了解一下 kryo吧,也是跟着框架才第一次听说,他是能避免json对 java对象的系列化困扰不?

areyouok commented 9 months ago

json不是一个 java 序列化方案,json 字符串里面缺乏类型信息,它无法保证反序列化出来的东西和原来的是一样的(除非是特别简单的对象)。

Jiiiiiin commented 9 months ago

谢谢指点