分布式之抉择分布式锁 (分布式决策是什么意思)

教程大全 2025-07-12 14:56:40 浏览

分布式之抉择分布式锁——多个方位比较

2018-07-17 08:14:22无论是redis还是zookpeer,其实可靠性都存在一点问题。但是,zookpeer的分布式锁的可靠性比redis强太多!但是,zookpeer读写性能不如redis,存在着性能瓶颈。大家在生产上使用,可自行进行评估使用。

引言

为什么写这篇文章?

目前网上大部分的基于zookeeper,和redis的分布式锁的文章都不够全面。要么就是特意避开集群的情况,要么就是考虑不全,读者看着还是一脸迷茫。坦白说,这种老题材,很难写出新创意,博主内心战战兢兢,如履薄冰,文中有什么不严谨之处,欢迎批评。

博主的这篇文章, 分布式之抉择分布式锁 不上代码,只讲分析。

(1)在redis方面,有开源redisson的jar包供你使用。(2)在zookeeper方面,有开源的curator的jar包供你使用

因为已经有开源jar包供你使用,没有必要再去自己封装一个,大家出门百度一个api即可,不需要再罗列一堆实现代码。

需要说明的是,Google有一个名为Chubby的粗粒度分布锁的服务,然而,Google Chubby并不是开源的,我们只能通过其论文和其他相关的文档中了解具体的细节。值得庆幸的是,Yahoo!借鉴Chubby的设计思想开发了zookeeper,并将其开源,因此本文不讨论Chubby。至于Tair,是阿里开源的一个分布式K-V存储方案。我们在工作中基本上redis使用的比较多,讨论Tair所实现的分布式锁,不具有代表性。

因此,主要分析的还是redis和zookeeper所实现的分布式锁。

文章结构

本文借鉴了两篇国外大神的文章,redis的作者antirez的《

Is Redlock safe?

》以及分布式系统专家Martin的《

How to do distributed locking

》,再加上自己微薄的见解从而形成这篇文章,文章的目录结构如下:

(1)为什么使用分布式锁(2)单机情形比较(3)集群情形比较(4)锁的其他特性比较

正文

先上结论:

zookeeper可靠性比redis强太多,只是效率低了点,如果并发量不是特别大,追求可靠性,***zookeeper。为了效率,则***redis实现。 为什么使用分布式锁?

使用分布式锁的目的,无外乎就是保证同一时间只有一个客户端可以对共享资源进行操作。

但是Martin指出,根据锁的用途还可以细分为以下两类:

(1)允许多个客户端操作共享资源这种情况下,对共享资源的操作一定是幂等性操作,无论你操作多少次都不会出现不同结果。在这里使用锁,无外乎就是为了避免重复操作共享资源从而提高效率。(2)只允许一个客户端操作共享资源这种情况下,对共享资源的操作一般是非幂等性操作。在这种情况下,如果出现多个客户端操作共享资源,就可能意味着数据不一致,数据丢失。

***回合,单机情形比较

(1)redis先说加锁,根据redis官网文档的描述,使用下面的命令加锁

SET resource_name my_random_value NX PX 30000

至于解锁,为了防止客户端1获得的锁,被客户端2给释放,采用下面的Lua脚本来释放锁

在执行这段LUA脚本的时候,KEYS[1]的值为resource_name,ARGV[1]的值为my_random_value。原理就是先获取锁对应的value值,保证和客户端穿进去的my_random_value值相等,这样就能避免自己的锁被其他人释放。另外,采取Lua脚本操作保证了原子性.如果不是原子性操作,则有了下述情况出现:

分析:这套redis加解锁机制看起来很***,然而有一个无法避免的硬伤,就是过期时间如何设置。如果客户端在操作共享资源的过程中,因为长期阻塞的原因,导致锁过期,那么接下来访问共享资源就不安全。

可是,有的人会说

那可以在客户端操作完共享资源后,判断锁是否依然归该客户端所有,如果依然归客户端所有,则提交资源,释放锁。若不归客户端所有,则不提交资源啊!

OK,这么做,只能降低多个客户端操作共享资源发生的概率,并不能解决问题。

为了方便读者理解,博主举一个业务场景。

