前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >分布式锁的几种实现原理

分布式锁的几种实现原理

作者头像
一枝花算不算浪漫
发布2019-02-20 17:27:41
1.4K0
发布2019-02-20 17:27:41
举报

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

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

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

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

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

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 = ?

实现原理入下图:

但是数据库的性能有限,如果在高并发的情况下会频发的访问数据库,对数据库会造成较大的压力。

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

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

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 继续努力加油学习更多知识,让自己在技术这条道路上越走越远!

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-01-05 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 分布式锁主流有三种模式:
  • 一、基于Mysql实现分布式锁 (乐观锁)
  • 二,基于redis的分布式锁实现
    • 2.1 死锁问题
      • 2.2 锁自动过期存在的隐患
      相关产品与服务
      云数据库 Redis
      腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档