Redis 基础 - 优惠券秒杀《分布式锁(初级)》

参考

Redis基础 - 基本类型及常用命令
Redis基础 - Java客户端
Redis 基础 - 短信验证码登录
Redis 基础 - 用Redis查询商户信息
Redis 基础 - 优惠券秒杀《非集群》

synchronized在集群上使用时的问题

synchronized只能够保证一个JVM内部的多个线程之间的互斥,而无法在集群之间互斥,要想解决这个问题必须要使用分布式锁。分布式锁是满足分布式系统或集群模式下“多进程可见”并且能“互斥”的锁。

为何需要分布式锁

比如有两个JVM,JVM1和JVM2,synchronized就是利用JVM内部的锁监视器来控制线程,在JVM的内部因为只有一个锁监视器,所以只会有一个线程获取锁,因此可以实现线程之间的互斥。但当有多个JVM的时候,就会有多个锁监视器,那么就会有多个线程获取到锁,这样的话无法实现多JVM之间的互斥。要想解决这个问题,肯定不能使用JVM内部的锁监视器了,必须让多个JVM去使用同一个锁监视器。所以他一定是在JVM外部的,多JVM进程都可以看到的锁监视器,这时候无论是JVM内部的还是多JVM的线程,都应该去找外部的锁监视器获取锁,这样也就会只有一个线程获取锁,就能实现多进程之间的互斥了。

业务场景

比如JVM1里有线程1在执行业务,她就会去获取互斥锁,她获取锁就会去找外部的锁监视器,一旦获取成功,就在锁监视器里记录当前获取锁的是线程1。此时如果其他线程也来获取锁,比如JVM2内部的线程3,她也会去外部的锁监视器试图获取锁,但因为锁监视器已经有线程1使用着,所以线程3获取一定会失败,失败之后她就会去等待锁释放。一方面,假如JVM1的线程1执行着:先查询订单,若没有就插入新订单,由于她是第一个来的,所以没有订单,所以插入新订单,执行完后就会释放锁。等线程1释放完之后,线程3拿到锁了,她也去执行一样的业务:获取锁成功,查询订单,但查询时由于线程1已经插入了,所以线程3就能查询到订单,由于已经存在,所以直接返回报错。

分布式锁的实现

分布式锁的核心是实现多进程之间互斥,而满足这一点的方式有很多,常见的有三种:

MySQL Redis zookeeper
互斥 利用民事权利本身的互斥锁机制互斥 利用setnx这样的互斥命令 利用节点的唯一性和有序性实现互斥
高可用
高性能 一般 一般
安全性 断开连接,自动释放锁 利用锁超时时间,到期释放 临时节点,断开连接自动释放

基于Redis的分布式锁

实现分布式锁时需要实现的两个基本方法:

(1)获取锁

  • 互斥:确保只能有一个线程获取锁,可以利用setnx。为了不让setnx后还没来得及设置expire时恰巧宕机,要保证setnx和expire同时成功或同时失败,所以可以用set命令,set命令有很多参数,比如set key value EX 10 NX,即设置了值还设置了EX(超时时间)参数,为10秒,还配了NX参数(互斥),没值时才设置。利用这个命令可以让NX和EX变成原子操作。
  • 非阻塞方式去获取锁:尝试一次,成功返回true,失败返回false

(2)释放锁

  • 手动释放:del 锁的key
  • 超时释放:获取锁时添加一个超时时间,即expire。避免服务宕机而出现的死锁。

基于Redis实现分布式锁(初级)

ILock.java

public interface ILock{
   
	// 尝试获取锁(因为这里用的是非阻塞获取)
	/*
	参数:锁持有的超时时间,过期后自动释放
	返回值:true 获取锁成功; false 获取锁失败
	*/
	boolean tryLock(long timeoutSec);

	// 释放锁
	void unlock();
}

SimpleRedisLock.java

public class SimpleRedisLock implements ILock {
   

	public String name;// 业务名,不同的业务的锁的key要不同(实际上就是key名)。

	private StringRedisTemplate stringRedisTemplate;

	// 通过构造函数传值
	public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
   
		this.name = name;
		this.stringRedisTemplate = stringRedisTemplate;
	}

	private static final String KEY_PREFIX = "lock:";//key的前缀

	@Override
	public boolean tryLock(long timeoutSec) {
   
		// 获取当前线程的id(线程的标识)
		long threadId = Thread.currentThread().getId();

		// 获取锁,setIfAbsent 如果不存在则执行。value最好加个标识,哪个线程拿到的锁。
		Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId + "", timeoutSec, TimeUnit.SECONDS);

		// 返回值是boolean基本类型,而这里是Boolean,所以直接返回的话会自动拆箱,如果值是null,拆箱时可能会报空指针错误,所以返回时可以这么返回,防止自动拆箱
		return Boolean.TURE.equals(success);// Boolean.TURE是常亮,所以与success比较后,一样就返回true,不一样(或是null时)就返回false。
	}

	@Override
	public void unlock() {
   
		stringRedisTemplate.delete(KEY_PREFIX + name);
	}
}
代码示例1:用一人一单的例子来测试

VoucherOrderServiceImpl.java

@Resource
prviate ISeckillVoucherService iSeckillVoucherService;

@Resource
prviate RedisIdWorker redisIdWorker;

@Resource
prviate StringRedisTemplate stringRedisTemplate;

@Override
// @Transactional 由于这个方法里只有查的,所以不需要这个。
public Result seckillVoucher(Long voucherId) {
   

	// 1,根
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
您好!对于分布式优惠券系统的结合,我可以给您一些简单的解释和示例。 首先,分布式是一种常用的解决并发访问共享资源的方法。在Redis中,可以使用Redis的setnx命令(或者RedLock算法等其他方式)来实现分布式。下面是一个简单的示例代码,演示了如何使用Redis实现一个分布式: ```python import redis def acquire_lock(redis_conn, lock_key, expire_time): # 尝试获取 lock_acquired = redis_conn.setnx(lock_key, "locked") if lock_acquired: # 设置的过期时间 redis_conn.expire(lock_key, expire_time) return lock_acquired def release_lock(redis_conn, lock_key): # 释放 redis_conn.delete(lock_key) # 使用示例 redis_conn = redis.Redis(host='localhost', port=6379, db=0) lock_key = "coupon_lock" expire_time = 60 # 的过期时间为60秒 if acquire_lock(redis_conn, lock_key, expire_time): try: # 在获得的情况下执行领取优惠券的操作 # ... print("成功领取优惠券!") finally: release_lock(redis_conn, lock_key) else: print("无法领取优惠券,其他用户正在操作,请稍后再试。") ``` 在上述示例中,我们使用了Redis作为分布式的存储介质,并通过`acquire_lock`函数尝试获取。如果成功获取到,则执行领取优惠券的操作;否则,表示其他用户正在操作,需要稍后重试。最后,使用`release_lock`函数来释放。 这只是一个简单的示例,实际应用中可能还需要考虑更多的细节和异常情况。希望对您有所帮助!如果您还有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值