业务场景:我们有一个内容修改页面,为了避免出现多个客户端修改同一个页面的请求,采用分布式锁。只有获得锁的客户端,才能修改页面。那么正常修改一次页面的流程如下图所示:

注意看,上面的步骤(3)–>步骤(4.1)并不是原子性操作。也就说,你可能出现在步骤(3)的时候返回的是有效这个标志位,但是在传输过程中,因为延时等原因,在步骤(4.1)的时候,锁已经超时失效了。那么,这个时候锁就会被另一个客户端锁获得。就出现了两个客户端共同操作共享资源的情况。

大家可以思考一下,无论你如何采用任何补偿手段,你都只能降低多个客户端操作共享资源的概率,而无法避免。例如,你在步骤(4.1)的时候也可能发生长时间GC停顿,然后在停顿的时候,锁超时失效,从而锁也有可能被其他客户端获得。这些大家可以自行思考推敲。 (2)zookeeper

先简单说下原理,根据网上文档描述,zookeeper的分布式锁原理是利用了临时节点(EPHEMERAL)的特性。

分析:这种情况下,虽然避免了设置了有效时间问题,然而还是有可能出现多个客户端操作共享资源的。

大家应该知道,zookeeper如果长时间检测不到客户端的心跳的时候(Session时间),就会认为Session过期了,那么这个Session所创建的所有的ephemeral类型的znode节点都会被自动删除。

这种时候会有如下情形出现

如上图所示,客户端1发生GC停顿的时候,zookeeper检测不到心跳,也是有可能出现多个客户端同时操作共享资源的情形。当然,你可以说,我们可以通过JVM调优,避免GC停顿出现。但是注意了,我们所做的一切,只能尽可能避免多个客户端操作共享资源,无法完全消除。

第二回合,集群情形比较

我们在生产中,一般都是用集群情形,所以***回合讨论的单机情形。算是给大家热热身。

为了redis的高可用,一般都会给redis的节点挂一个slave,然后采用哨兵模式进行主备切换。但由于Redis的主从复制(replication)是异步的,这可能会出现在数据同步过程中, master宕机,slave来不及同步数据就被选为master,从而数据丢失。 具体流程如下所示:

(1)客户端1从Master获取了锁。(2)Master宕机了,存储锁的key还没有来得及同步到Slave上。(3)Slave升级为Master。(4)客户端2从新的Master获取到了对应同一个资源的锁。

为了应对这个情形, redis的作者antirez提出了,步骤如下(该流程出自官方文档),假设我们有N个master节点(官方文档里将N设置成5,其实大等于3就行)

(1)获取当前时间(单位是毫秒)。(2)轮流用相同的key和随机值在N个节点上请求锁,在这一步里,客户端在每个master上请求锁时,会有一个和总的锁释放时间相比小的多的超时时间。比如如果锁自动释放时间是10秒钟,那每个节点锁请求的超时时间可能是5-50毫秒的范围,这个可以防止一个客户端在某个宕掉的master节点上阻塞过长时间,如果一个master节点不可用了,我们应该尽快尝试下一个master节点。(3)客户端计算第二步中获取锁所花的时间,只有当客户端在大多数master节点上成功获取了锁(在这里是3个),而且总共消耗的时间不超过锁释放时间,这个锁就认为是获取成功了。(4)如果锁获取成功了,那现在锁自动释放时间就是最初的锁释放时间减去之前获取锁所消耗的时间。(5)如果锁获取失败了,不管是因为获取成功的锁不超过一半(N/2+1)还是因为总消耗时间超过了锁释放时间,客户端都会到每个master节点上释放锁,即便是那些他认为没有获取成功的锁。

分析:RedLock算法细想一下还存在下面的问题:

节点崩溃重启,会出现多个客户端持有锁

假设一共有5个Redis节点:A, B, C, D, E。设想发生了如下的事件序列:

(1)客户端1成功锁住了A, B, C,获取锁成功(但D和E没有锁住)。(2)节点C崩溃重启了,但客户端1在C上加的锁没有持久化下来,丢失了。(3)节点C重启后,客户端2锁住了C, D, E,获取锁成功。这样,客户端1和客户端2同时获得了锁(针对同一资源)。

