1、场景
redis版本:
org.springframework.boot spring-boot-starter-data-redis
redis框架:
org.redisson redisson-spring-boot-starter 3.11.2
redis操作工具类:
public boolean setIfAbsent(String key, Object value, long time) {
key = append(key);
boolean flag = true;
try {
if (time > 0) {
flag = redisTemplate.opsForValue().setIfAbsent(key, value, time, TimeUnit.SECONDS);
} else {
flag = setIfAbsent(key, value);
}
} catch (Exception e) {
throw new DispatchCenterException(REDIS_SET_IF_ABSENT_ERR, "key=" + key, e);
}
return flag;
}
实际场景中我们使用的redission作为操作redis的框架,同时封装了对应的工具类,当我们使用时setIfAbset时控制台总是会出现空指针问题。问题截图如下:
通过异常堆栈可以看到是redis的问题,但是看代码并没有看出问题所以只能debug流程跟踪异常信息。在跟踪时就发现了最终的异常信息。
2、原因
public boolean setIfAbsent(String key, Object value, long time) {
key = append(key);
//获取setIfAbsent的响应值时,封装的工具类中使用的是boolean,而不是Boolean
boolean flag = true;
try {
if (time > 0) {
flag = redisTemplate.opsForValue().setIfAbsent(key, value, time, TimeUnit.SECONDS);
} else {
flag = setIfAbsent(key, value);
}
} catch (Exception e) {
throw new DispatchCenterException(REDIS_SET_IF_ABSENT_ERR, "key=" + key, e);
}
return flag;
}
//看源码可以知道setIfAbsent响应值的类型是Boolean
public Boolean setIfAbsent(K key, V value, long timeout, TimeUnit unit) {
byte[] rawKey = this.rawKey(key);
byte[] rawValue = this.rawValue(value);
Expiration expiration = Expiration.from(timeout, unit);
return (Boolean)this.execute((connection) -> {
return connection.set(rawKey, rawValue, expiration, SetOption.ifAbsent());
}, true);
}
.
.
.
public T getNow() {
return this.promise.getNow();
}
//跟踪到最终生成响应参数的源码,可以看到通过一定的逻辑的判断给出最后的返回值,
//但是这里通过三目表达式可以看到最终可能会返回null。
public V getNow() {
Object result = this.result;
return !(result instanceof DefaultPromise.CauseHolder) && result != SUCCESS && result != UNCANCELLABLE ? result : null;
}
场景一
幂等正常的redis请求,可以看到redis最终执行结束后result返回1,并且最终返回值的逻辑判断可以看到直接返回result。
场景二
幂等出现的场景,此时result是一个Object,并且是一个不可查看的Object,通过第二张图可以看到SUCESS的对象指向的内存地址和result指向的内存地址都是同一个对象,并且所有的逻辑判断最终结果为false,因此最终返回值为null,这样也就导致了如果接收参数不设置为Boolean而是设置为boolean,那么返回null之后进行拆箱时会出现空指针问题,不知道这是不是Redission的一个bug,因为正常的逻辑setIfAbsent如果redis中已经存在该key,那么redission应该返回false而不是返回一个null。
3、解决方案
将封装的redis工具类中的boolean改成包装类Boolean。
public Boolean setIfAbsent(String key, Object value, long time) {
key = append(key);
Boolean flag = true;
try {
if (time > 0) {
flag = redisTemplate.opsForValue().setIfAbsent(key, value, time, TimeUnit.SECONDS);
} else {
flag = setIfAbsent(key, value);
}
} catch (Exception e) {
throw new DispatchCenterException(REDIS_SET_IF_ABSENT_ERR, "key=" + key, e);
}
return Boolean.TRUE.equals(flag);
}
4、总结
不知道这算不算是Redission的一个小bug,按照正常理解如果redis存在key,那么使用setIfAbsent时应该返回false或者0,返回null让我怎么都想不通.在GitHub提个issue问了,等待回复吧。