在进行大型网站技术架构设计以及业务实现的过程中,多少都会遇到需要使用分布式锁的情况。那么问题也就接踵而至,哪种分布式锁更适合我们的项目?

下面就这个问题,我做了一些分析:

分布式锁现状:

目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题。

分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。”所以,很多系统在设计之初就要对这三者做出取舍。在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。

在很多场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。有的时候,我们需要保证一个方法在同一时间内只能被同一个线程执行。在单机环境中,Java中其实提供了很多并发处理相关的API,但是这些API在分布式场景中就无能为力了。也就是说单纯的Java Api并不能提供分布式锁的能力。所以针对分布式锁的实现目前有多种方案。

分布式锁实现方案:

分布式锁的实现,目前比较常用的有以下3种方案:

  • 基于数据库实现分布式锁

  • 基于缓存(redis,memcached,tair)实现分布式锁

  • 基于Zookeeper实现分布式锁

在实际落地的时候 会选择实现多个引擎(zk+redis/tair) 方便不同业务使用

分布式锁定义:

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

分布式锁的思考:

在分析这几种实现方案之前我们先来想一下,我们需要的分布式锁应该是怎么样的?(这里以方法锁为例,资源锁同理)

可以保证在分布式部署的应用集群中,同一个方法在同一时间只能被一台机器上的一个线程执行。

  • 这把锁要是一把可重入锁(避免死锁)

  • 这把锁最好是一把阻塞锁(根据业务需求考虑要不要这条)

  • 有高可用的获取锁和释放锁功能

  • 获取锁和释放锁的性能要好

分布式锁具体实现

  • 基于数据库

简单的方式就是建立一张锁表,通过操作该表的数据来实现了。

这种锁的设计是用数据库的乐观锁实现的,可以满足基本的交易的并发以及交易重试的幂等性。      大概实现就是,根据锁字段查找该锁是否存在,如果存在,则判断该锁状态,根据业务需要是否成功拿锁;如果不存在,则插入锁;

当然这种依赖数据库实现锁的缺陷有:

1、这把锁强依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。

2、这把锁没有失效时间,并且锁的数据会一直增长。

3、这把锁只能是非阻塞的,因为数据的insert操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作。

4、这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了。

5、操作数据库需要一定的开销,性能问题需要考虑

其实针对上述我们已经对1和4做了优化:

1.数据库做主从同步

4.为满足可重入,设置了线程号

基于数据库排他锁

除了可以通过增删操作数据表中的记录以外,其实还可以借助数据中自带的锁来实现分布式的锁。

  • 基于缓存实现分布式锁

可以使用缓存来代替数据库来实现分布式锁,这个可以提供更好的性能,同时,很多缓存服务都是集群部署的,可以避免单点问题。并且很多缓存服务都提供了可以用来实现分布式锁的方法,比如Tair的put方法,redis的setnx方法等。并且,这些缓存服务也都提供了对数据的过期自动删除的支持,可以直接设置超时时间来控制锁的释放。

使用缓存实现分布式锁的优点

性能好,实现起来较为方便。

使用缓存实现分布式锁的缺点

通过超时时间来控制锁的失效时间并不是十分的靠谱。

  • 基于Zookeeper实现分布式锁

基于zookeeper临时有序节点可以实现的分布式锁。

大致思想即为:每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。

判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。

当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。

来看下Zookeeper能不能解决前面提到的问题。

  • 锁无法释放?使用Zookeeper可以有效的解决锁无法释放的问题,因为在创建锁的时候,客户端会在ZK中创建一个临时节点,一旦客户端获取到锁之后突然挂掉(Session连接断开),那么这个临时节点就会自动删除掉。其他客户端就可以再次获得锁。

  • 非阻塞锁?使用Zookeeper可以实现阻塞的锁,客户端可以通过在ZK中创建顺序节点,并且在节点上绑定监听器,一旦节点有变化,Zookeeper会通知客户端,客户端可以检查自己创建的节点是不是当前所有节点中序号最小的,如果是,那么自己就获取到锁,便可以执行业务逻辑了。

  • 不可重入?使用Zookeeper也可以有效的解决不可重入的问题,客户端在创建节点的时候,把当前客户端的主机信息和线程信息直接写入到节点中,下次想要获取锁的时候和当前最小的节点中的数据比对一下就可以了。如果和自己的信息一样,那么自己直接获取到锁,如果不一样就再创建一个临时的顺序节点,参与排队。

  • 单点问题?使用Zookeeper可以有效的解决单点问题,ZK是集群部署的,只要集群中有半数以上的机器存活,就可以对外提供服务。

可以直接使用zookeeper第三方库  Curator 客户端,这个客户端中封装了一个可重入的锁服务。

比较三种分布式

分布式锁zk、数据库、以及Redis三者都能实现,同样是分布式锁,三者的区别何在?

从理解的难易程度角度(从低到高):数据库 > 缓存 > Zookeeper

从实现的复杂性角度(从低到高):Zookeeper >= 缓存 > 数据库

从性能角度(从高到低):缓存 > Zookeeper >= 数据库

从可靠性角度(从高到低):Zookeeper > 缓存 > 数据库

本篇文章只是从理论上分析分布式锁的原理及可实现方式,后面我会对3种实现方式结合代码做详细说明介绍,不足之处请多指教!