为了应对节点重启引发的锁失效问题,redis的作者antirez提出了的概念,即一个节点崩溃后,先不立即重启它,而是等待一段时间再重启,等待的时间大于锁的有效时间。采用这种方式,这个节点在重启前所参与的锁都会过期,它在重启后就不会对现有的锁造成影响。这其实也是通过人为补偿措施,降低不一致发生的概率。

时间跳跃问题

(1)假设一共有5个Redis节点:A, B, C, D, E。设想发生了如下的事件序列:(2)客户端1从Redis节点A, B, C成功获取了锁(多数节点)。由于网络问题,与D和E通信失败。(3)节点C上的时钟发生了向前跳跃,导致它上面维护的锁快速过期。客户端2从Redis节点C, D, E成功获取了同一个资源的锁(多数节点)。客户端1和客户端2现在都认为自己持有了锁。

为了应对始终跳跃引发的锁失效问题,redis的作者antirez提出了应该禁止人为修改系统时间,使用一个不会进行“跳跃”式调整系统时钟的ntpd程序。这也是通过人为补偿措施,降低不一致发生的概率。

超时导致锁失效问题

RedLock算法并没有解决,操作共享资源超时,导致锁失效的问题。回忆一下RedLock算法的过程,如下图所示:

如图所示,我们将其分为上下两个部分。对于上半部分框图里的步骤来说,无论因为什么原因发生了延迟,RedLock算法都能处理,客户端不会拿到一个它认为有效,实际却失效的锁。然而,对于下半部分框图里的步骤来说,如果发生了延迟导致锁失效,都有可能使得客户端2拿到锁。因此,RedLock算法并没有解决该问题。 (2)zookeeper

zookeeper在集群部署中,zookeeper节点数量一般是奇数,且一定大等于3。我们先回忆一下,zookeeper的写数据的原理。

如图所示,这张图懒得画,直接搬其他文章的了。

那么写数据流程步骤如下:

1.在Client向Follwer发出一个写的请求2.Follwer把请求发送给Leader3.Leader接收到以后开始发起投票并通知Follwer进行投票4.Follwer把投票结果发送给Leader,只要半数以上返回了ACK信息,就认为通过5.Leader将结果汇总后如果需要写入,则开始写入同时把写入操作通知给Leader,然后commit;6.Follwer把请求结果返回给Client

还有一点,zookeeper采取的是全局串行化操作。

OK,现在开始分析。

集群同步

client给Follwer写数据,可是Follwer却宕机了,会出现数据不一致问题么?不可能,这种时候,client建立节点失败,根本获取不到锁。client给Follwer写数据,Follwer将请求转发给Leader,Leader宕机了,会出现不一致的问题么?不可能,这种时候,zookeeper会选取新的leader,继续上面的提到的写流程。

总之,采用zookeeper作为分布式锁,你要么就获取不到锁,一旦获取到了,必定节点的数据是一致的,不会出现redis那种异步同步导致数据丢失的问题。

时间跳跃问题

不依赖全局时间,怎么会存在这种问题

超时导致锁失效问题

不依赖有效时间,怎么会存在这种问题

第三回合,锁的其他特性比较

(1)redis的读写性能比zookeeper强太多,如果在高并发场景中,使用zookeeper作为分布式锁,那么会出现获取锁失败的情况,存在性能瓶颈。(2)zookeeper可以实现读写锁,redis不行。(3)zookeeper的watch机制,客户端试图创建znode的时候,发现它已经存在了,这时候创建失败,那么进入一种等待状态,当znode节点被删除的时候,zookeeper通过watch机制通知它,这样它就可以继续完成创建操作(获取锁)。这可以让分布式锁在客户端用起来就像一个本地的锁一样:加锁失败就阻塞住,直到获取到锁为止。这套机制,redis无法实现。

总结

OK,正文啰嗦了一大堆。其实只是想表明两个观点,无论是redis还是zookeeper,其实可靠性都存在一点问题。但是,zookeeper的分布式锁的可靠性比redis强太多!但是,zookeeper读写性能不如redis,存在着性能瓶颈。大家在生产上使用,可自行进行评估使用。


java编程的特点是什么呢

