Redis分布式锁思路

对于线程安全问题,传统的而方案是对线程资源操作加锁。

线程锁

通常使用synchronized 或者lock关键字来进行加锁。但是这个是对于同一个jvm内存的。

分布式锁

如果在多个进程中保证任务同一时刻执行,那么这个就是分布式锁了。

分布式锁实现有很多方式,常见的有zookeeper实现,缓存实现,数据库实现。

但是这些锁都有如下的特征:

  • 多个进程可见
  • 互斥的,也就是在同一时刻只有一个进程获得锁。
  • 可重入:同一任务再次获取改锁不会被死锁。
  • 阻塞锁:获取失败时,具备重试的机制,尝试再次获取锁。
  • 高可用

思考如何实现

多进程可见:

在redis中本身不是出于jvm中,是可以保证的。

互斥:

redis 中有setnx指令。 这个指令是多次执行命令的时候,只有第一次执行才会成功并且返回1,其他情况返回0。
redis内部模型是基于linux epoll io多路复用模型,保证单线程执行,也就保证了原子的操作。

如何释放锁:加上锁之后,还需要考虑如何释放锁。redis中可以用del 命令删除key。不过如果服务器宕机,可能造成锁会永远无法删除。
为了避免这个问题,我们可以执行过期时间。

这样我们的设计的分布式锁可以这样来实现:

1.通过set命令设置锁
2.判断是否返回结果ok
2.1如果失败,结束或者重试
2.2 ok获取成功, 执行业务 ,释放锁,并且删除key
3.如果有异常,服务宕机,超过时间,会自动释放锁。

但是这个还是会有一些问题,比如,互斥性不一定会满足。
我们在释放锁的时候使用del语句把锁对应的key删除。
在如下情境中:
1.3个进程 a b c 在执行任务 a获取锁,设置10秒
2. a开始执行业务,因为某种原因阻塞住了,阻塞超过了10秒,所以锁自动释放了。
3. b恰好此时开始尝试获取锁,成功获取了锁。
4. a执行完毕了,不知道锁已经不是A自己的了,还是执行了释放锁的逻辑。造成了把b的锁给释放了,b其实还在执行任务。
5. c过来了,获取锁操作,依然获取成功了。因为a当时把b锁的删除了。

所以需要在删除锁的时候判断是否是自己的锁的标识。

可重入性如何保证?

首先我们需要知道什么是重入锁。Java中ReentrantLock和synchronized都是可重入锁。

打水的例子,有多个人在排队打水,此时管理员允许锁和同一个人的多个水桶绑定。这个人用多个水桶打水时,第一个水桶和锁绑定并打完水之后,第二个水桶也可以直接和锁绑定并开始打水,所有的水桶都打完水之后打水人才会将锁还给管理员。这个人的所有打水流程都能够成功执行,后续等待的人也能够打到水。这就是可重入锁。

在这里插入图片描述
转自

如何实现可重入锁呢?

  • 获取锁:首先尝试获取锁,如果获取失败,判断这个锁是否是自己的,如果是则允许再次获取,而且必须记录重复获取锁的次数。
  • 释放锁:释放锁不能直接删除了,因为锁是可重入的,如果锁进入了多次,在最内层直接删除锁,导致外部的业务在没有锁的情况下执行,会有安全问题。因此必须获取锁时累计重入的次数,释放时则减去重入次数,如果减到0,则可以删除锁.
    因此,存储在锁中的信息就必须包含:key、线程标识、重入次数。不能再使用简单的key-value结构,这里推荐使用hash结构:
  • key:lock
  • hashKey:线程信息
  • hashValue:重入次数,默认1

在这里插入图片描述

原子性:

原子性问题。无论是获取锁,还是释放锁的过程,都是有多行Redis指令来完成的,如果不能保证这些Redis命令执行的原子性,则整个过程都是不安全的。

Redis中支持以Lua脚本来运行多行命令,并且保证整个脚本运行的原子性。

阻塞锁:

我们可以添加失败后不断重试的逻辑。

高可用

redis可以搭建主从集群保证高可用。但是并不保证分布式锁的高可用。
Redis的作者又给出了一种新的算法来解决整个高可用问题,即Redlock算法。但是这个方案会有一些问题。

目前来说redission框架来帮我们实现了各种分布式锁方案。

发布了93 篇原创文章 · 获赞 26 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/sxj159753/article/details/104579700