redission-序列化问题追踪

58次阅读

共计 3698 个字符,预计需要花费 10 分钟才能阅读完成。

背景

项目原本是用 jedis 连接 redis,但考虑到需要用 redis 锁,因此替换为方便快捷的redisson,但是使用 redisson 之后会报 decode error,具体信息如下:

2019-05-15 13:39:59.973 [redisson-netty-2-3] ERROR o.r.c.h.CommandDecoder [decodeCommand:203]     - Unable to decode data. channel: [id: 0x477c5ced, L:/192.168.4.94:57423 - R:10.10.10.43/10.10.10.43:6379], reply: ReplayingDecoderByteBuf(ridx=102, widx=102), command: (GET), params: [Geek:xxxxx:xxxx]
java.io.IOException: java.lang.NullPointerException
    at org.nustaq.serialization.FSTObjectInput.readObject(FSTObjectInput.java:247)
    at org.redisson.codec.FstCodec$1.decode(FstCodec.java:228)
    at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:368)
    at org.redisson.client.handler.CommandDecoder.decodeCommand(CommandDecoder.java:200)
    at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:140)
    at org.redisson.client.handler.CommandDecoder.decode(CommandDecoder.java:115)
    at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:502)
    at io.netty.handler.codec.ReplayingDecoder.callDecode(ReplayingDecoder.java:366)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:278)

测试代码

RBucket ste = redissonClient.getBucket("Geek:add:ddd");
Object re = ste.get();

考虑可能是由于序列化产生的问题,查到 NullPointer 3.10.6,设置 codec 为StringCodec,即

redissonClient.getConfig().setCodec(new StringCodec());

但是并未解决问题,redisson 仍然使用默认的FstCodec,通过 idea 强大的提示功能可以看到 getBucket 接受一个 codec 参数

修改代码为

RBucket ste = redissonClient.getBucket("Geek:add:ddd", new StringCodec());
String re = ste.get();

完美解决

问题

为什么直接设置 redisson config 不生效呢,一步步查源码 RedissonObject#RedissonObject

public RedissonObject(CommandAsyncExecutor commandExecutor, String name) {this(commandExecutor.getConnectionManager().getCodec(), commandExecutor, name);
}

可以看出 redisson 默认从 ConnectionManager 里获取 codec 方式,继续看,以 SingleConnectionManager 为例,SingleConnectionManagerMasterSlaveConnectionManager 的子类,具体的类图关系

config.java

public Config(Config oldConf) {setExecutor(oldConf.getExecutor());
        if (oldConf.getCodec() == null) {
            // use it by default
            oldConf.setCodec(new FstCodec());
        }
......
    }

即检测到原有 codec 为空时,则设置为FstCodec

看一下 Redisson.java 配置关键部分代码

protected Redisson(Config config) {
  this.config = config;
  Config configCopy = new Config(config);

  connectionManager = ConfigSupport.createConnectionManager(configCopy);
  evictionScheduler = new EvictionScheduler(connectionManager.getCommandExecutor());
  writeBehindService = new WriteBehindService(connectionManager.getCommandExecutor());
}
    
public static RedissonClient create(Config config) {Redisson redisson = new Redisson(config);
  if (config.isReferenceEnabled()) {redisson.enableRedissonReferenceSupport();
  }
  return redisson;
}

可以看出,config 是在 redisson 初始化的时候传入的

因为我用的是redisson-spring-boot-starter,看一下这个 starter 里面,是如何初始化的,redisson starter 默认使用 spring-data-redis 配置。

@Bean(destroyMethod = "shutdown")
    @ConditionalOnMissingBean(RedissonClient.class)
    public RedissonClient redisson() throws IOException {
        Config config = null;
       ....
        
        if (redissonProperties.getConfig() != null) {....} else {config = new Config();
            String prefix = "redis://";
            Method method = ReflectionUtils.findMethod(RedisProperties.class, "isSsl");
            if (method != null && (Boolean)ReflectionUtils.invokeMethod(method, redisProperties)) {prefix = "rediss://";}
            
            config.useSingleServer()
                .setAddress(prefix + redisProperties.getHost() + ":" + redisProperties.getPort())
                .setConnectTimeout(timeout)
                .setDatabase(redisProperties.getDatabase())
                .setPassword(redisProperties.getPassword());
        }
        
   return Redisson.create(config);

回到一开始的问题,直接设置 redisson codec 为什么不生效?仔细以上分析可以知道,redisson 统一设置 codec 主要是通过初始化的时候传入 ConnectionManager 使 codec 生效,而通过 redissonClient.getConfig().setCodec(...)的方式并不能改变 ConnectionManager 中的编码方式。

结论:

  1. 如果想自定义 codec,需要自己初始化 redissonClient[调用 Redisson.create(config)],或者重写 redisson-starter
  2. 在定制化程度不高时,可直接使用默认 codec,或者把特定的 codec 传入方法体内

Reference

本文由 歧途老农 创作,采用 CC BY 4.0 CN 协议 进行许可。可自由转载、引用,但需署名作者且注明文章出处。如转载至微信公众号,请在文末添加作者公众号二维码。

正文完
 0