分布式环境下的解决方案——分布式锁

分布式锁,也就是在多进程情况下的锁。

多进程的锁 跟 多线程的 锁 有什么相同的地方呢?

  1. 需要有存储锁的空间,并且锁的空间是可以访问到的。
  2. 锁需要被唯一标识。
  3. 锁要有至少两种状态。

储存空间:

锁是一个抽象的概念,锁的实现,需要依存于一个可以存储锁的空间。在多线程中是内存,在多进程中是内存或者磁盘。更重要的是,这个空间是可以被访问到的。多线程中,不同的线程都可以访问到堆中的成员变量;在多进程中,不同的进程可以访问到共享内存中的数据或者存储在磁盘中的文件。但是在分布式环境中,不同的主机很难访问对方的内存或磁盘。这就需要一个都能访问到的外部空间来作为存储空间。

最普遍的外部存储空间就是数据库了,事实上也确实有基于数据库做分布式锁(行锁、version乐观锁),如quartz集群架构中就有所使用。除此以外,还有各式缓存如Redis、Tair、Memcached、Mongodb,当然还有专门的分布式协调服务Zookeeper,甚至是另一台主机。只要可以存储数据、锁在其中可以被多主机访问到,那就可以作为分布式锁的存储空间。

唯一标识:

不同的共享资源,必然需要用不同的锁进行保护,因此相应的锁必须有唯一的标识。在多线程环境中,锁可以是一个对象,那么对这个对象的引用便是这个唯一标识。多进程环境中,信号量在共享内存中也是由引用来作为唯一的标识。但是如果不在内存中,失去了对锁的引用,如何唯一标识它呢?上文提到的有名信号量,便是用硬盘中的文件名作为唯一标识。因此,在分布式环境中,只要给这个锁设定一个名称,并且保证这个名称是全局唯一的,那么就可以作为唯一标识。

至少两种状态:

为了给临界区加锁和解锁,需要存储两种不同的状态。如ReentrantLock中的status,0表示没有线程竞争,大于0表示有线程竞争;信号量大于0表示可以进入临界区,小于等于0则表示需要被阻塞。因此只要在分布式环境中,锁的状态有两种或以上:如有锁、没锁;存在、不存在等等,均可以实现。

数据库:去判断某个字段的状态 来判断是否能得到锁。

redis:根据 setnx 命令 来判断, 如果返回1 则获取到了锁,否则就是没获取到,死锁,锁的有效期的问题点:

zookeeper : 1、通过注册临时节点的方式,能注册成功会返回节点的名称,就是获取到了锁,

                           2、去创建最小临时有序节点,通过一系列的判断,当注册的节点是最小节点时就获取到了锁。临时节点消失也就意味着锁释放。

通过了解分布式锁 跟 普通 的锁的相同点之后,其实自己就可以实现一个简单的分布式锁,但为什么要用redis或者是 zookeeper这样复杂的中间件呢?

因为redis 跟zookeeper 会给我们解决下面这些问题:

  • 问题1:锁状态判断原子性无法保证 从读取锁的状态,到判断该状态是否为被锁,需要经历两步操作。如果不能保证这两步的原子性,就可能导致不止一个请求获取到了锁,这显然是不行的。因此,我们需要保证锁状态判断的原子性。
  • 问题2:网络断开或主机宕机,锁状态无法清除 假设在主机已经获取到锁的情况下,突然出现了网络断开或者主机宕机,如果不做任何处理该锁将仍然处于被锁定的状态。那么之后所有的请求都无法再成功抢占到这个锁。因此,我们需要在持有锁的主机宕机或者网络断开的时候,及时的释放掉这把锁。
  • 问题3:无法保证释放的是自己上锁的那把锁 在解决了问题2的情况下再设想一下,假设持有锁的主机A在临界区遇到网络抖动导致网络断开,分布式锁及时的释放掉了这把锁。之后,另一个主机B占有了这把锁,但是此时主机A网络恢复,退出临界区时解锁。由于都是同一把锁,所以A就会将B的锁解开。此时如果有第三个主机尝试抢占这把锁,也将会成功获得。因此,我们需要在解锁时,确定自己解的这个锁正是自己锁上的。

