Redis分布式锁

一、全局ID生成器

1.1 概念

全局ID生成器,是一种在分布式系统下用来生成全局唯一ID的工具。具有以下特点:

(1)唯一性;(2)高可用;(3)高性能;(4)递增性;(5)安全性。

1.2 实现

1.2.1 UUID

返回结果是为字符串,不是自增。使用较少。

1.2.2 Redis自增

1.2.3 snowflake算法

1.2.4 数据库自增

二、集群下的线程并发安全问题

通过加锁机制(synchronize)可以解决单机情况下的线程安全问题,但是在集群模式下就不行了。

修改nginx的配置文件nginx.conf:

说明:上图中的配置说明"/api"请求会转到"/backend"请求,且配置了两个服务(172.0.0.1:8081与172.0.0.1:8082)实现负载均衡,即实现集群模式。

使用锁下单(确保一个用户只能下一单):

但是若同一用户发了两次请求,一次请求发送到8081服务,另一次发送到8082服务,可以发现两次请求都获取到了锁,且都完成了下单,导致不符合业务要求。

原因是锁监视器存在机器本地的JVM中,而一台机器有自己的JVM,锁机制在集群模式下失效。该问题用分布式锁来实现。

三、分布式锁

分布式锁:满足分布式模式或集群模式下多进程可见并且互斥的锁。

3.1 三种实现方法

3.2 基于Redis的分布式锁

3.2.1 思路

注意:需要设置key的生存时间,防止服务宕机导致锁未释放。

3.2.2 代码实现

接口:

实现类:


使用锁:

3.2.3 锁误删的问题

3.2.3.1 产生原因

以上图图示说明,线程1获取了锁,但是执行时线程阻塞了,导致锁超时释放。而此时线程2去获取了锁。在线程2执行过程中,线程1执行完并去释放锁,实际上释放的是线程2设置的锁,即导致了锁误删。

3.2.3.2 流程修改方法

原流程:

修改后流程:

3.2.3.3 代码实现


3.2.4分布式锁的原子性问题

3.2.3中的锁误删问题的解决方法还有一个问题是,当线程1要删锁时,判断完锁是自己后,阻塞了一段时间,锁超时释放。之后线程2获取了锁,线程1阻塞结束继续执行,进行删锁,又导致了锁误删。因此,判断锁是否是自己的与删除锁的操作需要具备原子性。

3.2.5 Lua脚本解决多条命令原子性问题

Redis提供了Lua脚本(一种编程语言),在一个脚本中编写多条Redis命令,确保多条命令执行时的原子性。

Lua变成语言的基本语法的参考网站:

bash 复制代码
https://www.runoob.com/lua/lua-tutorial.html

执行脚本指令:

eval

例如:

Lua脚本编写如下:

java的执行redis的lua脚本的接口:

在项目中新建脚本文件:

加载脚本(提前加载好):

3.3 基于Redis分布式锁的问题

注意:上述四个问题出现的概率极低,不考虑这些问题的锁的性能也够用了。

四、Redisson

4.1 Redisson介绍

Redisson是封装好的实现Redis分布式锁的依赖,可以直接使用(意思是前面实现锁的代码其实都不用自己写)。使用方法如下。

4.1.1 引入依赖

4.1.2 配置Redisson客户端

有两个配置方式,一种是使用java配置进行配置;一种是使用配置文件与springboot整合实现,并且官方还提供一种springboot start。建议使用第一种方法。

4.1.3 使用Redisson的分布式锁

例如:

4.2 Redisson可重入锁原理

之前介绍的自定义锁未实现可重入机制。Redisson可实现。

例如:

实现机制是当获取锁时判断获取锁的是否是当前线程。

4.3 Redisson分布式锁锁重试问题

4.4 Redisson分布式锁主从一致性问题

4.4.1 问题产生原因

首先,redis的主节点获取锁后,开始向从节点同步信息,但就在此时主节点发生了故障即同步尚未完成。Redis有哨兵监控集群状态,当它发现主节点宕机后,从从节点种选择一个作为主节点。Java应用去访问新的主节点时发现锁已丢失,即锁失效了,此时再有其他线程来获取锁还是可以成功的,这就会产生线程并发安全的问题即主从一致性导致的锁失效问题。

4.4.2 Redisson解决方案

Redisson将所有的节点都看成独立的redis节点,相互之间没有主从关系。此时获取锁的方式就变了,之前是找到master节点然后获取锁;但现在必须依次向多个redis节点都去获取锁,这些都保存了锁标识才算获取锁成功(即使用联锁)。

也可以保留主从节点关系,只是有多个主节点:

不具备主从关系的代码实现:

三个独立的redisclient,

创建联锁,

4.5 使用JVM阻塞线程

未使用redis缓存的秒杀下单流程如下。

如上图,对流程进行优化。"判断秒杀库存"和"校验一人一单"由redis完成,再通过消息队列完成下单操作。

具体实现:

代码实现:

新增秒杀优惠券的同时,将优惠券信息保存到redis中,

基于lua脚本,实现秒杀库存、一人一单,决定用户是否抢购成功,

如果抢购成功,将优惠券id和用户id封装后存入阻塞队列,

开启线程任务,不断从阻塞队列中获取信息,实现异步下单功能,

说明:上图中的线程的任务是从阻塞队列中不断地取出订单。


五、消息队列

4.5中使用JVM线程可能造成内存不足的情况,且存在数据安全问题(重启或宕机后阻塞队列中的信息全部丢失)。

5.1 基于List结构模拟消息队列


使用BRPOP指令。

优缺点:

5.2 基于PubSub的消息队列



5.3 基于Steam的消息队列

Stream是Redis 5.0引入的一种新数据类型(即可实现持久化),可以实现一个功能完善的消息队列。

相关推荐
猿小喵35 分钟前
MySQL四种隔离级别
数据库·mysql
Y编程小白41 分钟前
Redis可视化工具--RedisDesktopManager的安装
数据库·redis·缓存
洪小帅1 小时前
Django 的 `Meta` 类和外键的使用
数据库·python·django·sqlite
祁思妙想2 小时前
【LeetCode】--- MySQL刷题集合
数据库·mysql
V+zmm101342 小时前
教育培训微信小程序ssm+论文源码调试讲解
java·数据库·微信小程序·小程序·毕业设计
m0_748248022 小时前
【MySQL】C# 连接MySQL
数据库·mysql·c#
东软吴彦祖3 小时前
包安装利用 LNMP 实现 phpMyAdmin 的负载均衡并利用Redis实现会话保持nginx
linux·redis·mysql·nginx·缓存·负载均衡
想做富婆3 小时前
大数据,Hadoop,HDFS的简单介绍
大数据·hadoop·分布式
霍格沃兹测试开发学社测试人社区4 小时前
软件测试丨消息管道(Kafka)测试体系
软件测试·分布式·测试开发·kafka