业务场景
在实际开发过程中,会遇到下单操作,这里涉及到库存问题。
无锁情况下下单
假如现在有一个场景,系统中有一个用户下单接口,但在用户下订单前一定要去检查库存,确保库存足够了才给用户下单,假如目前是单台服务器。
由于系统有一定的并发,所以会预先将商品的库存保存在 Redis
中,用户下单的时候会更新 Redis
的库存。
单进程jvm加锁
现在有两个线程用户
访问,线程1【用户1】和线程2【用户2】
,假如在不加锁的情况下,当某个时刻,Redis和mysql的库存为1。
线程1执行到第3步后,还没执行到第四步,但线程2已执行到第2步。此时mysql的数据库为0,Redis
的数据库为线程2可以执行第3步,执行完后,mysql数据库的库存为-1。
因而,这里就出现了并发带来第二超卖问题
,针对这个问题,线程1需要锁定2、3、4操作,然后线程2才能执行第2步操作。
这里可以使用synchronized和ReentrantLock加锁
,如图所示:
分布式无锁
在项目的初期很好,但随着用户量的增加,一台服务器的Tomcat无法支撑起很大的并发量,毕竟每个Tomcat最多并发量是500
,也很容易出现单点故障。
于是,需要多台服务器,来支撑较高的并发量,于是,使用nginx做负载均衡,采用轮训的机制,并采用epoll进行多路复用, 架构如下:
用户1和用户2被负载到服务器1,在同一个JVM
当中,lock
加锁有效。同理,用户3和用户4的lock也成立。
假如Redis和mysql存储的库存都是1,用户1执行完第3步后,mysql数据库库存为0。此时,用户3才执行完第二步,从Redis中取出的数据为1,再执行第3步,此时mysql的数据库库存为-1,这就减了两次,造成了超卖的脏数据,因而,这里需要加分布式锁,锁住当前库存。
分布式锁的思路:在整个系统提供一个全局、唯一的获取锁的“变量”,然后每个系统在需要加锁时,都去问这个“变量”拿到一把锁,这样不同的系统拿到的就可以认为是同一把锁。
分布式锁
能够实现分布式锁的两种途径:
-
基于 Redis 实现分布式锁
-
基于zookeeper实现分布式锁
redis分布式锁
基于 Redis原生 API 来实现,容易造成锁失效。
假如线程1设置超时时间为30s,但在30秒之内还未完成业务处理,线程2便获取了锁,我们需要额外去维护这个过期时间。因而,我们常使用开源框架Redission
。
Redisson
所有指令都通过 Lua 原子性的脚本执行,它提供简单易用的API,如下加锁代码:
Config config = new Config();
config.useClusterServers()
.addNodeAddress("redis://192.168.31.101:7001")
.addNodeAddress("redis://192.168.31.101:7002")
.addNodeAddress("redis://192.168.31.101:7003")
.addNodeAddress("redis://192.168.31.102:7001")
.addNodeAddress("redis://192.168.31.102:7002")
.addNodeAddress("redis://192.168.31.102:7003");
RedissonClient redisson = Redisson.create(config);
RLock lock = redisson.getLock("anyLock");
lock.lock();
lock.unlock();
Redisson
设置一个Key 的默认过期时间为30s,它会在你获取锁后,使用 Watchdog
【线程池,定时执行】,也就是看门狗,每隔10s帮你把Key的超时时间设为 30s。
基于zookeeper实现分布式锁
Zookeeper是分布式协调组件,它有有序节点、临时节点、事件监听的特性,因而,可以根据这些特性创建分布式锁:
-
使用 zookeeper的临时节点和有序节点,每个线程获取锁就是在zookeeper创建一个临时有序的节点,比如在 /lock/ 目录下。
-
创建节点成功后,获取 /lock目录下的所有临时节点,再判断当前线程创建的节点是否是所有的节点的序号最小的节点。
-
如果当前线程创建的节点是所有节点序号最小的节点,则认为获取锁成功。
-
如果当前线程创建的节点不是所有节点序号最小的节点,则对节点序号的前一个节点添加一个事件监听。比如当前线程获取到的节点序号为 /lock/003,然后所有的节点列表为[/lock/001,/lock/002,/lock/003],则对 /lock/002这个节点添加一事件监听器.
-
如果锁释放了,会唤醒下一个序号的节点,然后重新执行第 3 步,判断是否自己的节点序号是最小。比如 /lock/001释放了,/lock/002监听到时间,此时节点集合为[/lock/002,/lock/003],则 /lock/002为最小序号节点,获取到锁。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/99246.html