分布式锁原理的学习与思考——redis、zookeeper
分布式锁和我们平时讲的锁原理基本是一样的。目标是保证当有多个线程同时存在时,一次只有一个线程操作store,或者方法,变量。
在一个进程中,即在JVM或应用程序中,我们可以轻松地管理控制。 jdk java.util并发包为我们提供了这些加锁的方法,例如synchronized关键字或者Lock锁都可以进行处理。
但是如果我们当前的应用只部署一台服务器,并行性就很弱了。数以万计的请求同时进来很可能会导致服务器压力过大而瘫痪。
想想像双十一和付宝的三十夜十小时红包这样的业务场景。使用多台服务器同时处理这些业务是很自然的。那么这些服务可以同时由数百台服务器处理,
但是试想如果有100台服务器来处理红包业务,现在我们假设有1亿个红包,除以10。万人,金额是随机的,那么在这个业务场景中我们需要保证这1000万人最终红包的总金额是1亿。
管理不好的话~~大家都拿到100万那马云爸爸估计大年初一就破产了~~
1。那么传统锁呢?
首先我们来说说为什么要构建集群。简单理解就是需求(请求的同时性)增加了,一个worker的处理能力有限,所以需要招更多的worker来一起处理。
假设 1000 万个请求均匀分布在 100 台服务器上,每台服务器收到 10 万个请求(这 10 万个请求不是同一秒到来的,而是可能在 1、2 小时内发生,下次考虑一下。 30. 打开晚上发红包,10.20开始的时候,有的人立马打开,有的人直到12点才记得~)
这样的话平均每秒请求数:不到1000个服务器,这个正常压力还是可以忍受的。
收到第一个请求后,你是否必须给他一亿的一部分?金额是随机的。假设第一个人得到了100,他是否应该从1个亿中减去100元,剩下的金额第二个用户分成99999900份~
第二个用户随机分配金额。这次将被分成200块。那么必须从剩下的99999900枚中再减去200枚,剩下99999700枚。
如果十万来了。用户看到还有1,000,000,那么这1,000,000都是他的。
这相当于每台服务器之间划分了1亿,即10万用户划分为1亿。最终总共有100台服务器,分布了100亿。
如果真是这样的话,虽然马云和爸爸不会破产(根据最新统计马云有2300亿),但是领取奖金包的开发项目组和产品经理GG~
简化结构图如下:
![]()
2。我们如何处理分布式锁?
为了解决这个问题,我们把1000万用户分成1亿,而不是100亿。这就是分体锁派上用场的地方。
分布式锁可以把整个集群当作一个应用程序,所以这个锁必须是独立于各个服务的,而不是在一个服务内部。
假设第一台服务器收到用户1的请求后,不能再仅仅判断自己的应用程序中可以分配多少钱,而是必须向外部发出请求来管理这1亿。 。红包人(服务)问他:哎,我这里要分100块,给我100吧。
处理红包(服务)的女孩看到还剩下一亿。嗯,我给了100块钱,那么我还剩下99999900块钱。
收到第二个请求后,服务器2收到了。我继续询问那个处理红包的女孩。我想分10元。办理红包的女孩先查了一下,有99999900,于是她说:好,给你10块钱。还剩下99999890块
当第1000万个请求到来时,服务器100收到请求继续询问,拿红包的女孩要100个,女孩垂下眼睛说他说只有1个。剩下一块。 ,你想让我爱你吗?目前只能给1元(1元是钱,还是可以买萝卜面的)。
这些请求1和2并不代表执行顺序。官方场景应该有100台服务器。每个服务器都有一个请求,访问负责处理红包的女孩(服务)。负责红包的女孩一次有100个请求。这时候就必须给负责红包的女孩加一把锁(扔绣球花)。谁从100个服务器中获得了锁(抢到了绣球花),就进来和我说话。 ,我给你积分,其他人等着。
处理完上面的裂锁后,马云和爸爸终于松了口气,决定给红包队的每人发一只鸡腿。
简化结构图如下:
![]()
3。分布式锁有哪些实现方式?
说到实现分布式锁,还有很多,包括数据库方式、redis分布式锁、zookeeper分布式锁等等,负责任的姑娘(服务)》可以换成redis吗,请决定。
3.1、redis为什么可以实现分布式锁?
首先,redis是单链的。这里的单线程是指网络请求模块使用一个线程(所以不考虑并发安全),即一个线程处理所有网络请求,其他模块使用多个线程。 。
在实际操作中,流程大致如下:
服务器1想要访问发红包的妹子,也就是redis,然后通过“setnx key value”操作在redis中设置一个key。 。价值是多少并不重要。重要的是你有一个密钥,它是一个标签,如果所有服务器都有相同的密钥,你可以随意称呼这个密钥。
假设我们设置如下 1
![]()
然后我们可以看到我们返回 1 表示成功。
如果有另一个请求设置相同的密钥,如下:
![]()
此时会返回0,表示失败。
然后,通过这个动作,我们可以判断当前锁是否可用,或者访问“负责发红包的女孩”。如果它返回 1,那么我开始执行以下逻辑。如果返回0,则说明已经很忙,我还在等待。
服务器1收到锁后,进行业务处理。完成后需要解锁如下图:
![]()
删除成功返回1,然后其他服务器可以继续重复以上步骤进行设置。该密钥用于获取锁。
当然,以上操作都是直接在redis客户端进行的。如果我们通过程序调用的话,肯定不能这样写。比如java必须通过jedis来调用,但是整个处理逻辑基本是一样的
通过上面这样看来我们已经解决了分布式锁的问题,但是仔细想想,是不是还有其他的问题呢? ?
是的,仍然有问题。可能会出现死锁问题。例如,服务器1设置并获取锁后,突然关闭。
之后,无法进行后续的密钥删除操作。这个key会一直存在于redis中。每次其他服务器检查它时,它们都会返回 0。他们认为有人正在使用锁,我必须等待。
为了解决死锁问题,我们需要设置密钥有效期。
您可以通过两种方式进行设置
1。第一种是设置key后直接设置key的“expire key timeout”,设置key的超时时间,单位是秒。如果超过这个超时时间,就会自动释放,避免死锁。
这个方法相当于把锁的有效期传递给redis检查。如果超时了,你没有给我删除key,redis会直接给你删除,其他服务器可以继续setnx获取锁。
2。第二种方式是将删除密钥的权利转移给其他服务器。那么你应该使用该值。
例如服务器1设置的值为超时,即当前时间+1秒。那么服务器2通过get判断时间已经超过了当前系统时间,也就是说服务器1还没有解锁。服务器 1 可能有问题。
服务器2开始删除key操作,并继续执行setnx操作。 。
不过这样有一个问题,就是不仅你的服务器2可能发现服务器1超时了,服务器3也可能发现,如果出现这种情况,服务器2,setnx操作完成,服务器A 3 还在删除吗? Server 3也能设置成功吗?
这意味着服务器2和服务器3也获得了锁,这是一个大问题。这种情况我们应该怎么办呢?
目前需要使用“GETSET key value”。该命令的意思是获取当前键的值并设置一个新值。
假设2号服务器判断key已经过期,开始调用getset命令,然后根据接收到的时间判断是否过期。如果给定的时间仍然过期,则意味着锁定完成。
如果不是,说明在服务2执行getet之前,服务器3也可以判断出锁已经过期,并在服务器2之前执行getet操作,重置过期时间。
那么服务器2必须放弃进一步的操作,继续等待,直到服务器3释放锁或者观察密钥是否过期。
这方面其实有点问题。服务器3更改了有效期。 Server 2获取锁后,也更改了有效期,但获取锁失败。但是,服务器3中的有效期发生了变化。基于此,有一些增加,但这个影响实际上很小,几乎可以忽略不计。
3.2。为什么zookeeper可以实现分布式锁?
百度百科是这样介绍的:ZooKeeper是一个分布式、开源的分布式应用协调服务。它是 Google Chubby 的开源实现,也是 Hadoop 和 Hbase 的重要组件。
对于第一次见面的人来说,我们可以理解ZooKeeper就像我们的计算机文件系统。我们可以在d盘创建文件夹,在该文件夹中我们可以继续创建文件夹a1和a2。
我们的文件系统有什么特点? ?即同一个目录下文件名不能重复,ZooKeeper 也是如此。
所有ZooKeeper节点,即文件夹,都被命名为Znode,这个Znode节点可以存储数据。
您可以使用“create /zkjjj Nice”命令创建节点。该命令的意思是在以下目录创建一个zkjjj节点,值nice。同样,这里的值和前面提到的redis中的值是一样的。没有道理。你可以随心所欲地给予。
另外,ZooKeeper 可以创建 4 种类型的节点,分别是:
1、永久节点
2、永久顺序节点
3、临时3、临时
首先,让我们谈谈关于永久节点和临时节点的区别。持久节点是指只要创建了这个节点,ZooKeeper服务器就会记录这个节点,无论ZooKeeper客户端是否宕机。 临时节点正好相反。 ZooKeeper客户端断开连接后,ZooKeeper服务器不再保存该节点。 我们来谈谈顺序节点。顺序节点是指当创建一个节点时,ZooKeeper会自动给该节点编号,例如0000001、0000002。 最后,zookeeper还有一个监控机制。客户端注册以监视对其重要的目录节点。如果目录节点发生变化(数据变更、删除、子目录节点增删)等,zookeeper会通知客户端。 我们继续结合上面的奖励包场景,描述一下如何锁定zookeeper。 假设服务器 1 创建一个节点/zkjjj。如果成功,服务器 1 就会获取锁。如果服务器 2 重新创建相同的锁,则会失败。此时只能监控该节点。改变。 当服务器1处理完交易并删除节点时,它会收到通知,然后创建相同的节点,获取锁来处理交易,然后删除该节点。下面的100台服务器会类似 注意这里的100台服务器并不是单独执行上面的节点创建操作,而是并行的。当服务器1成功创建后,剩下的99个将注册监听该节点并等待通知,以此类推。 但是你发现这里还有问题,还是会陷入僵局,对吧? 如果服务器1创建节点后挂了,无法删除,其他99台服务器仍然会等待通知,就结束了。 。 。 您当前必须使用临时节点。前面提到,临时节点的特点是客户端断开连接后就会丢失。即服务器1创建节点时,如果冻结了。 该节点随后会自动删除,以便其他下游服务器可以继续创建节点并获取锁定。 但是我们可能还要注意一件事,那就是令人着迷的羊群效应:举个很简单的例子,当你把一块食物扔到一群鸽子中间时,虽然只有一只鸽子会抢到最后的食物,所有的鸽子都惊呆了。 Race,no catch... 当服务器节点 1 发生变化时,通知其余 99 个服务器,但最终只有 1 个服务器创建成功,所以 98 个仍然要等待监控,所以处理这种情况,暂时你必须使用顺序节点 大致意思是以前99台服务器都监听一个节点,但现在每台服务器都监听它之前的节点。 假设有 100 台服务器同时发送请求,则会在 /zkjjj /zkjjj/000000001、/zkjjj/000000002、/zkjjj/000000002 下创建 100 个临时顺序节点,直至 /zkjjj/000000002,顺序为设置了获取哪些锁。 节点001处理并删除节点后,节点002收到通知,获得锁,开始执行。执行完成后,节点被删除,通知003~等等。
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网