加载中...
02redis分布式锁分析与cluster模式搭建
发表于:2021-10-20 | 分类: redis
字数统计: 3.2k | 阅读时长: 11分钟 | 阅读量:

1.redis分布式锁的问题

  1. 第一种方案,基于redis单实例,不靠谱,因为redis单点故障,会导致系统全盘崩溃,做不到高可用,除非你是那种不太核心的小系统,随便用一下分布式锁,你可以这么弄,应该还好,毕竟对高可用要求没那么高

  2. 第二种方案,基于redis主从架构+哨兵,保证高可用,master宕机,slave接替,但是有隐患,master宕机的一瞬间,还没异步复制锁到slave,导致重复加锁的问题,高可用是高可用了,但是锁的实现有漏洞,可能导致系统异常,客户端刚在master实例加了一个锁,但是master->slave的复制数据(锁复制过去)是异步的,导致master突然宕机,此时锁还没复制到slave,然后master->slave主备切换(哨兵),客户端B此时也对同一个key上锁,此时就会成功的在切换为master的slave实例上加锁,客户端A和客户端B同时对一个key完成了上锁,只要多个客户端同时对某个key上锁,就会导致数据肯定会出错

  3. 第三种方案,基于redis多master集群(redis-cluster,或者redis部署多机器,twitter开源的twemproxy做客户端集群分片,豌豆荚开源的codis做集群分片,都行),redlock算法,个人不推荐,实现过程太复杂繁琐,很脆弱,多节点同时设置分布式锁,但是失效时间都不一样,随着不同的linux机器的时间不同步,以及各种你无法考虑到的问题,很可能出现重复加锁

举个例子,给5个redis master都设置了一把锁key,失效时间是10s,但是因为设置key的时间可能因为网络等各种情况不同,会导致,也许客户端A还加着锁呢,因为处理耗时特别长,然后结果过了几秒钟,其中3台机器的锁key都过期失效了,因为这3台机器加锁的时间都比较靠前,已经10秒过期了,此时分布式锁自然失效,客户端B成功加锁,出现两个客户端同时加锁的问题

所以redlock算法,有两个问题,第一是实现过程和步骤太复杂,上锁的过程和机制很重,很复杂,导致很脆弱,各种意想不到的情况都可能发生;第二是并不健壮,不一定能完全实现健壮的分布式锁的语义

http://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html

这是国外一个牛人写的redlock算法的分析小论文,大家可以看一下,里面的结论就是我时候的那两个,第一是太复杂,第二是不健壮可能加锁会失败

当然redis作者不赞同他,但是同样redis作者自己也没法证明他的算法是健壮的,所以这个方案不建议大家采用

因为redis RedLock分布式锁的算法,涉及过程比较复杂,涉及到很多不同情况下的时间问题,就会导致说,你如果要证明这个算法是ok的,到最后其实会成为一个数学问题,你要从数学和逻辑的角度,大量的推算,证明说,这个算法,无论在任何极端的情况下都是成功的

综上所述,redis分布式锁实际上目前没有100%完美的方案,或多或少有点问题,实际生产系统中,我设计的系统有时候用zk分布式锁,但是有的时候也会用redis分布式锁。

redis锁又有一个优点,redisson,知名的、优秀的开源的redis客户端类库,他封装了大量的基于redis的复杂的一些操作,数据集合(map、set、list)的分布式的存储,还有多种复杂的分布式锁,分布式执行操作以及对象,甚至就是说基于redis+redisson,甚至都可以将redis作为一个轻量级的NoSQL数据库、数据存储来使用

redisson:redis分布式锁的支持,非常不错,可重入锁、读写锁、公平锁、信号量、CountDownLatch,很多种复杂的锁的语义,可以支持我们将分布式锁玩儿的非常的好

