据RedisLabs最新公告,2025年8月发布的Redis企业版7.2中,针对分布式锁机制进行了重大优化,新增了自动续期和锁分级功能,这为高并发场景下的数据一致性提供了更强大的支持,虽然我们日常开发中可能用不到企业版,但这些改进思路值得我们借鉴。
说到高并发,大家肯定都遇到过"超卖"问题,我去年就碰到过一个惨案:某电商平台大促时,因为库存扣减没处理好,同一件商品卖出了实际库存的3倍,最后只能含泪给用户发优惠券道歉。
传统单机锁(比如synchronized)在分布式系统里根本不管用,因为你的服务可能部署在10台机器上,这时候就需要分布式锁来帮忙了,Redis凭借其高性能和丰富的数据结构,成为了实现分布式锁的首选方案。
// 加锁 Boolean locked = redisTemplate.opsForValue().setIfAbsent("lock:order123", "1", 30, TimeUnit.SECONDS); // 业务处理 if(locked) { try { // 处理核心逻辑 } finally { // 释放锁 redisTemplate.delete("lock:order123"); } }
这种锁简单粗暴,但有个致命问题——如果业务执行时间超过30秒,锁自动释放了,其他线程就能获取到锁,这时候如果第一个线程继续执行删除操作,就会把别人的锁给删了!
String clientId = UUID.randomUUID().toString(); Boolean locked = redisTemplate.opsForValue().setIfAbsent( "lock:order123", clientId, 30, TimeUnit.SECONDS ); // 释放锁时先检查是不是自己的锁 String value = redisTemplate.opsForValue().get("lock:order123"); if(clientId.equals(value)) { redisTemplate.delete("lock:order123"); }
这下安全多了,但还有个问题——检查和删除不是原子操作!在get和delete之间锁可能已经过期被其他客户端获取了。
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
用Lua脚本可以确保检查和删除的原子性,这才是生产环境可用的方案。
上面说的都是互斥锁,但有些场景其实读操作是可以并行的,只有写操作需要互斥,这时候就该读写锁出场了。
Redis本身没有内置读写锁,但我们可以用组合的方式实现:
实现思路:
// 获取读锁 public boolean tryReadLock(String key, String clientId, long expireTime) { // 如果没有写锁,就增加读锁计数 String luaScript = "if redis.call('get', 'write_'..KEYS[1]) == false then " + " return redis.call('incr', 'read_'..KEYS[1]) " + "else " + " return 0 " + "end"; Long result = redisTemplate.execute(new DefaultRedisScript<>(luaScript, Long.class), Collections.singletonList(key)); if(result != null && result > 0) { redisTemplate.expire("read_" + key, expireTime, TimeUnit.SECONDS); return true; } return false; } // 获取写锁 public boolean tryWriteLock(String key, String clientId, long expireTime) { // 必须没有读锁和写锁才能获取写锁 String luaScript = "if redis.call('get', 'read_'..KEYS[1]) == false and " + " redis.call('get', 'write_'..KEYS[1]) == false then " + " return redis.call('set', 'write_'..KEYS[1], ARGV[1], 'EX', ARGV[2], 'NX') " + "else " + " return false " + "end"; Boolean result = redisTemplate.execute(new DefaultRedisScript<>(luaScript, Boolean.class), Collections.singletonList(key), clientId, String.valueOf(expireTime)); return Boolean.TRUE.equals(result); }
这是个头疼的问题,设置太短,业务没执行完锁就失效了;设置太长,万一客户端崩溃,其他客户端要等很久才能获取锁。
解决方案:采用自动续期机制,启动一个守护线程,在锁快要过期时自动延长过期时间,Redisson框架已经实现了这个功能。
在Redis主从架构下,如果主节点挂了,从节点变成主节点,但锁信息可能还没同步到从节点,导致锁"神秘消失"。
解决方案:对于强一致性要求的场景,可以使用Redis的RedLock算法(需要至少3个独立的Redis主节点),但要注意性能会有所下降。
同一个线程内如果多次获取同一把锁怎么办?
解决方案:在锁的值中记录客户端ID和重入次数,Redisson同样实现了这个功能。
锁分段:比如库存有1000件,可以分成10个锁(lock_stock_1到lock_stock_10),这样并发度能提高10倍。
乐观锁替代:某些场景可以用版本号机制替代悲观锁,
UPDATE stock SET count = count - 1, version = version + 1 WHERE product_id = 100 AND version = 5
本地缓存+分布式锁:对于热点数据,可以先读本地缓存,只有缓存失效时才去抢分布式锁。
去年我们做了一个秒杀系统,峰值QPS达到5万+,是这样用Redis锁的:
先用Redis原子操作预减库存,避免超卖
local stock = tonumber(redis.call('GET', KEYS[1])) if stock > 0 then redis.call('DECR', KEYS[1]) return 1 end return 0
创建订单时使用分布式锁保证唯一性
String lockKey = "order_lock_" + userId + "_" + productId; try { if(redisLock.tryLock(lockKey, 10, TimeUnit.SECONDS)) { // 检查是否已下单 // 创建订单 } } finally { redisLock.unlock(lockKey); }
支付完成后异步更新数据库库存
Redis分布式锁是解决高并发问题的利器,但用不好反而会成为性能瓶颈甚至导致严重bug,关键要记住:
最后提醒一句:分布式系统没有银弹,Redis锁虽好,但也要根据具体业务场景灵活运用,有时候换个思路,用消息队列或者CAS机制可能更合适。
本文由 波果 于2025-08-03发表在【云服务器提供商】,文中图片由(波果)上传,本平台仅提供信息存储服务;作者观点、意见不代表本站立场,如有侵权,请联系我们删除;若有图片侵权,请您准备原始证明材料和公证书后联系我方删除!
本文链接:https://vps.7tqx.com/wenda/529918.html
发表评论