分布式锁的几种实现原理

分布式锁主流有三种模式:

实现方式 功能要求 实现难度 学习成本 运维成本
MySQL 的方案借助表锁/行锁实现 知足基本要求 不难 熟悉 小量OK、大量影响现有业务、1主多从架构,不方便扩容
经过 ZK 建立数据节点的方式实现 知足要求 熟悉 ZK API 便可 须要学习 重,须要堆机器,有跨机房请求
Redis 使用 setnxex 基本要求 不难 熟悉 扩容方便、现有服务html

MySQL 单主架构,写都会到 master,有瓶颈。ZK 的方式须要本身搭建、运维,并且须要堆机器,利用率不高。最终采用了 Redis 来实现,流量/存储均可以扩容,运维也不须要本身。java

1、基于Mysql实现分布式锁 (乐观锁)

Mysql实现分布式锁 主要是基于数据库的排他锁(也叫行级排他锁), 采用乐观锁的方式去作。
咱们能够经过一个update语句是否成功来判断线程抢占锁是否成功,好比以下sql语句:mysql

CREATE TABLE `t_schedule_cluster` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '@cname:主键',
  `execute` int(1) NOT NULL COMMENT '@cname:执行状态',
  `version` int(11) NOT NULL COMMENT '@cname:版本号 ',
  `task_name` varchar(128) NOT NULL COMMENT '@cname:任务名称',
  `execute_ip` varchar(32) DEFAULT NULL COMMENT '@cname:执行ip ',
  `update_time` datetime DEFAULT NULL COMMENT '@cname:修改时间',
  PRIMARY KEY (`id`),
  KEY `Index_series_id` (`execute`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8 COMMENT='@cname:多机定时任务调度';

争抢锁的sql语句:
update t_schedule_cluster set execute = 1 version = ?, execute_ip = ?, update_time = ? where task_name = ? and version = ?git

实现原理入下图:
github

可是数据库的性能有限,若是在高并发的状况下会频发的访问数据库,对数据库会形成较大的压力。redis

二,基于redis的分布式锁实现

基于Redis实现的分布式锁其实很简单,底层就是使用redis的setnx指令来实现的加锁,咱们来看看官方对setnx的定义:
SETNX key value
将 key 的值设为 value ,当且仅当 key 不存在。
若给定的 key 已经存在,则 SETNX 不作任何动做。
SETNX 是『SET if Not eXists』(若是不存在,则 SET)的简写。
返回值:
设置成功,返回 1 。
设置失败,返回 0 。sql

redis> EXISTS job # job 不存在
(integer) 0
redis> SETNX job "programmer" # job 设置成功
(integer) 1
redis> SETNX job "code-farmer" # 尝试覆盖 job ,失败
(integer) 0
redis> GET job # 没有被覆盖
"programmer"

以上内容来自于:http://redisdoc.com/string/setnx.html数据库

既然setnx这么强大,那么咱们是否是能够高枕无忧直接使用了? 固然了,咱们还要考虑一些极端场景。架构

2.1 死锁问题

既然设置了value值,那么咱们确定会想到过时时间,那么就须要再使用setnx指令后继续使用expire指令。可是这两部操做一定不是原子性的,若是执行expire失败怎么办?
其实Redis官方也考虑到了这个问题,在Redis2.8 以后,官方执行setnx 和 expire命令一块儿使用了。以下:
SET lock_key lock_value NX PX 30000
其中:
1.lock_key:即锁名称,这个名称应是公开的,在分布式环境中,对于某一肯定的公共资源,全部争用方(客户端)都应该知道对应锁的名字。对于 Redis 而言,lock_name 就是 key-value 中的 key,具备惟一性。并发

  1. lock_value:是由客户端生成的一个随机字符串,它要保证在足够长的一段时间内在全部客户端的全部获取锁的请求中都是惟一的,用于惟一标识锁的持有者。
  2. NX 表示只有当 lock_key(key) 不存在的时候才能 SET 成功,从而保证只有一个客户端能得到锁,而其它客户端在锁被释放以前都没法得到锁。
  3. PX 30000 表示这个锁节点有一个 30 秒的自动过时时间(目的是为了防止持有锁的客户端故障后,没法主动释放锁而致使死锁,所以要求锁的持有者必须在过时时间以内执行完相关操做并释放锁)。
    具体操做以下图:

2.2 锁自动过时存在的隐患

例如咱们有两个线程A、B,此时线程A抢到了锁,且设置自动过时时间为10s钟,由于系统其余缘由致使系统A发生阻塞。而此刻10s钟后锁自动过时,线程C获取到了同一个资源的锁,线程A从阻塞中恢复,认为本身仍然持有锁,继续操做同一资源。这样就使得加锁的互斥性失效了。

解决方案:
咱们在上面set lock_key lock_value 时讲过,lock_value是一个随机生成的字符串,在每次获取锁的时候都会从新生成。那么咱们在执行真正的业务逻辑(相似于和db进行交互的操做,同一时刻只能一个线程操做的状况)时,判断当前生成的随机字符串和lock_value是否一致,若是不一致则说明redis中的lock_value被修改过,也就说明此刻锁已经被其余线程所占有。

具体操做流程以下图:

主要使用的就是这两种方案,在这里只是作个简单总结,其实还有其余一些能够实现分布式锁,根据本身项目自己状况选择最合适的。
另外 已经Redis也有开源的框架能够很好地支持基于Redis的分布式锁,这里推荐一个:Redission https://github.com/redisson/redisson

PS:2019 继续努力加油学习更多知识,让本身在技术这条道路上越走越远!

相关文章
相关标签/搜索