但是zk分布式锁呢?其实有优点也有缺点,优点是锁模型健壮、稳定、可用性高,缺点是目前没有太好的开源的zk分布式锁的类库,封装多种不同的锁类型,因为有可重入锁、读写锁、公平锁等多种锁类型,但是zk的话,目前常见的方案,就是自己动手封装一个基于顺序节点的普通的悲观锁,没有复杂的锁机制的支持

你对redis分布式锁的使用,用是可以用的,但是你要知道他的优势和劣势,如果你可以容忍一些劣势,以及做一些对应的措施和预案,后台可以容忍做补数据的操作,而且你对分布式锁的需求很旺盛,需要人家各种高级分布式锁的一些支持

但是如果有zookeeper的环境,而且你的分布式锁的需求很简单,就是普通的悲观锁模型就可以了,不涉及到什么公平锁、读写锁之类的东西,那么可以考虑基于zk的分布式锁模型,健壮,稳定,zk的临时顺序节点实现的分布式锁,其实那套模型挺健壮的,zk本身就是集群多节点分布式高可用,分布式系统协调、处理的,支持分布式锁的时候,基于zk的一些语义,监听节点的变化

zk锁,优点就是健壮、稳定、简单、易懂;缺点就是没有太好的开源的客户端类库,对zk分布式锁的封装和支持,可以支持那么多种不同的高阶的锁类型,公平锁、可重入锁、读写锁、信号量、CountDownLatch,Curator客户端主要还是针对zk的一些基础的语义做的一些封装,redis的Jedis + Redisson结合起来,Jedis封装redis的一些基础的语义,一些操作,都是不错的

目前行业里,基本redis和zk做分布式锁,都有,具体看架构师自己对这个东西怎么理解和考量了,redis锁没有特别好的100%完美和健壮的锁模型,或多或少会导致一些问题,如果你在一些业务场景下,能容忍这些问题,同时需要redission的复杂锁类型的支持,可以用redis锁;但是如果你的业务场景要求锁的语义绝对没问题,很健壮,很稳定,绝对不会导致说多个客户端同时加到一把锁,100%不会发生,而且对锁的功能没有特别的需求,那么可以用zk锁

但是我说下我个人的角度,和经验,以及对待技术的一些态度,来理解的。设计架构为什么偏好用zk分布式锁,原因两个,第一是redis锁的算法模型目前业内方案都有隐患,心里不安,zk锁的机制更加健壮和稳定;第二是我设计架构,喜欢考虑到技术的本质问题,redis本质是什么?其实说白了,还是缓存!redis现在越来越往什么可以实现队列的功能、分布式锁的功能、发布订阅的功能,如果我是redis作者,我个人觉得本末倒置了

要敢于质疑一下那些技术权威,如果我是作者开源的发起人,我会回归他的本质,他的本质是一个kv类的缓存,如果要发展,应该是纵向发展,比如支持磁盘存储,做成可以支持磁盘+内存的大规模,海量数据的kv存储,这是他可以的发展方向

而且作为开源项目,rabbitmq,就考虑到一些集群的管理和运维的一些操作,提供了漂亮的可视化的界面,让人可以管理。

完全可以提供更加方便的全可视化的界面管理工作台,方便运维和管理,包括一键部署集群,集群上下线节点,不同的集群模式的一键转换(单实例模式 -> 主从模式 -> 哨兵模式 -> 集群模式),数据迁移,数据备份,一键集群版本滚动升级,子集群模式与业务隔离,热key大value的自动发现与报警,集群资源的监控与报警,多机房集群部署容灾,集群访问量监控与扩容预警,等等吧。

这些东西,现在有一个CacheCloud(搜狐开源的,自己弄的)在搞,还有各大公司,都是基础架构团队自己在搞

以我设计大量的系统的经验而言,我真的觉得redis还支持什么lua脚本,但是做一个轻量级的支持就可以了,大部分情况下肯定都是不需要使用这么重的操作的,hyperloglog

