什么? 搞不定redis分布式锁?

作者:故事凌

分布式锁

1. 什么是分布式锁

分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。

2. 为什么要使用分布式锁

为了保证一个方法或属性在高并发情况下的同一时间只能被同一个线程执行,在传统单体应用单机部署的情况下,可以使用Java并发处理相关的API(如ReentrantLock或Synchronized)进行互斥控制。在单机环境中,Java中提供了很多并发处理相关的API。但是,随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的Java API并不能提供分布式锁的能力。为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!

举个例子:

机器A , 机器B是一个集群, A, B两台机器上的程序都是一样的, 具备高可用性能.

A, B机器都有一个定时任务, 每天晚上凌晨2点需要执行一个定时任务, 但是这个定时任务只能执行一遍, 否则的话就会报错, 那A,B两台机器在执行的时候, 就需要抢锁, 谁抢到锁, 谁执行, 谁抢不到, 就不用执行了!

3. 锁的处理

  • 单个应用中使用锁: (单进程多线程)
synchronize
  • 分布式锁控制分布式系统之间同步访问资源的一种方式

       分布式锁是控制分布式系统之间同步同问共享资源的一种方式

4. 分布式锁的实现

  • 基于数据的乐观锁实现分布式锁
  • 基于zookeeper临时节点的分布式锁
  • 基于redis的分布式锁

5. redis的分布式锁

  • 获取锁:

在set命令中, 有很多选项可以用来修改命令的行为, 一下是set命令可用选项的基本语法

redis 127.0.0.1:6379>SET KEY VALUE [EX seconds] [PX milliseconds] [NX|XX]

redis 127.0.0.1:6379>SET KEY VALUE [EX seconds] [PX milliseconds] [NX|XX]- EX seconds  设置指定的到期时间(单位为秒)- PX milliseconds 设置指定的到期时间(单位毫秒)- NX: 仅在键不存在时设置键- XX: 只有在键已存在时设置

方式1: 推介

    private static final String LOCK_SUCCESS = "OK";private static final String SET_IF_NOT_EXIST = "NX";private static final String SET_WITH_EXPIRE_TIME = "PX";public static boolean getLock(JedisCluster jedisCluster, String lockKey, String requestId, int expireTime) {// NX: 保证互斥性String result = jedisCluster.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);if (LOCK_SUCCESS.equals(result)) {return true;}return false;}

方式2:

public static boolean getLock(String lockKey,String requestId,int expireTime) {Long result = jedis.setnx(lockKey, requestId);if(result == 1) {jedis.expire(lockKey, expireTime);return true;}return false;}

注意: 推介方式1, 因为方式2中setnx和expire是两个操作, 并不是一个原子操作, 如果setnx出现问题, 就是出现死锁的情况, 所以推荐方式1

  • 释放锁:

方式1: del命令实现

public static void releaseLock(String lockKey,String requestId) {if (requestId.equals(jedis.get(lockKey))) {jedis.del(lockKey);}
}

方式2: redis+lua脚本实现 推荐

public static boolean releaseLock(String lockKey, String requestId) {String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return
redis.call('del', KEYS[1]) else return 0 end";Object result = jedis.eval(script, Collections.singletonList(lockKey),
Collections.singletonList(requestId));if (result.equals(1L)) {return true;
}return false;}

6. zookeeper的分布式锁

6.1 zookeeper实现分布式锁的原理

理解了锁的原理后,就会发现,Zookeeper 天生就是一副分布式锁的胚子。

首先,Zookeeper的每一个节点,都是一个天然的顺序发号器。

在每一个节点下面创建子节点时,只要选择的创建类型是有序(EPHEMERAL_SEQUENTIAL 临时有序或者PERSISTENT_SEQUENTIAL 永久有序)类型,那么,新的子节点后面,会加上一个次序编号。这个次序编号,是上一个生成的次序编号加一

