最近在开发一个电商平台的秒杀功能时,遇到了一个让人头疼的问题:系统在高峰期频繁报出空指针异常(NullPointerException),而且偏偏出现在Redis的自增操作上,明明代码里已经做了判空处理,为什么还会出现这种问题?更奇怪的是,这个错误并不是每次都会出现,而是在并发量突然增大的时候才会偶尔蹦出来。
经过一番排查,终于找到了问题的根源,如果你也遇到过类似的情况,或者想提前避坑,不妨看看这篇文章。
先来看一段典型的代码:
public Long increaseStock(String productId) { String key = "stock:" + productId; // 尝试从Redis中自增库存 Long currentStock = redisTemplate.opsForValue().increment(key, 1); if (currentStock == null) { // 如果返回null,可能是key不存在,尝试初始化 redisTemplate.opsForValue().setIfAbsent(key, 0L); currentStock = redisTemplate.opsForValue().increment(key, 1); } return currentStock; }
看起来逻辑很严谨:先尝试自增,如果返回null
,就初始化key
再自增,在高并发场景下,这段代码仍然可能抛出空指针异常!
错误日志如下:
java.lang.NullPointerException: null
at com.example.service.StockService.increaseStock(StockService.java:42)
Redis的INCR
命令在正常情况下不会返回null
,但如果Redis连接异常(比如网络抖动、Redis服务短暂不可用),某些Redis客户端(如Lettuce或Jedis)可能会返回null
,而不是抛出异常。
在高并发场景下,多个线程可能同时发现key
不存在,并同时执行setIfAbsent
,虽然Redis的SETNX
是原子的,但setIfAbsent
后立即increment
的操作并不是原子的,仍然可能导致竞争问题。
RedisTemplate
在某些情况下(如序列化配置不当)也可能导致返回null
,尤其是在使用自定义序列化器时。
最稳妥的方式是使用Lua脚本,将判断key是否存在 + 初始化 + 自增
作为一个原子操作执行:
private static final String INCREMENT_SCRIPT = "local current = redis.call('incr', KEYS[1])\n" + "if current == 1 then\n" + // 如果是第一次自增,说明key刚被创建 " redis.call('expire', KEYS[1], ARGV[1])\n" + // 可选:设置过期时间 "end\n" + "return current"; public Long safeIncrement(String key, long timeoutSeconds) { return redisTemplate.execute( new DefaultRedisScript<>(INCREMENT_SCRIPT, Long.class), Collections.singletonList(key), String.valueOf(timeoutSeconds) ); }
如果不想用Lua脚本,可以在代码层面加锁(比如分布式锁):
public Long safeIncrementWithLock(String key) { Long value = redisTemplate.opsForValue().increment(key, 1); if (value == null) { synchronized (this) { // 分布式场景下可以用Redisson锁 value = redisTemplate.opsForValue().increment(key, 1); if (value == null) { redisTemplate.opsForValue().setIfAbsent(key, 0L); value = redisTemplate.opsForValue().increment(key, 1); } } } return value; }
如果可能,提前将所有可能的key
初始化(比如在商品上架时写入Redis),避免运行时动态创建。
null
,也要做好防御性编程。 Redis的自增操作看似简单,但在高并发环境下可能会遇到各种边界情况,空指针异常往往只是表象,背后可能是竞争条件、连接问题或客户端行为不一致导致的,通过Lua脚本、双重检查或预初始化等方式,可以有效避免这类问题。
如果你的系统也依赖Redis的高并发操作,不妨检查一下相关代码,看看是否有类似的隐患!
本文由 脱吟 于2025-08-05发表在【云服务器提供商】,文中图片由(脱吟)上传,本平台仅提供信息存储服务;作者观点、意见不代表本站立场,如有侵权,请联系我们删除;若有图片侵权,请您准备原始证明材料和公证书后联系我方删除!
本文链接:https://vps.7tqx.com/wenda/538367.html
发表评论