常见的分布式锁(简略)

1、背景和简介#

  • 传统单体应用使用本地锁(synchronized、ReentrantLock)
  • 随着分布式的快速发展,本地锁已经无法解决并发问题
  • 需要一种能跨微服务/虚拟机的锁机制--->分布式锁

分布式锁的作用

  • 并发正确性(资源独占)
  • 效率: 避免多个pod(虚拟机)都执行同一个操作,重复处理业务。

分布式锁的特性

  • 互斥性:基本功能,一个获取锁,另一个就不能获取
  • 可重入性:一个线程获取锁之后可以再次获取这个锁
  • 锁超时:持有锁的线程挂掉后,一定时间后锁自动释放
  • 高效:加锁/释放锁的速度快
  • 高可用
  • 支持阻塞和非阻塞
  • 支持公平锁(排着队来等)和非公平锁(随机来抢)

常用的分布式锁中间件

  • MySQL
  • Zookeeper
  • Chubby(谷歌)
  • Etcd
  • Redis

2、MySQL分布式锁#

  • 专用的数据表
CREATE TABLE `resource_lock`(
	`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
    `resource_name` varchar(128) NOT NULL DEFAULT '' COMMENT '资源名字、模块名称、业务名称',
    `node_info` varhcar(128) NOT NULL DEFAULT '' COMMENT '节点信息、线程信息、线程标识',
    `count` varchar(11) NOT NULL DEFAULT '0' COMMENT '锁的次数,实现可重入',
    `create_time` timestamp NULL DEFAULT NULL COMMENT '创建时间', 
    `update_time` timestamp NULL DEFAULT NULL COMMENT '更新时间',
    PRIMARY KEY (`id`),
    UNIQUE KEY `unq_resource`(`resource_name`)
)
  • 若需要枷锁的资源恰好有对应的数据表,可以在数据表中增加相应的字段,达到复用数据的目的

代码实现(方式)#

阻塞式获取锁

循环调用lock()函数,直到返回true

@Transaction // 必须要有事物
public boolean lock() {
    // for update 增加写锁
    if(select * from resource_lock where resource_name = '***' for update -> 有数据){
        // 有数据,表示资源已经被枷锁,需要判断是否是重入
        if(currentNodeInfo == resultNodeInfo) {
           // 是自己加的锁,增加count,表示重入了
            update resource_lock set count = count + 1 where resource_name = '***';
            return true;
        } else {
            return false
        }
    } else {
        // 
        insert into resource_lock
        return true;
    }
}

非阻塞式获取锁

public boolean tryLock(long timeout `超时时间`) {
    long endTimeout = System.currentTimeMills() + timeout;
    where(true){
        // 上面的lock锁
        if(mysqlLock.lock()) {
            return true;
        }
        // 如果超时,加锁失败
        if(endTimeout < System.currentTimeMills()) {
            return false;
        }
    }
}

释放锁

@Transaction
public boolean unlock() {
    if (select * from resource_lock where resource_name = '***' for update -> 有数据) {
        // 有数据,表示资源已经被加锁,需要判断是否被自己加锁
        if (currentNodeInfo == resultNodeInfo) {
            // 是自己加的锁
            if (count > 1) {
                // 锁被自己重入,直接减少count字段
                update count = count - 1;
            } else {
                // 锁没有重入,直接删除
                delete;
            }
        } else {
            // 不是自己加的锁,无法释放
            return false;
        }
    } else {
        return true;
    }
}

锁超时

启动一个定时任务循环遍历锁,长时间未释放的锁即为超时,直接删除。

MySQL分布式锁特点#

  • 适用场景:没有其他中间件可用,需要枷锁的资源刚好有对应的数据表
  • 优点:理解起来简单,不需要维护其他中间件
  • 缺点:需要自己实现加锁、解锁过程,性能比较差

3、ZooKeeper分布式锁#

ZoopKeeper是以Paxos算法为基础分布式应用程序协调服务

简介和工作原理#

image-20230205191557528

image-20230205192435166

image-20230205192812376

知识点:

​ 临时节点(有序、无序)、zookeeper中的watch

加锁流程#

  • 进行重入判定(可以利用ThreadLocal)
  • 在被锁资源下建立ephemeral_sequential节点
  • 判断自己的节点是否位于第一个
  • 若是第一个,则获取到锁,返回
  • 若不是第一个,在前一个节点上注册watcher
  • 进行阻塞等待

解锁流程#

  • 进行重入判定(可以利用ThreadLocal)
  • 若为重入,在重入次数上减1,返回
  • 删除Zookeeper上的节点

代码实现#

image-20230205194339755

Curator包含了几个包

curator-framework:对zookeeper的底层api的一些封装
curator-client:提供一些客户端的操作,例如重试策略等
curator-recipes:封装了一些高级特性,如:Cache事件监听、选举、分布式锁、分布式计数器、分布式Barrier等

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>2.12.0</version>
</dependency>
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-client</artifactId>
    <version>2.12.0</version>
</dependency>
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>2.12.0</version>
</dependency>

优缺点#

image-20230205195017232

4、Chubby分布式锁#

简介#

  • Chubby是谷歌开放的分布式应用程序协调服务
  • 功能上与Zookeeper类似

image-20230205200700488

5、Etcd分布式锁#

简介#

  • Etcd是一个高可用的分布式键值(key-value)数据库
  • Etcd内部采用raft协议作为一致性算法

image-20230205200922646

image-20230205201453911

image-20230205201706879

image-20230205202209182

实现方式#

image-20230205201855301

6、Redis分布式锁#

简介#

  • Redis(REmote Dlctionary Server)是一个key-value存储中间件
  • Redis中的value可以是字符串(string)、哈希(hash)、列表(list)、集合(set)和有序集合(sorted sets)等类型

简易实现方式#

image-20230205202648732

ex : 超时时间,用于锁超时处理

nx:是否存在,存在则不成功,用于锁的独占处理

image-20230205202957518

image-20230205203119245

image-20230205203234698

image-20230205203313357

Redission实现分布式锁#

image-20230205203539246

image-20230205204247185

image-20230205204336239

image-20230205204510249

RedLock实现Redis分布式锁#

  • Redis主从与集群并不是强一致性的,所以在极端情况下,会有一致性问题

  • 若Redis未及时落盘,重启后会丢失数据

  • 为了解决以上问题,Redis作者提出了RedLock红锁算法

  • 首先生成多个Redis集群的Rlock,并将其构造成RedLock

  • 依次循环对三个(多个)集群进行加锁,加锁方式和Redission一致

  • 如果循环加锁的过程中加锁失败,那么需要判断加锁失败的次数是否超出了最大之(要多数成功)

  • 加锁的过程中需要判断是否加锁超时

  • 若失败,向所有节点请求解锁

image-20230205210144370

优缺点#

image-20230205210425349

7、分布式锁的安全问题#

GC导致锁超时

image-20230205210843647

网络I/O导致锁超时

image-20230205211210934

时钟跳跃导致锁超时

image-20230205211717495

Ref:https://www.bilibili.com/video/BV1fP4y1n7Ko/?spm_id_from=333.337.search-card.all.click&vd_source=9ad3878388e007b196d679a10e69ec8a

作者:mountainstudy

出处:https://www.cnblogs.com/mountainstudy/p/17094045.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   Mountain_SY  阅读(159)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示