2023-06-19:讲一讲Redis分布式锁的实现?
2023-06-19:讲一讲Redis分布式锁的实现?
答案2023-06-19:
Redis分布式锁最简单的实现
要实现分布式锁,确实需要使用具备互斥性的Redis操作。其中一种常用的方式是使用SETNX命令,该命令表示"SET if Not Exists",即只有在key不存在时才设置其值,否则不进行任何操作。通过这种方式,两个客户端进程可以执行SETNX命令来实现互斥,从而达到分布式锁的目的。
下面是一个示例:
客户端1申请加锁,加锁成功:
SETNX lock_key 1
客户端2申请加锁,由于它处于较晚的时间,加锁失败:
SETNX lock_key 1
通过这种方式,您可以使用Redis的互斥性来实现简单的分布式锁机制。

对于加锁成功的客户端,可以执行对共享资源的操作,比如修改MySQL的某一行数据或调用API请求。
操作完成后,需要及时释放锁,以便后续的请求能够访问共享资源。释放锁非常简单,只需使用DEL命令来删除相应的锁键(key)即可。
下面是释放锁的示例逻辑:
DEL lock_key
通过执行以上DEL命令,成功释放锁,以让后续的请求能够获得锁并执行操作共享资源的逻辑。
这样,通过使用SETNX命令进行加锁,然后使用DEL命令释放锁,您就可以实现基本的分布式锁机制。

但是,它存在一个很大的问题,当客户端 1 拿到锁后,如果发生下面的场景,就会造成「死锁」:
1、程序处理业务逻辑异常,没有及时释放锁。
2、进程崩溃或意外停止,无法释放锁。
在这种情况下,客户端将永远占用该锁,其他客户端将无法获取该锁。如何解决这个问题呢?
如何避免死锁?
当考虑在申请锁时为其设置一个「租期」时,可以在Redis中通过设置「过期时间」来实现。假设我们假设操作共享资源的时间不会超过10秒,在加锁时,可以给该key设置一个10秒的过期时间即可。这样做可以确保在申请锁后的一段时间内,如果锁的持有者在该时间内没有更新锁的过期时间,锁将会自动过期,从而防止锁被永久占用
SETNX lock 1 // 加锁
EXPIRE lock 10 // 10s后自动过期

这样一来,无论客户端是否异常,这个锁都可以在 10s 后被「自动释放」,其它客户端依旧可以拿到锁。
但现在还是有问题:
当前的操作是将加锁和设置过期时间作为两个独立的命令执行,存在一个问题,即可能只执行了第一条命令而第二条命令却未能及时执行,从而导致问题。例如:
SETNX 命令执行成功后,由于网络问题导致 EXPIRE 命令执行失败。
SETNX 命令执行成功后,Redis 异常宕机,导致 EXPIRE 命令没有机会执行。
SETNX 命令执行成功后,客户端异常崩溃,同样导致 EXPIRE 命令没有机会执行。
总之,这两条命令不能保证是原子操作(一起成功),就有潜在的风险导致过期时间设置失败,依旧发生「死锁」问题。
幸运的是,在 Redis 2.6.12 版本之后,Redis 扩展了 SET 命令的参数。用这一条命令就可以了:
SET lock 1 EX 10 NX