但是让我失望的是,redis cluster,居然刚搞出来的时候,还不支持天然的读写分离,slave纯就是做高可用自动切换的,简直是在搞笑,而且极其繁琐的运维,还搞什么乱七八糟的RedLock分布式锁算法,队列支持,发布订阅支持,我觉得简直是在瞎搞

redis天然就不是为了分布式锁这种分布式系统的基础组件来设计的,zk才是最适合做各种分布式系统的基础设施依赖的,而且业界基本各大开源项目,都是依赖的zk做各种分布式系统的基础设施

所以国内的工程师们,要开始有点自己的技术判断力了,不要人云亦云,老外做什么就认可什么

下一讲我们还是给大家来演示一下redis的分布式锁的使用,其实一般当然不会自己傻呵呵写一堆代码去实现redis分布式锁的客户端代码了,那太繁琐了,一般就是用redisson这个客户端框架,他有完整的一套redis分布式锁的实现

我们出于技术完整性的考虑,还是给大家仔细说一说,先是部署几台虚拟机,然后部署个redis集群,使用redis cluster就好了,然后基于目前比较好的redis开源客户端框架,redisson来做一个分布式锁的学习,重点是读一下人家客户端框架的源码

毕竟在行业里,redis分布式锁其实用的也是很多的

2.window上安装docker,安装3主3从redis cluster

1、拉镜像

1
2
3
4
5

docker pull redis:3.0.7

docker pull ruby

2、创建文件夹

image-20211020165829201

3、下一份redis的源码将redis.conf弄出来改吧改吧 webget http://download.redis.io/releases/redis-3.0.7.tar.gz

image-20211020170027575

4、dockerfile:

1
2
3
4
5
6
7
8
9

FROM redis:5.0.3

EXPOSE 6379

ADD redis.conf /redis.conf

ENTRYPOINT [ "redis-server", "/redis.conf" ]

5、在当前目录下执行制作镜像

1
2
3

docker build -t "redis-cluster" .

6、创建network

1
2
3

docker network create --subnet=172.10.0.0/16 redis1

7、分别执行开启6个redis节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

docker run -d --net redis1 --ip 172.10.0.91 -p 8001:6379 --name redis-test1 redis-cluster

docker run -d --net redis1 --ip 172.10.0.92 -p 8002:6379 --name redis-test2 redis-cluster

docker run -d --net redis1 --ip 172.10.0.93 -p 8003:6379 --name redis-test3 redis-cluster

docker run -d --net redis1 --ip 172.10.0.94 -p 8004:6379 --name redis-test4 redis-cluster

docker run -d --net redis1 --ip 172.10.0.95 -p 8005:6379 --name redis-test5 redis-cluster

docker run -d --net redis1 --ip 172.10.0.96 -p 8006:6379 --name redis-test6 redis-cluster



image-20211020170532284

8、然后在你的工作目录上再创建一个空文件夹,里面放入 redis-trib.rb和Dockerfile

image-20211020170642139

1
2
3
4
5

FROM ruby

ADD redis-trib.rb /redis-trib.rb

image-20211020170756725

9、在当前目录制作镜像

1
2
3

docker build -t "ruby-redis" .

10、启动镜像

1
2
3

docker run --net=redis1 --ip 172.10.0.100 --name ruby11 -i -d ruby-redis

11、这样就打开了ruby容器,docker exec -it ruby11 /bin/bash 进入创建的容器。然后执行

1
2
3

gem install redis --version 3.0.7

12、用 –version 来指定特定版本的redis插件,然后执行

1
2
3

./redis-trib.rb create --replicas 1 172.10.0.91:6379 172.10.0.92:6379 172.10.0.93:6379 172.10.0.94:6379 172.10.0.95:6379 172.10.0.96:6379

image-20211020171237598

13、开客户端验证

image-20211020171439170

参考

上一篇:
java多线程加速遍历解析
下一篇:
01redis和zk锁的实现原理
本文目录
本文目录