在实际的情况下,还有这些问题:

  1. 可重入:线程中的可重入,指的是外层函数获得锁之后,内层也可以获得锁,ReentrantLock和synchronized都是可重入锁;衍生到分布式环境中,一般仍然指的是线程的可重入,在绝大多数分布式环境中,都要求分布式锁是可重入的。
  2. 惊群效应(Herd Effect):在分布式锁中,惊群效应指的是,在有多个请求等待获取锁的时候,一旦占有锁的线程释放之后,如果所有等待的方都同时被唤醒,尝试抢占锁。但是这样的情况会造成比较大的开销,那么在实现分布式锁的时候,应该尽量避免惊群效应的产生。
  3. 公平锁和非公平锁:不同的需求,可能需要不同的分布式锁。非公平锁普遍比公平锁开销小。但是业务需求如果必须要锁的竞争者按顺序获得锁,那么就需要实现公平锁。
  4. 阻塞锁和自旋锁:针对不同的使用场景,阻塞锁和自旋锁的效率也会有所不同。阻塞锁会有上下文切换,如果并发量比较高且临界区的操作耗时比较短,那么造成的性能开销就比较大了。但是如果临界区操作耗时比较长,一直保持自旋,也会对CPU造成更大的负荷。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏小特工作室

Navi.Soft31.WinCE框架.开发手册(含下载地址)

1.概述 1.1应用场景 随着物联网的普及,越来越多的制造商对货品从原料配备,加工生产,销售出库等环节的要求和把控越来越高.在此情况之下,传统的ERP软件已经无...

1985
来自专栏Android开发实战

android Ndk JNI 入门

相信很多人对于NDK 开发都是懵逼的,比如都知道把加密的逻辑以及密钥放在so文件中,但是你知道怎么生成so文件吗?你知道开发jni吗?你熟悉jni语法吗?怎么配...

1532
来自专栏Laoqi's Linux运维专列

Memcached基础了解

1163
来自专栏黑泽君的专栏

什么是SOA

参考链接:https://blog.csdn.net/u011225629/article/details/73558087

6442
来自专栏FreeBuf

如何从内存提取LastPass中的账号密码

简介 首先必须要说,这并不是LastPass的exp或者漏洞,这仅仅是通过取证方法提取仍旧保留在内存中数据的方法。之前我阅读《内存取证的艺术》(The Art ...

2728
来自专栏JetpropelledSnake

ELK学习笔记之Logstash和Filebeat解析对java异常堆栈下多行日志配置支持

logstash官方最新文档。 假设有几十台服务器,每台服务器要监控系统日志syslog、tomcat日志、nginx日志、mysql日志等等,监控OOM、内存...

4881
来自专栏菩提树下的杨过

spring: 加载远程配置

通常在spring应用中,配置中的properties文件,都是打包在war包里的,部署规模较小,只有几台服务器时,这样并没有什么大问题。如果服务器多了,特别是...

3538
来自专栏圣杰的专栏

Asp.net web api 知多少

本系列主要翻译自《ASP.NET MVC Interview Questions and Answers 》- By Shailendra Chauhan,想...

2535
来自专栏游戏杂谈

恢复低版本的FlashPlayer

做页游开发,经常会遇到一些很奇怪的问题。我最近就遇到一个问题:用户进入游戏,只显示游戏部分界面,chrome浏览器是正常的,就IE死活不行,而我自己的IE却又...

5433
来自专栏Java架构师历程

ssh工作原理

1.通过Configuration().configure();读取并解析hibernate.cfg.xml配置文件

1403

扫码关注云+社区

领取腾讯云代金券