锁被别人释放怎么办?
上面的命令执行时,每个客户端在释放锁时,并没有进行严格的验证,存在释放别人锁的潜在风险。为了解决这个问题,可以在加锁时为每个客户端设置一个唯一的标识符(unique identifier),并在解锁时对比标识符来验证是否有权释放锁。
例如,可以是自己的线程 ID,也可以是一个 UUID(随机且唯一),这里我们以UUID 举例:
SET lock $uuid EX 20 NX
之后,在释放锁时,要先判断这把锁是否还归自己持有,伪代码可以这么写:
if redis.get("lock") == $uuid:
redis.del("lock")
这里释放锁使用的是 GET + DEL 两条命令,这时,又会遇到我们前面讲的原子性问题了。这里可以使用lua脚本来解决。
安全释放锁的 Lua 脚本如下:
if redis.call("GET",KEYS[1]) == ARGV[1]
then
return redis.call("DEL",KEYS[1])
else
return 0
end
好了,这样一路优化,整个的加锁、解锁的流程就更「严谨」了。
这里我们先小结一下,基于 Redis 实现的分布式锁,一个严谨的的流程如下:
1、加锁
SET lock_key $unique_id EX $expire_time NX
2、操作共享资源
3、释放锁:Lua 脚本,先 GET 判断锁是否归属自己,再DEL 释放锁
go代码实现分布式锁
package main
import (
"context"
"fmt"
"sync"
"time"
"github.com/go-redis/redis/v8"
"github.com/google/uuid"
)
const (
LockTime = 5 * time.Second
RS_DISTLOCK_NS = "tdln:"
RELEASE_LOCK_LUA = `
if redis.call('get',KEYS[1])==ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
`
)
type RedisDistLock struct {
id string
lockName string
redisClient *redis.Client
m sync.Mutex
}
func NewRedisDistLock(redisClient *redis.Client, lockName string) *RedisDistLock {
return &RedisDistLock{
lockName: lockName,
redisClient: redisClient,
}
}
func (this *RedisDistLock) Lock() {
for !this.TryLock() {
time.Sleep(100 * time.Millisecond)
}
}
func (this *RedisDistLock) TryLock() bool {
if this.id != "" {
// 处于加锁中
return false
}
this.m.Lock()
defer this.m.Unlock()
if this.id != "" {
// 处于加锁中
return false
}
ctx := context.Background()
id := uuid.New().String()
reply := this.redisClient.SetNX(ctx, RS_DISTLOCK_NS+this.lockName, id, LockTime)
if reply.Err() == nil && reply.Val() {
this.id = id
return true
}
return false
}
func (this *RedisDistLock) Unlock() {
if this.id == "" {
// 未加锁
panic("解锁失败,因为未加锁")
}
this.m.Lock()
defer this.m.Unlock()
if this.id == "" {
// 未加锁
panic("解锁失败,因为未加锁")
}
ctx := context.Background()
reply := this.redisClient.Eval(ctx, RELEASE_LOCK_LUA, []string{RS_DISTLOCK_NS + this.lockName}, this.id)
if reply.Err() != nil {
panic("释放锁失败!")
} else {
this.id = ""
}
}
func main() {
client := redis.NewClient(&redis.Options{
Addr: "172.16.11.111:64495",
})
const LOCKNAME = "百家号:福大大架构师每日一题"
lock := NewRedisDistLock(client, LOCKNAME)
lock.Lock()
fmt.Println("加锁main")
ch := make(chan struct{})
go func() {
lock := NewRedisDistLock(client, LOCKNAME)
lock.Lock()
fmt.Println("加锁go程")
lock.Unlock()
fmt.Println("解锁go程")
ch <- struct{}{}
}()
time.Sleep(time.Second * 2)
lock.Unlock()
fmt.Println("解锁main")
<-ch
}

锁过期时间不好评估怎么办?

看上面这张图,加入key的失效时间是10s,但是客户端C在拿到分布式锁之后,然后业务逻辑执行超过10s,那么问题来了,在客户端C释放锁之前,其实这把锁已经失效了,那么客户端A和客户端B都可以去拿锁,这样就已经失去了分布式锁的功能了!!!
比较简单的妥协方案是,尽量「冗余」过期时间,降低锁提前过期的概率,但是这个并不能完美解决问题,那怎么办呢?
分布式锁加入看门狗
在加锁过程中,可以设置一个过期时间,并启动一个守护线程(也称为「看门狗」线程),定时检测锁的剩余有效时间。如果锁即将过期,但共享资源操作尚未完成,守护线程可以自动对锁进行续期,重新设置过期时间。
为什么要使用守护线程:

go中的红锁
package main
import (
"fmt"
"time"
"github.com/go-redis/redis/v8"
"github.com/go-redsync/redsync/v4"
"github.com/go-redsync/redsync/v4/redis/goredis/v8"
)
func main() {
client := redis.NewClient(&redis.Options{
Addr: "172.16.11.111:64495",
Password: "", // 如果有密码,请提供密码
DB: 0, // 如果使用不同的数据库,请修改为准确的数据库编号
})
pool := goredis.NewPool(client)
const LOCKNAME = "百家号:福大大架构师每日一题"
redsync := redsync.New(pool)
mutex := redsync.NewMutex(LOCKNAME)
if err := mutex.Lock(); err != nil {
fmt.Println("加锁失败:", err)
return
}
fmt.Println("加锁main")
ch := make(chan struct{})
go func() {
mutex := redsync.NewMutex(LOCKNAME)
if err := mutex.Lock(); err != nil {
fmt.Println("加锁失败:", err)
return
}
fmt.Println("加锁go程")
mutex.Unlock()
fmt.Println("解锁go程")
ch <- struct{}{}
}()
time.Sleep(time.Second * 2)
mutex.Unlock()
fmt.Println("解锁main")
<-ch
}

