设计一个支持高并发的分布式锁,商品维度

本文探讨了一段用于扣减库存的代码存在的问题,并提出了改进方案,包括如何正确实现Redis分布式锁来避免并发问题,以及通过分段锁思想提升Redis处理高并发的能力。

 

我们现在要设计一个扣减库存的代码,如下代码存在哪些问题?

        try {
            Boolean product = redisTemplate.opsForValue().setIfAbsent("product-test001", "1", 11, TimeUnit.SECONDS);
            if (null == product) {
                // 自旋
                while (true) {
                    System.out.println("自旋开始" + Thread.currentThread().getName());
                    Thread.sleep(4000L);
                    // 加锁成功
                    if (null != redisTemplate.opsForValue().setIfAbsent("product-test001", "1", 11, TimeUnit.SECONDS)) {
                        // 扣减库存
                        System.out.println("自旋扣减库存开始" + Thread.currentThread().getName());
                        Thread.sleep(10000L);
                        System.out.println("自旋扣减库存成功" + Thread.currentThread().getName());
                        break;
                    }
                }
            }
            // 扣减库存 模拟扣减库存
            Thread.sleep(10000L);
        } finally {
            // 防止异常情况 必须释放锁
            redisTemplate.delete("product-test001");
        }


自旋开始http-nio-8081-exec-1
自旋开始http-nio-8081-exec-4
自旋开始http-nio-8081-exec-1
自旋开始http-nio-8081-exec-4
自旋开始http-nio-8081-exec-1
自旋扣减库存开始http-nio-8081-exec-4
自旋开始http-nio-8081-exec-1
自旋开始http-nio-8081-exec-1
自旋扣减库存成功http-nio-8081-exec-4
自旋开始http-nio-8081-exec-1
自旋扣减库存开始http-nio-8081-exec-1
自旋扣减库存成功http-nio-8081-exec-1

看上去好像没有什么问题 

场景1.在整个代码块中发生异常了,就比如第一个setNx的时候超时了,可能有很多原因比如大的bigkey、比如很多很多的请求突然打到web,那么这个时候就会发生直接去finally ,释放了锁。那么在后续购买商品的请求访问时,锁失效 !

解决思路:使用线程的value进行比较,只有是本线程的加锁的才能释放。

那么这个时候又有一个问题,假设这个线程因为操作库存某些问题导致死了,那程序不就也死了?

设置了redis的超时时间,假设这个商品不能扣减库存了,那也就这10秒不能操作,互联网公司的业务完全能接受。

 

        String threadId = String.valueOf(Thread.currentThread().getId());
        try {
            // 获取库存数量 40
            Boolean product = redisTemplate.opsForValue().setIfAbsent("product-test001", threadId, 11, TimeUnit.SECONDS);
            if (null == product) {
                // 自旋
                while (true) {
                    System.out.println("自旋开始" + Thread.currentThread().getName());
                    Thread.sleep(4000L);
                    // 加锁成功
                    if (null != redisTemplate.opsForValue().setIfAbsent("product-test001",threadId, 11, TimeUnit.SECONDS)) {
                        // 扣减库存
                        System.out.println("自旋扣减库存开始" + Thread.currentThread().getName());
                        Thread.sleep(10000L);
                        System.out.println("自旋扣减库存成功" + Thread.currentThread().getName());
                        break;
                    }
                }
            }
            // 扣减库存 模拟扣减库存
            Thread.sleep(10000L);
        } finally {
            if(threadId.equals(redisTemplate.opsForValue().get("product-test001"))){
                // 防止异常情况 必须释放锁
                redisTemplate.delete("product-test001");
            }
        }

 

 

因为这款商品是爆品所以导致一旦开启会有超高并发的可能存在,这个时候就会对redis的压力非常大,怎么设计?

使用分段锁思想,比如将product_test1分成product_test1_001,product_test1_002,...,product_test1_100 根据某种规则让他坐落在集群的不同的槽位中 ,假设有10个集群节点那么,16384/10 尽量坐落在不同的节点,可以大幅度提高redis的吞吐量。

### 分布式锁设计要点 设计一个高性能且可靠的分布式锁机制,需要综合考虑性能、可靠性、实现复杂度等多个维度。以下是一些关键设计要点: #### 1. **使用 Redis 的原子操作实现锁** Redis 提供了 `SETNX`(Set if Not eXists)命令,可以用于实现分布式锁。该命令可以确保在多个客户端并发请求时,只有一个客户端能够成功设置锁。 ```java public String getWithRedisLock(Jedis jedis, String key) { String lockKey = "lock:" + key; try { if (jedis.setnx(lockKey, "locked") == 1) { jedis.expire(lockKey, 10); // 设置锁的过期时间 String data = fetchDataFromDatabase(key); jedis.setex(key, 60, data); // 更新缓存 return data; } else { Thread.sleep(100); return jedis.get(key); } } catch (Exception e) { return "Error"; } finally { jedis.del(lockKey); } } ``` 在实现中,需要设置锁的过期时间,防止锁被永久占用,导致死锁[^1]。 #### 2. **RedLock 算法提升可靠性** RedLock 是一种基于多个独立 Redis 节点的分布式锁实现算法。它通过在多个节点上同时获取锁,提高锁的可靠性。然而,RedLock 的实现复杂度较高,性能开销也较大,适用于对可靠性要求极高的场景。 #### 3. **锁的续租机制** 在某些场景中,锁的持有时间可能超过预期,为了避免锁过期,可以实现锁的续租机制。即在锁的有效期内,定期延长锁的过期时间,确保任务完成前锁不会被释放。 #### 4. **避免死锁和资源争用** 为了防止死锁,锁的获取和释放必须是幂等的。在实现中,建议使用 `Lua` 脚本确保锁的获取和释放操作的原子性。此外,锁的持有时间应尽可能短,以减少资源争用带来的性能瓶颈。 ```lua -- Lua 脚本实现原子锁释放 if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end ``` #### 5. **性能优化策略** 为了提升分布式锁的性能,可以采用以下策略: - **批量操作**:将多个锁请求合并为一个请求,减少网络往返次数。 - **异步释放锁**:在锁的持有者完成任务后,异步释放锁,避免阻塞主线程。 - **本地缓存锁信息**:在客户端本地缓存锁的状态,减少对 Redis 的访问频率。 #### 6. **监控与日志** 分布式锁的实现应包含完善的监控和日志记录机制,确保在发生异常时能够快速定位问题。例如,记录锁的获取和释放时间、锁的持有者信息等。 --- ### 性能与可靠性的权衡 在设计分布式锁时,需要权衡性能和可靠性。对于大多数高并发场景,使用 `SETNX` 和 `EXPIRE` 的组合已经足够满足需求,且性能较好。而在对可靠性要求极高的场景中,可以采用 RedLock 算法,尽管其性能开销较大。 此外,分布式锁的实现应尽量避免复杂的逻辑,保持简洁,以减少出错的可能性。同时,锁的持有时间应尽可能短,以减少对系统的资源占用。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值