前言
秒杀是电商平台一个非常重要的功能,在这种高并发的情况下,怎么控制不超库存呢?那就是用锁。但是我们现在都是分布式系统,也就会诞生另外的一个问题。怎么加一个全局的锁,传统的 synchronize 或 lock 锁不行,因为会有多台实例,那么应该加什么锁呢?今天跟着刘雪松老师一起学习一下分布式锁。
Redis 锁原理
借助 setnx 和 expire 两个redis命令完成
setnx:当key不存在,将key设置为value ,存在不做任何操作,返回 0
setnx [key] [value]
setnx lock "123"
expire:设置key 的过期时间
expire [key] [time]
expire lock 10 单位秒
加锁两个条件
- 能加锁
- 形成锁的互斥
问题:setnx是两个命令,如果执行了setnx,expire失败 ,就会造成锁无法释放
解决:
- 使用set key value [Ex seconds][Px milliseconds] [NX|XX]
- 使用 lua 脚本执行,两个命令是个原子操作
setnx lock "123" EX 100 NX
EX seconds 设置失效时长,单位秒
PX milliseconds 设置失效时长,单位毫秒
NX key不存在时设置value
XX key存在时设置value
Jedis 分布式锁实现
Jedis就是集成了redis的一些命令操作,封装了redis的java客户端。提供了连接池管理。'
加锁 set
jedis.set = set key value [Ex seconds][Px milliseconds] [NX|XX]
解锁 del
jedis.del(key)
锁过期问题:
线程一持有锁超时 20秒,执行10秒的时候,遭遇fullgGC ,STW导致任务挂起,超过20秒,线程二开始执行,fullGC结束,线程一继续执行,导致数据混乱
1、增加乐观锁,线程一的version 为1 ,线程二的version 2 ,当线程一回来的时候,发现版本不一致,不予执行
2、看门狗,是一个后台线程,会定期检测是否线程一是否还持有锁,如果还持有锁,延长时间
(只对dislock.lock 有效,显式指定时间的看门狗无用 )
redisson分布式锁
Redisson
是基于netty的redis 客户端。不但能操作原生的Redis 数据结构,还能为使用者提供了一系列具有分布式特性的常用工具类,实现了分布式锁。
Redisson 分布式锁
RLock 分布式锁和JUC的lock方法相似。RLock接口继承了Lock接口
RLock disLock = client.getLock("DISLOCK");
disLock.lock(); // 默认 30s
=======================
disLock.tryLock(2000, 150000, TimeUnit.MILLISECONDS);
// 2000 等待时间
// 150000 持有时间
// TimeUnit..MILLISECONDS 单位
// 可重入锁
disLock.tryLock(2000, 15000, TimeUnit.MILLISCONDS);
// 释放锁
disLock.unlock();
分段锁
可以使用分段的方式,以空间换时间,为了达到每秒600个订单,将锁分为 120 段,一秒操作五次,120段就是 600。
首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。
ConcurrentHashMap 用的就是分段锁。容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率。
行而不辍,未来可期。 --《荀子·修身》
参考文档:刘雪松老师的PPT