2023-06-19:讲一讲Redis分布式锁的实现?的更多相关文章
- SpringBoot集成Redis分布式锁以及Redis缓存
https://blog.csdn.net/qq_26525215/article/details/79182687 集成Redis 首先在pom.xml中加入需要的redis依赖和缓存依赖 < ...
- 死磕 java同步系列之redis分布式锁进化史
问题 (1)redis如何实现分布式锁? (2)redis分布式锁有哪些优点? (3)redis分布式锁有哪些缺点? (4)redis实现分布式锁有没有现成的轮子可以使用? 简介 Redis(全称:R ...
- 分布式-技术专区-Redis分布式锁实现-第一步
承接前面一篇Redis分布式锁的原理介绍 https://www.cnblogs.com/liboware/p/11921759.html 我们针对于实现方案进行接下来上篇进行重新的规划和定义以及完善 ...
- redis咋么实现分布式锁,redis分布式锁的实现方式,redis做分布式锁 积极正义的少年
前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介 ...
- Redis分布式锁的正确实现方式
前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介 ...
- Redis分布式锁---完美实现
这几天在做项目缓存时候,因为是分布式的所以需要加锁,就用到了Redis锁,正好从网上发现两篇非常棒的文章,来和大家分享一下. 第一篇是简单完美的实现,第二篇是用到的Redisson. Redis分布式 ...
- redis分布式锁实践
分布式锁在多实例部署,分布式系统中经常会使用到,这是因为基于jvm的锁无法满足多实例中锁的需求,本篇将讲下redis如何通过Lua脚本实现分布式锁,不同于网上的redission,完全是手动实现的 我 ...
- 关于分布式锁原理的一些学习与思考-redis分布式锁,zookeeper分布式锁
首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法.变量. 在一个进程中,也就是一个jvm 或者说应用中,我们很容易去处理控制,在j ...
- Redlock(redis分布式锁)原理分析
Redlock:全名叫做 Redis Distributed Lock;即使用redis实现的分布式锁: 使用场景:多个服务间保证同一时刻同一时间段内同一用户只能有一个请求(防止关键业务出现并发攻击) ...
- Redis分布式锁的实现
前段时间,我在的项目组准备做一个类似美团外卖的拼手气红包[第X个领取的人红包最大],基本功能实现后,就要考虑这一操作在短时间内多个用户争抢同一资源的并发问题了,类似于很多应用如淘宝.京东的秒杀活动场景 ...
随机推荐
- freeswitch媒体协商的优先级方案
概述 freeswitch是一款简单好用的VOIP开源软交换平台. 不同的媒体格式各有优缺点,实际环境中的应用要根据线路和客户的实际需求确定. 本文中介绍一种较为通用的媒体配置方案,可以适配大部分场景 ...
- 数据挖掘关联分析—R实现
关联分析 关联分析又称关联挖掘,就是在交易数据.关系数据或其他信息载体中,查找存在于项目集合或对象集合之间的频繁模式.关联.相关性或因果结构.或者说,关联分析是发现交易数据库中不同商品(项)之间的联系 ...
- IBM Cloud Computing Practitioners 2019 (IBM云计算从业者2019)Exam答案
Cloud Computing Practitioners 2019 IBM Cloud Computing Practitioners 2019 (IBM云计算从业者2019)Exam答案,加粗的为 ...
- MySQL笔记之一致性视图与MVCC实现
一致性读视图是InnoDB在实现MVCC用到的虚拟结构,用于读提交(RC)和可重复度(RR)隔离级别的实现. 一致性视图没有物理结构,主要是在事务执行期间用来定义该事物可以看到什么数据. 一.Read ...
- LeeCode链表问题(一)
本文中所使用的链表定义如下所示: # Definition for singly-linked list. class ListNode: def __init__(self, val=0, next ...
- 被吐槽 GitHub仓 库太大,直接 600M 瘦身到 6M,这下舒服了
大家好,我是小富- 前言 忙里偷闲学习了点技术写了点demo代码,打算提交到我那 2000Star 的Github仓库上,居然发现有5个Issues,最近的一条日期已经是2022/8/1了,以前我还真 ...
- 弱语言返回的数值型变量有可能是int,也有可能是string,该如何赋值给结构体
包地址 github.com/jefferyjob/go-easy-util... 介绍 在解析弱语言类型返回的 Json 数据时,我们可能会遇到一些麻烦,比如 Json 数据中的数值型变量既可能是 ...
- linux shell 自动化部署 npm vue 项目
此 shell 是提供给前端登录服务器自动化部署 vue 项目的 用此命令,工具化部署项目,可以杜绝前端自己部署项目时,对服务器违规操作 如有其它问题,可在下方留言! #!/bin/sh # url: ...
- Rust中的智能指针:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak<T>
Rust中的智能指针是什么 智能指针(smart pointers)是一类数据结构,是拥有数据所有权和额外功能的指针.是指针的进一步发展 指针(pointer)是一个包含内存地址的变量的通用概念.这个 ...
- [Pytorch框架] 3.1 logistic回归实战
文章目录 3.1 logistic回归实战 3.1.1 logistic回归介绍 3.1.2 UCI German Credit 数据集 3.2 代码实战 import torch import to ...