jetcache icon indicating copy to clipboard operation
jetcache copied to clipboard

com.alicp.jetcache.AbstractCache: jetcache(RedisLettuceCache) GET error. key=[XXX]

Open herodotus-ecosystem opened this issue 2 years ago • 6 comments

环境:

  • JDK 8 U331
  • Spring Boot 2.7.2
  • JetCache 2.6.7
  • RedisClient :Lettuce

问题:

首先该问题在单体应用或者一个独立服务中,没有发现。

如果是用微服务,两个服务同时使用同一个 Redis。第一个服务没有任何问题,第二个服务中,如果缓存中使用了和第一个服务相同的Key的缓存,那么就会出现这个问题。

com.alicp.jetcache.AbstractCache: jetcache(RedisLettuceCache) GET error. key=[XXX]

额外说明:

这个问题在 JetCache 2.6.7 版本下存在。同样的环境,清空缓存,使用 2.6.6 就没有这个问题。

尝试解决:

进行了代码跟踪,尝试看看能不能解决这个问题。奈何能力和精力有限,未能解决。但是通过跟踪代码发现,主要问题出现在以下代码中:

RedisLettuceCache

@Override
    protected CacheGetResult<V> do_GET(K key) {
        try {
            byte[] newKey = buildKey(key);
            RedisFuture<byte[]> future = stringAsyncCommands.get(newKey);
            CacheGetResult<V> result = new CacheGetResult<>(future.handle((valueBytes, ex) -> {
                if (ex != null) {
                    JetCacheExecutor.defaultExecutor().execute(() -> logError("GET", key, ex));
                    return new ResultData(ex);
                } else {
                    try {
                        if (valueBytes != null) {
                            CacheValueHolder<V> holder = (CacheValueHolder<V>) valueDecoder.apply(valueBytes);
                            if (System.currentTimeMillis() >= holder.getExpireTime()) {
                                return new ResultData(CacheResultCode.EXPIRED, null, null);
                            } else {
                                return new ResultData(CacheResultCode.SUCCESS, null, holder);
                            }
                        } else {
                            return new ResultData(CacheResultCode.NOT_EXISTS, null, null);
                        }
                    } catch (Exception exception) {
                        logError("GET", key, ex);
                        return new ResultData(ex);
                    }
                }
            }));
            setTimeout(result);
            return result;
        } catch (Exception ex) {
            logError("GET", key, ex);
            return new CacheGetResult(ex);
        }
    }

问题主要出在:valueDecoder.apply(valueBytes)。出问题时,valueBytes是负值,代码 AbstractValueDecoder 中,无法根据这个值转换后的identityNumber,找到对应的Decoder,导致反序列化失败。

DecoderMap 中是存有 KryoValueDecoderSpringJavaValueDecoder 两个反序列化器的。不知道为什么生成的identityNumber 会变,导致找不到Decoder

 @Override
    public Object apply(byte[] buffer) {
        try {
            if (useIdentityNumber) {
                DecoderMap.registerBuildInDecoder();
                int identityNumber = parseHeader(buffer);
                AbstractValueDecoder decoder = DecoderMap.getDecoder(identityNumber);
                Objects.requireNonNull(decoder, "no decoder for identity number:" + identityNumber);
                return decoder.doApply(buffer);
            } else {
                return doApply(buffer);
            }
        } catch (Exception e) {
            throw new CacheEncodeException("decode error", e);
        }
    }

herodotus-ecosystem avatar Jul 24 '22 03:07 herodotus-ecosystem

2.6.7和2.6.6只有一个区别,就是2.6.7把这个异常打出来了,你以前用2.6.6应该也有问题,只不过没发现。 应该是你的value序列化遇到了问题,可以看看有没有堆栈。

areyouok avatar Jul 24 '22 05:07 areyouok

哦,好的。谢谢大佬指点,我再好好跟一下看看。

herodotus-ecosystem avatar Jul 24 '22 05:07 herodotus-ecosystem

这两天一直在跟踪这个问题。

大多数情况,确实是因为远程存储的数据反序列化的问题导致Get抛出错误。

但是还存在 valueDecoder 找不到,导致的抛空。(注:下图是出现问题的cache,是通过 @CreateCache 创建)

QQ浏览器截图20220728215233

创建代码如下:

    @CreateCache(name = ProtectConstants.CACHE_NAME_TOKEN_SECURE_KEY, cacheType = CacheType.BOTH)
    protected Cache<String, SecretKey> cache;

@areyouok

herodotus-ecosystem avatar Jul 28 '22 14:07 herodotus-ecosystem

你这前面4个字节应该是一个升级了kryo5,同时jetcache版本小于2.6.7的程序写进去的。

kryo4和kryo5完全不兼容,并且kryo5改成了little endian,jetcache<=2.6.6使用kryo Output.writeInt()来写前面4个字节,会导致kryo4和kryo5写入的结果不一样,但是decoder的时候只能按一种方式解。所以jetcache 2.6.7改成了自己按big endian的方式来写着4个字节。

areyouok avatar Jul 28 '22 14:07 areyouok

你这前面4个字节应该是一个升级了kryo5,同时jetcache版本小于2.6.7的程序写进去的。

kryo4和kryo5完全不兼容,并且kryo5改成了little endian,jetcache<=2.6.6使用kryo Output.writeInt()来写前面4个字节,会导致kryo4和kryo5写入的结果不一样,但是decoder的时候只能按一种方式解。所以jetcache 2.6.7改成了自己按big endian的方式来写着4个字节。

我用的2.7.3,缓存是新建的,并且只有一个服务使用这个缓存还是有这样的问题 image

        scoreLiveCache = cacheManager.getOrCreateCache(
                QuickConfig.newBuilder("scoreLiveCache.")
                        .cacheType(CacheType.REMOTE)
                        .build()

image

Skqing avatar Mar 22 '23 09:03 Skqing