Java分布式锁实现详解的更多相关文章

  1. Java同步锁全息详解

    一 同步代码块 1.为了解决并发操作可能造成的异常,java的多线程支持引入了同步监视器来解决这个问题,使用同步监视器的通用方法就是同步代码块.其语法如下: synchronized(obj){ // ...

  2. Java精通并发-自旋对于synchronized关键字的底层意义与价值分析以及互斥锁属性详解与Monitor对象特性解说【纯理论】

    自旋对于synchronized关键字的底层意义与价值分析: 对于synchronized关键字的底层意义和价值分析,下面用纯理论的方式来对它进行阐述,自旋这个概念就会应运而生,还是很重要的,下面阐述 ...

  3. java web.xml配置详解(转)

    源出处:java web.xml配置详解 1.常规配置:每一个站的WEB-INF下都有一个web.xml的设定文件,它提供了我们站台的配置设定. web.xml定义: .站台的名称和说明 .针对环境参 ...

  4. java.lang.Thread类详解

    java.lang.Thread类详解 一.前言 位于java.lang包下的Thread类是非常重要的线程类,它实现了Runnable接口,今天我们来学习一下Thread类,在学习Thread类之前 ...

  5. Java并发关键字Volatile 详解

    Java并发关键字Volatile 详解 问题引出: 1.Volatile是什么? 2.Volatile有哪些特性? 3.Volatile每个特性的底层实现原理是什么? 相关内容补充: 缓存一致性协议 ...

  6. Java中日志组件详解

    avalon-logkit Java中日志组件详解 lanhy 发布于 2020-9-1 11:35 224浏览 0收藏 作为开发人员,我相信您对日志记录工具并不陌生. Java还具有功能强大且功能强 ...

  7. Java 8 Stream API详解--转

    原文地址:http://blog.csdn.net/chszs/article/details/47038607 Java 8 Stream API详解 一.Stream API介绍 Java8引入了 ...

  8. java反射机制深入详解

    java反射机制深入详解  转自:http://www.cnblogs.com/hxsyl/archive/2013/03/23/2977593.html 一.概念 反射就是把Java的各种成分映射成 ...

  9. 国际化,java.util.ResourceBundle使用详解

    java.util.ResourceBundle使用详解   一.认识国际化资源文件   这个类提供软件国际化的捷径.通过此类,可以使您所编写的程序可以:          轻松地本地化或翻译成不同的 ...

随机推荐

  1. px em rem的详解与区别

    在前端项目开发中,px,em,以及rem都是页面布局常用的单位,虽然它们是长度单位,但是所含的意义不一样.通过复习和查阅,总结了以下知识. px像素(Pixel) 定义:相对长度单位.像素px是相对于 ...

  2. VC++6.0在win8.1系统下运行失败的解决办法

    在win8.1系统下安装了VC++6,.0编译软件之后,发现打不开.出现下面的错误: 解决办法: 安装文件目录:Microsoft Visual Studio--common--MSDev98--Bi ...

  3. 【Salvation】——登录注册存储数据&验证用户

    写在前面:登录注册功能是在纯Unity3D环境内实现的,用到UGUI绘制界面技术,数据库的部分是后面拓展加进来的,这里数据存储是指存在XML用户文件中. 注册用户名和密码 zc() 用户名和密码登录 ...

  4. javascript高性能写法

    看到一篇不错的博文,如果想写出比较高性能的代码,可参看这个链接http://developer.51cto.com/art/200906/131335.htm

  5. 如何用Android Studio同时使用SVN和Git管理项目

    这篇来讲讲如何在 Android Studio 上同时用 SVN 和 Git 来管理项目.我知道,你肯定会说我吃饱了撑着,没事找事做,为啥要同时用 SVN 和 Git 来管理项目.问题是,我也不想啊, ...

  6. ASP.NET Core 认证与授权[4]:JwtBearer认证

    在现代Web应用程序中,通常会使用Web, WebApp, NativeApp等多种呈现方式,而后端也由以前的Razor渲染HTML,转变为Stateless的RESTFulAPI,因此,我们需要一种 ...

  7. 关于laravel5.5控制器方法参数依赖注入原理深度解析及问题修复

    在laravel5.5中,可以根据控制器方法的参数类型,自动注入一个实例化对象,极大提升了编程的效率,但是相比较与Java的SpringMVC框架,功能还是有所欠缺,使用起来还是不太方便,主要体现在方 ...

  8. A Simple Math Problem(矩阵快速幂)(寒假闭关第一题,有点曲折啊)

    A Simple Math Problem Time Limit: 3000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Other ...

  9. 0_Simple__inlinePTX + 0_Simple__inlinePTX_nvrtc

    在核函数代码中加入并行线程执行(Parallel Thread eXecution,PTX),通过汇编指令获取得有关线程束的信息.并且在静态代码和运行时编译两种条件下使用. ▶ 源代码:静态使用 #i ...

  10. HDU3045 Picnic Cows (斜率DP优化)(数形结合)

    转自PomeCat: "DP的斜率优化--对不必要的状态量进行抛弃,对不优的状态量进行搁置,使得在常数时间内找到最优解成为可能.斜率优化依靠的是数形结合的思想,通过将每个阶段和状态的答案反映 ...