Java语言的特性:简单性:*Java语言的语法特性类似于C++(有没有C++基础没关系,各有各得好处); *Java语言摒弃了C++中容易引发错误的地方,例如指针和内存管理等; *Java 提供了非常丰富的类库供我们使用(丰富不代表包罗万象,很多时候都需要自己写新的类库,我觉得Java更像是给我们提供了丰富的沙子和水泥,想要房子?好啊,你设计吧,设计完了自己盖。 如果和C#比较一下,C#就像是地产商,想要房子?我这里有两居室、三居室和花园洋房,马上就能交钥匙,要哪个?)。 安全性:*不会引起致命的错误; *到目前为止,没有任何一种Java病毒(记得参加SUN公司的JavaOne大会,主持人跑出来非常兴奋的告诉大家:“到目前为止,还没有出现Java病毒。 ”); *基于网络的安全管理机制;可移植性:*Java是一种跨平台的开发语言(就像是金鱼要生活在鱼缸中,鱼缸却能放在不同的家中,金鱼跨平台了吗?没有,跨平台的是鱼缸。 相同的道理,Java运行于虚拟机JVM上,JVM有Windows版、LINUX版、Unix版等,所以Java就跨平台了)。 面向对象性:*Java是一种纯面向对象OOP的编程语言(近年来OOP如日方中,其中Java起到了很大的推动作用,但Java不是第一个,也不是最后一个OOP语言)。 有这么一句经典的,最令初学者头痛的概念:“万物皆为对象”,我在初学的时候就被这句话(忽悠)了一个月。 健壮性:*没有指针使Java 减少了内存出错的可能; *实现了真数组,避免数据覆盖; *异常管理机制。 多线程性:* Java为我们提供了强大的多线程机制。 体系结构中立:* Java是一种不带平台特点的语言(将Java编译成一种文件,可以在任何安装有JVM的机器上运行);解释执行与高性能:* Java 解释器能直接运行目标代码指令(评价:够用、绝对够用)。 分布式:Java 有强大的基于网络的类库供我们使用(有很多著名的支持分布式运算的软件都是使用Java开发)。 动态性:*准确的讲Java不能称为动态语言(动态语言是指程序在运行时可以改变其结构),然而Java的反射机制赋予了它对类动态加载调用的能力,所以很多朋友都称Java为准动态语言。

初学者学习 java 都需要掌握哪些知识。

基本上初学者学到这个程度就可以去找工作了!

第一部分:JavaSE:Java语言最基本的一套库

学习JavaEE或JavaME之前,JavaSE是必学的。

* Java开发环境搭建

* Java基础语法

* 面向对象

* 数组

* 异常

* 集合

* 线程

* IO流

* 反射机制

* 注解Annotation

* 网络编程

第二部分:数据库 【MySQL + JDBC】

* 只要学习编程,数据库是一定要学习的,是一门公共的学科。

* java、C、python、C#等程序员都需要学习数据库。

* 数据库产品很多: MySQL、Oracle、SqlServer、DB2......

* 我们动力节点数据库课程包括:MySQL + Oracle

* Oracle:银行、政府使用oracle的较多。

* MySQL:互联网公司、一般企业使用MySQL较多。

* Oracle我们是提供视频的。 课堂上不讲。

* 我们课堂上讲MySQL。

* Java语言链接数据库:JDBC

第三部分:WEB前端

* 系统结构:B/S【Browser/Server】 C/S【Client/Server】

* WEB是网站的意思。 WEB前端是:网站当中的页面。

* WEB前端程序是运行在浏览器当中的。

* HTML5 + CSS3 + JavaScript(JS)

* WEB前端也有很多框架:

- Bootstrap

- AugularJS

第四部分:JavaWEB

* ajax(是JavaScript的一部分语法,专门做页面局部刷新)

第五部分:JavaWEB项目

* 做一个B/S结构的项目,将WEB前端和JavaWEB内容做一个整合练习。

* 其实到这里为止,所有的系统都可以做了。 但是用的技术很Low。 没有用框架。

memcached和redis的区别

medis与Memcached的区别传统MySQL+ Memcached架构遇到的问题 实际MySQL是适合进行海量数据存储的,通过Memcached将热点数据加载到cache,加速访问,很多公司都曾经使用过这样的架构,但随着业务数据量的不断增加,和访问量的持续增长,我们遇到了很多问题: 需要不断进行拆库拆表,Memcached也需不断跟着扩容,扩容和维护工作占据大量开发时间。 与MySQL数据库数据一致性问题。 数据命中率低或down机,大量访问直接穿透到DB,MySQL无法支撑。 4.跨机房cache同步问题。 众多NoSQL百花齐放,如何选择 最近几年,业界不断涌现出很多各种各样的NoSQL产品,那么如何才能正确地使用好这些产品,最大化地发挥其长处,是我们需要深入研究和思考的问题,实际归根结底最重要的是了解这些产品的定位,并且了解到每款产品的tradeoffs,在实际应用中做到扬长避短,总体上这些NoSQL主要用于解决以下几种问题 1.少量数据存储,高速读写访问。 此类产品通过数据全部in-momery 的方式来保证高速访问,同时提供数据落地的功能,实际这正是Redis最主要的适用场景。 2.海量数据存储,分布式系统支持,数据一致性保证,方便的集群节点添加/删除。 3.这方面最具代表性的是dynamo和bigtable 2篇论文所阐述的思路。 前者是一个完全无中心的设计,节点之间通过gossip方式传递集群信息,数据保证最终一致性,后者是一个中心化的方案设计,通过类似一个分布式锁服务来保证强一致性,数据写入先写内存和redo log,然后定期compat归并到磁盘上,将随机写优化为顺序写,提高写入性能。 free,auto-sharding等。 比如目前常见的一些文档数据库都是支持schema-free的,直接存储json格式数据,并且支持auto-sharding等功能,比如mongodb。 面对这些不同类型的NoSQL产品,我们需要根据我们的业务场景选择最合适的产品。 Redis适用场景,如何正确的使用 前面已经分析过,Redis最适合所有数据in-momory的场景,虽然Redis也提供持久化功能,但实际更多的是一个disk-backed的功能,跟传统意义上的持久化有比较大的差别,那么可能大家就会有疑问,似乎Redis更像一个加强版的Memcached,那么何时使用Memcached,何时使用Redis呢?如果简单地比较Redis与Memcached的区别,大多数都会得到以下观点: 1Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。 2Redis支持数据的备份,即master-slave模式的数据备份。 3Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。 抛开这些,可以深入到Redis内部构造去观察更加本质的区别,理解Redis的设计。 在Redis中,并不是所有的数据都一直存储在内存中的。 这是和Memcached相比一个最大的区别。 Redis只会缓存所有的 key的信息,如果Redis发现内存的使用量超过了某一个阀值,将触发swap的操作,Redis根据“swappability = age*log(size_in_memory)”计 算出哪些key对应的value需要swap到磁盘。 然后再将这些key对应的value持久化到磁盘中,同时在内存中清除。 这种特性使得Redis可以 保持超过其机器本身内存大小的数据。 当然,机器本身的内存必须要能够保持所有的key,毕竟这些数据是不会进行swap操作的。 同时由于Redis将内存 中的数据swap到磁盘中的时候,提供服务的主线程和进行swap操作的子线程会共享这部分内存,所以如果更新需要swap的数据,Redis将阻塞这个 操作,直到子线程完成swap操作后才可以进行修改。 使用Redis特有内存模型前后的情况对比: VM off: 300k keys, 4096 bytes values: 1.3G used VM on:300k keys, 4096 bytes values: 73M used VM off: 1 million keys, 256 bytes values: 430.12M used VM on:1 million keys, 256 bytes values: 160.09M used VM on:1 million keys, values as large as you want, still: 160.09M used当 从Redis中读取数据的时候,如果读取的key对应的value不在内存中,那么Redis就需要从swap文件中加载相应数据,然后再返回给请求方。 这里就存在一个I/O线程池的问题。 在默认的情况下,Redis会出现阻塞,即完成所有的swap文件加载后才会相应。 这种策略在客户端的数量较小,进行 批量操作的时候比较合适。 但是如果将Redis应用在一个大型的网站应用程序中,这显然是无法满足大并发的情况的。 所以Redis运行我们设置I/O线程 池的大小,对需要从swap文件中加载相应数据的读取请求进行并发操作,减少阻塞的时间。 如果希望在海量数据的环境中使用好Redis,我相信理解Redis的内存设计和阻塞的情况是不可缺少的。

本文版权声明本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,请联系本站客服,一经查实,本站将立刻删除。

发表评论

热门推荐