用redis实现分布式锁,解决高并发时的线程安全问题

本文通过实例代码展示了如何利用Redis实现分布式锁解决多线程并发时的线程安全问题,特别是在抢购场景中避免票数混乱。讨论了volatile关键字的作用,以及在分布式锁中设置超时时间以防止死锁的重要性。最后提到了在Spring中使用Redis可能出现的‘ERR EXEC without MULTI’错误及其原因。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

实际工作中,经常会遇到多线程并发时的类似抢购的功能,本篇描述一个简单的redis分布式锁实现的多线程抢票功能。

直接上代码。首先按照慣例,給出一個错误的示范

我们可以看看,当20个线程一起来抢10张票的时候,会发生什么事。

package com.tiger.utils;

public class TestMutilThread {

	// 总票量
	public static int count = 10;

	public static void main(String[] args) {
		statrtMulti();
	}

	public static void statrtMulti() {
		for (int i = 1; i <= 20; i++) {
			TicketRunnable tickrunner = new TicketRunnable();
			Thread thread = new Thread(tickrunner, "Thread No: " + i);
			thread.start();
		}

	}

	public static class TicketRunnable implements Runnable {

		@Override
		public void run() {
			System.out.println(Thread.currentThread().getName() + "  start "
					+ count);
			// TODO Auto-generated method stub
			// logger.info(Thread.currentThread().getName()
			// + "  really  start" + count);
			if (count <= 0) {
				System.out.println(Thread.currentThread().getName()
						+ "  ticket sold out ! No tickets remained!" + count);
				return;
			} else {
				count = count - 1;
				System.out.println(Thread.currentThread().getName()
						+ " bought a ticket,now remaining :" + (count));
			}
		}
	}
}

测试结果,从结果可以看到,票数在不同的线程中已经出现混乱。

Thread No: 2  start 10
Thread No: 6  start 10
Thread No: 4  start 10
Thread No: 5  start 10
Thread No: 3  start 10
Thread No: 9  start 6
Thread No: 1  start 10
Thread No: 1 bought a ticket,now remaining :3
Thread No: 9 bought a ticket,now remaining :4
Thread No: 3 bought a ticket,now remaining :5
Thread No: 12  start 3
Thread No: 5 bought a ticket,now remaining :6
Thread No: 4 bought a ticket,now remaining :7
Thread No: 8  start 7
Thread No: 7  start 8
Thread No: 12 bought a ticket,now remaining :1
Thread No: 14  start 0
Thread No: 6 bought a ticket,now remaining :8
Thread No: 16  start 0
Thread No: 2 bought a ticket,now remaining :9
Thread No: 16  ticket sold out ! No tickets remained!0
Thread No: 14  ticket sold ou
<think>嗯,用户想了解如何使用Redis实现分布式锁解决商品超卖的问题。首先,我得回忆一下Redis分布式锁的基本概念和相关命令。记得SETNX和EXPIRE是关键,不过现在可能更推荐用SET命令结合NX和PX选项,这样可以原子性地设置键和过期间,避免客户端崩溃导致锁无法释放的情况。 然后,用户提到商品超卖,这通常发生在高并发下,多个请求同检查库存,都认为还有库存,然后同扣减,导致实际库存不足。所以需要分布式锁来保证同一间只有一个请求可以处理库存扣减。 接下来,我需要考虑如何设计这个锁的获取和释放流程。获取锁的候,要生成一个唯一的value,比如UUID,这样在释放锁的候可以验证是否是自己持有的锁,防止误删其他客户端的锁。释放锁的候,需要原子性地执行判断和删除操作,可以用Lua脚本来实现,因为Lua脚本在Redis中是原子执行的。 另外,锁的过期间设置也很重要。间不能太短,否则业务还没执行完锁就自动释放了,会导致其他客户端获取锁,同样出现超卖。也不能太长,否则系统故障恢复间变长。可能需要根据业务处理的间来评估,或者使用看门狗机制自动续期,但这样会增加复杂性。可能用户需要一个简单可靠的方案,所以暂先设置合理的固定过期间。 然后,用户可能需要一个具体的代码示例,比如用Java或者Python。不过用户之前给出的示例是Python,所以可能倾向于Python的代码。不过要注意连接Redis的库,比如使用redis-py,以及如何实现原子操作。 还要考虑异常情况,比如在扣减库存后,确保释放锁,避免死锁。所以需要try-finally块来保证锁的释放。此外,网络延迟或Redis故障的情况,可能需要重试机制,或者使用RedLock算法来跨多个Redis实例获取锁,提高容错性。但RedLock实现起来更复杂,用户可能只需要单实例的基础方案,所以先提供基础方案,再提到高级方案作为优化方向。 最后,需要总结最佳实践,包括原子性操作、唯一标识、合理超、容错处理,以及可能的扩展方案如RedLock。同,生成相关问题,帮助用户进一步学习,比如锁失效的情况、RedLock的实现、与其他分布式锁的比较,以及库存扣减的其他方案如预扣库存。</think>### 如何使用Redis实现分布式锁解决商品超卖问题 #### 1. 问题背景 商品超卖指在高并发场景下,库存数量被多次扣减导致实际库存为负数。例如:商品库存仅剩1件,多个用户同请求购买,若未加锁可能全部成功下单[^1]。 #### 2. 实现原理 使用Redis的`SET key value NX PX timeout`命令实现分布式锁: - `NX`:仅当键不存在设置值 - `PX`:设置键的过期间(毫秒) - 唯一标识(如UUID)确保锁归属 - 原子性操作避免锁失效问题[^2] #### 3. 具体实现步骤 **3.1 获取锁** ```python import redis import uuid redis_client = redis.StrictRedis() def acquire_lock(lock_key, timeout=3000): identifier = str(uuid.uuid4()) # 原子性设置锁(NX + PX) success = redis_client.set(lock_key, identifier, nx=True, px=timeout) return identifier if success else None ``` **3.2 释放锁(Lua脚本保证原子性)** ```lua if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end ``` ```python def release_lock(lock_key, identifier): script = """ if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end""" return redis_client.eval(script, 1, lock_key, identifier) ``` **3.3 库存扣减流程** ```python def deduct_stock(product_id): lock_key = f"lock:{product_id}" identifier = acquire_lock(lock_key) if not identifier: return "系统繁忙,请重试" try: # 查询库存 stock = redis_client.get(f"stock:{product_id}") if int(stock) <= 0: return "库存不足" # 扣减库存 redis_client.decr(f"stock:{product_id}") return "购买成功" finally: release_lock(lock_key, identifier) ``` #### 4. 最佳实践 1. **原子性操作**:使用`SET NX PX`而非分开执行`SETNX`和`EXPIRE`[^2] 2. **唯一标识**:防止误删其他客户端的锁 3. **合理超**:根据业务复杂度设置3000-10000ms 4. **容错处理**:配合Redis Sentinel/Cluster实现高可用[^3] 5. **重试机制**:获取锁失败后添加随机退避重试 #### 5. 扩展优化方案 - **RedLock算法**:跨多个独立Redis实例实现强一致性锁 - **自动续期**:通过守护线程延长锁有效期(需处理复杂性) - **库存预扣**:结合消息队列实现异步库存处理 $$ \text{锁安全性公式}:锁持有间 < 锁超间 < 业务最大容忍间 $$
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值