基于Redis的分布式锁、Redis的Lua脚本、Redisson、Redis优化秒杀

分布式锁

  • 定义:满足分布式系统或集群模式下多进程可见并且互斥的锁
  • 满足条件:
    多进程可见、互斥、高可用、高性能、安全性、
  • 实现:分布式锁的核心是多进程之间互斥,满足这点的方式有以下三种
    在这里插入图片描述
  • 实现分布式锁时需要实现的两个基本方法:
    • 获取锁:
      • 互斥:确保只有一个线程获取锁
      • 非阻塞:尝试一次,成功返回true,失败返回false
#添加锁,利用setnx的互斥特性
setnx lock thread1

#添加过期时间,避免服务宕机引起的死锁
expire lock 10

#添加锁,nx是互斥,ex是设置超时时间
set lock thread1 nx ex 10

fsda

  • 释放锁:
    • 手动释放
    • 超时释放:获取锁时添加一个超时时间
#释放锁,删除即可
del key
  • 分布式锁实现要满足:
    • 在释放锁时存入线程标示(可以使用UUID表示)
    • 在释放锁时先获取锁中的线程标示,判断是否跟当前线程标示一致
      • 如果一致则释放锁
      • 如果不一致则不释放锁

Redis的Lua脚本

  • 在一个脚本中编写多条Redis命令,确保多条命令执行时的原子性。Lua是一种编程语言。Lua 教程 | 菜鸟教程 (runoob.com) 参考该网站
Redis提供的调用函数
#执行Redis命令
 redis.call('命令名称','key','其他参数',……)
  • 这里重点介绍Redis提供的调用函数,语法如下:
redis.call('命令名称', 'key', '其它参数', ...)

例如,我们要执行set name jack,则脚本是这样:

#执行 set name jack
redis.call('set', 'name', 'jack')

例如,我们要先执行set name Rose,再执行get name,则脚本如下:

#先执行 set name jack
redis.call('set', 'name', 'Rose')
#再执行 get name
local name = redis.call('get', 'name')
#返回
return name

写好脚本以后,需要用Redis命令来调用脚本,调用脚本的常见命令如下:在这里插入图片描述

例如,我们要执行 redis.call(‘set’, ‘name’, ‘jack’) 这个脚本,语法如下:在这里插入图片描述

如果脚本中的key、value不想写死,可以作为参数传递。key类型参数会放入KEYS数组,其它参数会放入ARGV数组,在脚本中可以从KEYS和ARGV数组获取这些参数:在这里插入图片描述

接下来我们来回一下我们释放锁的逻辑:
释放锁的业务流程是这样的
1、获取锁中的线程标示
2、判断是否与指定的标示(当前线程标示)一致
3、如果一致则释放锁(删除)
4、如果不一致则什么都不做
如果用Lua脚本来表示则是这样的:
最终我们操作redis的拿锁比锁删锁的lua脚本就会变成这样

-- 获取锁中的线程标示 get key
local id = redis.call('get', KEYS[1])
-- 比较线程标示与锁中的标示是否一致
if(id == ARGV[1]) then
    -- 释放锁 del key
    return redis.call('del',KEYS[1])
end
-- 不一致,直接返回
return 0

-- 简化
-- 这里的 KEYS[1] 就是锁的key,这里的ARGV[1] 就是当前线程标示
-- 获取锁中的标示,判断是否与当前线程标示一致
if (redis.call('GET', KEYS[1]) == ARGV[1]) then
  -- 一致,则删除锁
  return redis.call('DEL', KEYS[1])
end
-- 不一致,则直接返回
return 0
  • 基于Redis的分布式锁实现思路
    • 利用set nx ex 获取锁,并设置过期时间,保存线程标示
    • 释放锁时先判断线程标示是否与自己一致,一致则删除锁
  • 特性:
    • 利用set nx 满足互斥性
    • 利用set ex 保证故障时锁依然能释放,避免死锁,提高安全性
    • 利用Redis集群保证高可用和高并发特征

基于setnx实现的分布式锁存在下面的问题:

  1. 重入问题:重入问题是指 获得锁的线程可以再次进入到相同的锁的代码块中,可重入锁的意义在于防止死锁,比如HashTable这样的代码中,他的方法都是使用synchronized修饰的,假如他在一个方法内,调用另一个方法,那么此时如果是不可重入的,不就死锁了吗?所以可重入锁他的主要意义是防止死锁,我们的synchronized和Lock锁都是可重入的。
  2. 不可重试:是指目前的分布式只能尝试一次,我们认为合理的情况是:当线程在获得锁失败后,他应该能再次尝试获得锁。
  3. 超时释放:我们在加锁时增加了过期时间,这样的我们可以防止死锁,但是如果卡顿的时间超长,虽然我们采用了lua表达式防止删锁的时候,误删别人的锁,但是毕竟没有锁住,有安全隐患
  4. 主从一致性: 如果Redis提供了主从集群,当我们向集群写数据时,主机需要异步的将数据同步给从机,而万一在同步过去之前,主机宕机了,就会出现死锁题。
    在这里插入图片描述

Redisson

  1. 是一个在Redis基础上实现的java驻内存数据网格。不仅提供了一系列的分布式的java常用对象,还提供了许多分布式服务,其中就包含了各种分布式锁的实现。
  2. Redisson用法
1.引入依赖
2.配置Bean客户端
3.使用Redisson分布式锁

Redisson分布式锁原理:

  • 可重入:利用hash结构记录线程id和重入次数
  • 可重试:利用信号量和PubSub功能实现等待、唤醒、获取锁失败的重试机制
  • 超时续约:利用watchDog,每隔一段时间(releaseTime/3),重置超时时间。

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

  1. 利用redisson锁的MutiLock,将多个服务器实现主从一致
    总结:
    • 不可重入Redis分布式锁
      • 原理:利用setnx的互斥性,利用ex避免死锁;释放锁时判断线程标示
      • 缺陷:不可重入、无法重试、锁超时失效
    • 可重入的Redis分布式锁:
      • 原理:利用hash结构,记录线程标示和重入次数;利用watchDog延续锁时间;利用信号量控制锁重试等待
      • 缺陷:redis宕机引起锁失效问题
  • Redisson的multiLock:
    • 原理:多个独立的Redis节点,必须在所有节点都获取重入锁,才算获取锁成功
    • 缺点:运维成本高、实现复杂

Redis优化秒杀

  1. 思路:
    a. 先利用Redis完成库存余量、一人一单的判断,完成抢单业务
    b. 再将下单业务放入阻塞队列,利用独立线程异步下单
  2. 基于阻塞队列的异步秒杀存在哪些问题?
    a. 内存限制问题
    b. 数据安全问题
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bullet Java

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值