比如,创建一个用于发号的节点“/test/lock”,然后以他为父亲节点,可以在这个父节点下面创建相同前缀的子节点,假定相同的前缀为“/test/lock/seq-”,在创建子节点时,同时指明是有序类型。如果是第一个创建的子节点,那么生成的子节点为/test/lock/seq-0000000000,下一个节点则为/test/lock/seq-0000000001,依次类推,等等。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在这里插入图片描述

其次,Zookeeper节点的递增性,可以规定节点编号最小的那个获得锁。

一个zookeeper分布式锁,首先需要创建一个父节点,尽量是持久节点(PERSISTENT类型),然后每个要获得锁的线程都会在这个节点下创建个临时顺序节点,由于序号的递增性,可以规定排号最小的那个获得锁。所以,每个线程在尝试占用锁之前,首先判断自己是排号是不是当前最小,如果是,则获取锁。

第三,Zookeeper的节点监听机制,可以保障占有锁的方式有序而且高效。

每个线程抢占锁之前,先抢号创建自己的ZNode。同样,释放锁的时候,就需要删除抢号的Znode。抢号成功后,如果不是排号最小的节点,就处于等待通知的状态。等谁的通知呢?不需要其他人,只需要等前一个Znode 的通知就可以了。当前一个Znode 删除的时候,就是轮到了自己占有锁的时候。第一个通知第二个、第二个通知第三个,击鼓传花似的依次向后。

Zookeeper的节点监听机制,可以说能够非常完美的,实现这种击鼓传花似的信息传递。具体的方法是,每一个等通知的Znode节点,只需要监听linsten或者 watch 监视排号在自己前面那个,而且紧挨在自己前面的那个节点。 只要上一个节点被删除了,就进行再一次判断,看看自己是不是序号最小的那个节点,如果是,则获得锁。

为什么说Zookeeper的节点监听机制,可以说是非常完美呢?

一条龙式的首尾相接,后面监视前面,就不怕中间截断吗?比如,在分布式环境下,由于网络的原因,或者服务器挂了或则其他的原因,如果前面的那个节点没能被程序删除成功,后面的节点不就永远等待么?

其实,Zookeeper的内部机制,能保证后面的节点能够正常的监听到删除和获得锁。在创建取号节点的时候,尽量创建临时znode 节点而不是永久znode 节点,一旦这个 znode 的客户端与Zookeeper集群服务器失去联系,这个临时 znode 也将自动删除。排在它后面的那个节点,也能收到删除事件,从而获得锁。

说Zookeeper的节点监听机制,是非常完美的。还有一个原因。

Zookeeper这种首尾相接,后面监听前面的方式,可以避免羊群效应。所谓羊群效应就是每个节点挂掉,所有节点都去监听,然后做出反映,这样会给服务器带来巨大压力,所以有了临时顺序节点,当一个节点挂掉,只有它后面的那一个节点才做出反映。

###6.2 zookeeper实现分布式锁的示例

zookeeper是通过临时节点来实现分布式锁.

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.junit.Before;
import org.junit.Test;/*** @ClassName ZookeeperLock* @Description TODO* @Author lingxiangxiang* @Date 2:57 PM* @Version 1.0**/
public class ZookeeperLock {// 定义共享资源private static int NUMBER = 10;private static void printNumber() {// 业务逻辑: 秒杀System.out.println("*********业务方法开始************\n");System.out.println("当前的值: " + NUMBER);NUMBER--;try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("*********业务方法结束************\n");}// 这里使用@Test会报错public static void main(String[] args) {// 定义重试的侧策略 1000 等待的时间(毫秒) 10 重试的次数RetryPolicy policy = new ExponentialBackoffRetry(1000, 10);// 定义zookeeper的客户端CuratorFramework client = CuratorFrameworkFactory.builder().connectString("10.231.128.95:2181,10.231.128.96:2181,10.231.128.97:2181").retryPolicy(policy).build();// 启动客户端client.start();// 在zookeeper中定义一把锁final InterProcessMutex lock = new InterProcessMutex(client, "/mylock");//启动是个线程for (int i = 0; i <10; i++) {new Thread(new Runnable() {@Overridepublic void run() {try {// 请求得到的锁lock.acquire();printNumber();} catch (Exception e) {e.printStackTrace();} finally {// 释放锁, 还锁try {lock.release();} catch (Exception e) {e.printStackTrace();}}}}).start();}}
}

7. 基于数据的分布式锁

我们在讨论使用分布式锁的时候往往首先排除掉基于数据库的方案,本能的会觉得这个方案不够“高级”。从性能的角度考虑,基于数据库的方案性能确实不够优异,整体性能对比:缓存 > Zookeeper、etcd > 数据库。也有人提出基于数据库的方案问题很多,不太可靠。数据库的方案可能并不适合于频繁写入的操作.

下面我们来了解一下基于数据库(MySQL)的方案,一般分为3类:基于表记录、乐观锁和悲观锁。

7.1 基于表记录

要实现分布式锁,最简单的方式可能就是直接创建一张锁表,然后通过操作该表中的数据来实现了。当我们想要获得锁的时候,就可以在该表中增加一条记录,想要释放锁的时候就删除这条记录。

为了更好的演示,我们先创建一张数据库表,参考如下:

CREATE TABLE `database_lock` (`id` BIGINT NOT NULL AUTO_INCREMENT,`resource` int NOT NULL COMMENT '锁定的资源',`description` varchar(1024) NOT NULL DEFAULT "" COMMENT '描述',PRIMARY KEY (`id`),UNIQUE KEY `uiq_idx_resource` (`resource`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据库分布式锁表';
  • 获得锁

我们可以插入一条数据:

INSERT INTO database_lock(resource, description) VALUES (1, 'lock');

因为表database_lock中resource是唯一索引, 所以其他请求提交到数据库, 就会报错, 并不会插入成功, 只有一个可以插入. 插入成功, 我们就获取到锁

  • 删除锁
INSERT INTO database_lock(resource, description) VALUES (1, 'lock');

这种实现方式非常的简单,但是需要注意以下几点:

  1. 这种锁没有失效时间,一旦释放锁的操作失败就会导致锁记录一直在数据库中,其它线程无法获得锁。这个缺陷也很好解决,比如可以做一个定时任务去定时清理。
  2. 这种锁的可靠性依赖于数据库。建议设置备库,避免单点,进一步提高可靠性。
  3. 这种锁是非阻塞的,因为插入数据失败之后会直接报错,想要获得锁就需要再次操作。如果需要阻塞式的,可以弄个for循环、while循环之类的,直至INSERT成功再返回。
  4. 这种锁也是非可重入的,因为同一个线程在没有释放锁之前无法再次获得锁,因为数据库中已经存在同一份记录了。想要实现可重入锁,可以在数据库中添加一些字段,比如获得锁的主机信息、线程信息等,那么在再次获得锁的时候可以先查询数据,如果当前的主机信息和线程信息等能被查到的话,可以直接把锁分配给它。

7.2 乐观锁

顾名思义,系统认为数据的更新在大多数情况下是不会产生冲突的,只在数据库更新操作提交的时候才对数据作冲突检测。如果检测的结果出现了与预期数据不一致的情况,则返回失败信息。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

乐观锁大多数是基于数据版本(version)的记录机制实现的。何谓数据版本号?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表添加一个 “version”字段来实现读取出数据时,将此版本号一同读出,之后更新时,对此版本号加1。在更新过程中,会对版本号进行比较,如果是一致的,没有发生改变,则会成功执行本次操作;如果版本号不一致,则会更新失败。

为了更好的理解数据库乐观锁在实际项目中的使用,这里也就举了业界老生常谈的库存例子。一个电商平台都会存在商品的库存,当用户进行购买的时候就会对库存进行操作(库存减1代表已经卖出了一件)。如果只是一个用户进行操作数据库本身就能保证用户操作的正确性,而在并发的情况下就会产生一些意想不到的问题:

比如两个用户同时购买一件商品,在数据库层面实际操作应该是库存进行减2操作,但是由于高并发的情况,第一个用户购买完成进行数据读取当前库存并进行减1操作,由于这个操作没有完全执行完成。第二个用户就进入购买相同商品,此时查询出的库存可能是未减1操作的库存导致了脏数据的出现【线程不安全操作】,通常如果是单JVM情况下使用JAVA内置的锁就能保证线程安全,如果在多JVM的情况下,使用分布式锁也能实现【后期会补】,而本篇着重的去讲数据库层面的。

针对上面的问题,数据库乐观锁也能保证线程安全,通常哎代码层面我们都会这样做:

select goods_num from goods where goods_name = "小本子";
update goods set goods_num = goods_num -1 where goods_name = "小本子";

上面的SQL是一组的,通常先查询出当前的goods_num,然后再goods_num上进行减1的操作修改库存,当并发的情况下,这条语句可能导致原本库存为3的一个商品经过两个人购买还剩下2库存的情况就会导致商品的多卖。那么数据库乐观锁是如何实现的呢?
首先定义一个version字段用来当作一个版本号,每次的操作就会变成这样:

select goods_num,version from goods where goods_name = "小本子";
update goods set goods_num = goods_num -1,version =查询的version值自增 where goods_name ="小本子" and version=查询出来的version;

其实,借助更新时间戳(updated_at)也可以实现乐观锁,和采用version字段的方式相似:更新操作执行前线获取记录当前的更新时间,在提交更新时,检测当前更新时间是否与更新开始时获取的更新时间戳相等。

7.3 悲观锁

除了可以通过增删操作数据库表中的记录以外,我们还可以借助数据库中自带的锁来实现分布式锁。在查询语句后面增加FOR UPDATE,数据库会在查询过程中给数据库表增加悲观锁,也称排他锁。当某条记录被加上悲观锁之后,其它线程也就无法再改行上增加悲观锁。

悲观锁,与乐观锁相反,总是假设最坏的情况,它认为数据的更新在大多数情况下是会产生冲突的。

在使用悲观锁的同时,我们需要注意一下锁的级别。MySQL InnoDB引起在加锁的时候,只有明确地指定主键(或索引)的才会执行行锁 (只锁住被选取的数据),否则MySQL 将会执行表锁(将整个数据表单给锁住)。

在使用悲观锁时,我们必须关闭MySQL数据库的自动提交属性(参考下面的示例),因为MySQL默认使用autocommit模式,也就是说,当你执行一个更新操作后,MySQL会立刻将结果进行提交。

mysql> SET AUTOCOMMIT = 0;
Query OK, 0 rows affected (0.00 sec)

这样在使用FOR UPDATE获得锁之后可以执行相应的业务逻辑,执行完之后再使用COMMIT来释放锁。

我们不妨沿用前面的database_lock表来具体表述一下用法。假设有一线程A需要获得锁并执行相应的操作,那么它的具体步骤如下:

STEP1 - 获取锁:SELECT * FROM database_lock WHERE id = 1 FOR UPDATE;。
STEP2 - 执行业务逻辑。
STEP3 - 释放锁:COMMIT。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/518858.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

阿里云CDN技术掌舵人文景:相爱相杀一路狂奔的这十年

提到阿里云CDN&#xff0c;不得不提技术掌舵人姚伟斌&#xff08;文景&#xff09;&#xff0c;虽然他不是团队中最“老”的同学&#xff0c;但他却历经了淘宝业务发展最为飞速的几年&#xff0c;见证了从最初服务淘宝和集团内部的CDN&#xff0c;到如今国内服务客户最多的云CD…

把握数据库发展趋势 DBA应如何避免“踩坑”?

在DTCC 2019大会上&#xff0c;阿里云智能数据库产品事业部高级产品专家萧少聪做了题为《如何构建云时代DBA的知识体系》的演讲&#xff0c;进行云时代以后&#xff0c;IT行业各工种的职责都在发生变化&#xff0c;云数据库使得日常DBA管理实现更多的自动化&#xff0c;大大提高…

DevOps 转型时如何安全融入?对企业产出有何影响?2019年 DevOps 最新现状研究报告解读 | 原力计划...

作者 | liumiaocn责编 | 徐威龙封图| CSDN 下载于视觉中国2019年DORA发布了DevOps的研究报告&#xff0c;迄今为止这已经是DORA的第八次报告的发布。相较于往年的报告&#xff0c;2019年的报告全篇只聚焦于一个要素&#xff1a;安全。在2018年DORA提供了一个包含五个步骤的模型…

开源性能可视化工具——FlameScope模式识别

FlameScope是一个新的开源性能可视化工具&#xff0c;它使用次秒级偏移热图和火焰图来分析周期活动、方差、扰动。我们在Netflix TechBlog上面&#xff0c;发表了技术文章Netflix FlameScope&#xff0c;以及工具的源代码。火焰图很好理解&#xff0c;次秒级偏移热图理解起来要…

在腾讯云开通短信验证服务设置正确格式的签名和正文模板并完成群发消息测试

链接&#x1f449; https://blog.csdn.net/weixin_45001200/article/details/118878336?spm1001.2014.3001.5501 经历了一晚上的审核&#xff0c;终于在第二天近10点发来了测试验证码......

独家揭秘:阿里小程序的一云多端!看这篇就够了!

专家介绍 视频回放 https://yq.aliyun.com/live/1097 阿里小程序的一云多端 相信绝大部分同学知道阿里一云多端的项目&#xff0c;最早始于19年三月份在北京云栖大会上&#xff0c;阿里云的CEO在云栖大会上对外发布了一云多端的项目。 一云多端是什么? 大家今天常见都是微…

Docker 概念很难理解?一文搞定 Docker 端口绑定

作者 | Dieter Jordens译者 | 苏本如&#xff0c;责编 | 夕颜出品 | CSDN&#xff08;ID:CSDNnews&#xff09;以下为译文&#xff1a;作为初级开发人员的你&#xff0c;是不是参加过这样的面试&#xff0c;在面试中面试官希望你准确地回答Docker的工作原理&#xff1f;现今的面…

阿里云高级技术专家张毅萍:我眼中的边缘计算

边缘计算是目前公认的大方向&#xff0c;越来越多的边缘计算应用将随着5G建设的步伐而兴起。阿里云边缘计算团队的目标是在行业爆发来临之前&#xff0c;完成基础计算资源平台的构建&#xff0c;为产业提供基于体验的计算调度能力&#xff0c;进而助推整个产业快速应用发展。那…

docker 安装 mysql和Navicat Premium 远程连接

文章目录1. 安装mysql2. Navicat Premium 远程连接1. 安装mysql # 通过搜索镜像 docker search mysql# 拉取mysql镜像 docker pull mysql# 查看拉取的mysql镜像 docker images#创建MySQL容器 docker run -di --name mymysql -p 3306:3306 -e MYSQL_ROOT_PASSWORDroot mysql# 查…

牛客网SQL篇刷题篇(1-2)

https://www.nowcoder.com/ta/sql

阿里PB级Kubernetes日志平台建设实践

阿里PB级Kubernetes日志平台建设实践 QCon是由InfoQ主办的综合性技术盛会&#xff0c;每年在伦敦、北京、纽约、圣保罗、上海、旧金山召开。有幸参加这次QCon10周年大会&#xff0c;作为分享嘉宾在刘宇老师的运维专场发表了《阿里PB级Kubernetes日志平台建设实践》&#xff0c…

果断拿下4000万美元D轮融资,Rancher发力中国本土化与国产化!

2020年3月17日&#xff0c;业界应用广泛的Kubernetes管理平台创建者Rancher Labs&#xff08;以下简称Rancher&#xff09;宣布完成新一轮4000万美元D轮融资。 本轮融资由Telstra Ventures领投&#xff0c;既有投资者Mayfield、Nexus Venture Partners、国富绿景创投&#xff0…

MySQL 8.0 技术详解

MySQL 8.0 简介 MySQL 5.7 到 8.0&#xff0c;Oracle 官方跳跃了 Major Version 版本号&#xff0c;随之而来的就是在 MySQL 8.0 上做了许多重大更新&#xff0c;在往企业级数据库的路上大步前行&#xff0c;全新 Data Dictionary 设计&#xff0c;支持 Atomic DDL&#xff0c…

解决:Error response from daemon: manifest for xxx:latest not found: manifest unknown...

在使用docker 拉去最新的镜像时&#xff0c;会提示如下错误&#xff1a; 这里错误的意思是docker需要我们指定下载镜像的版本号。 但是我们想下载最新的版本号&#xff0c;该如何得知最新的版本号呢&#xff1f; 我们可以登录docker hub&#xff1a;https://hub.docker.com/…

牛客网SQL篇刷题篇(3-10)

https://www.nowcoder.com/ta/sql 1.inner join 和left join &#xff08;1&#xff09;在表中存在至少一个匹配时&#xff0c;INNER JOIN 关键字返回行。 SELECT *** FROM A INNER JOIN B ON 条件 注释&#xff1a;INNER JOIN 与 JOIN 是相同的。 &#xff08;2&#…

技术三板斧:关于技术规划、管理、架构的思考

阿里妹导读&#xff1a;实践需要理论的指导&#xff0c;理论从实践中来。作为技术工程师&#xff0c;要不断地从事件中反思经验、总结规律&#xff0c;才能避免踏入同一个坑&#xff0c;才能更高效地完成 KPI &#xff0c;甚至是晋升。今天的文章来自阿里巴巴高级技术专家毕啸&…

让服务器突破性能极限 阿里云神龙论文入选计算机顶会ASPLOS

疫情肆虐&#xff0c;全球多个科技领域盛会宣布改为线上举办&#xff0c;计算机领域顶会 ASPLOS也不例外。 日前&#xff0c;ASPLOS 2020公布了计算机界最新科技成果&#xff0c;其中包括阿里云提交的名为《High-density Multi-tenant Bare-metal Cloud》的论文&#xff0c;该…

自动化日志收集及分析在支付宝 App 内的演进

背景 结合《蚂蚁金服面对亿级并发场景的组件体系设计》&#xff0c;我们能够通盘了解支付宝移动端基础组件体系的构建之路和背后的思考&#xff0c;本文基于服务端组建体系的大背景下&#xff0c;着重探讨“自动化日志手机与分析”在支付宝 App 内的演进之路。 支付宝移动端技…

牛客网SQL篇刷题篇(16-23)

https://www.nowcoder.com/ta/sql 1.SQL嵌套查询 https://www.cnblogs.com/glassysky/p/11559082.html &#xff08;1&#xff09;什么是嵌套查询 .   嵌套查询的意思是&#xff0c;一个查询语句(select-from-where)查询语句块可以嵌套在另外一个查询块的where子句中&…

突破性能极限——阿里云神龙最新ASPLOS论文解读

作者 | 阿里云神龙团队责编 | 徐威龙封图| CSDN 下载于视觉中国日前&#xff0c;ASPLOS 2020公布了计算机界最新科技成果&#xff0c;其中包括阿里云提交的名为《High-density Multi-tenant Bare-metal Cloud》的论文&#xff0c;该论文阐述了阿里云自研的神龙服务器架构如何解…