分布式锁

原文

分布式锁:

  • 原理:与常规锁的原理相同。
    • 目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法、变量。
      • 在一个进程中,也就是一个jvm 或者说应用中,我们很容易去处理控制,在jdk java.util 并发包中已经为我们提供了这些方法去加锁, 比如synchronized 关键字 或者Lock 锁,都可以处理。
  • 背景:现在的应用程序如果只部署一台服务器,那并发量是很差的,如果同时有上万的请求那么很有可能造成服务器压力过大,而瘫痪。自然需要用到多台服务器去同时处理这些业务,那么这些服务可能会有上百台同时处理
    • 双十一 和 三十晚上十点分支付宝红包等业务场景
    • 如果有100台服务器 要处理分红包的业务,现在假设有1亿的红包,1千万个人分,金额随机,那么这个业务场景下是不是必须确保这1千万个人最后分的红包金额总和等于1亿。
    • 如果处理不好~~每人分到100万,那马云爸爸估计大年初一,就得宣布破产了~~
  • 常规锁的局限性:
    • 为什么要搞集群,简单理解就是,需求量(请求并发量)变大了,一个工人处理能力有限,那就多招一些工人来一起处理。  
      • 假设1千万个请求平均分配到100台服务器上,每个服务器 接收10w的请求(这10w个请求并不是在同一秒中来的,可能是在1,2个小时内,可以联想下我们三十晚上开红包,等到10.20开始,有的人立马开了,有的人是不是等到12点了才想起来~)
        • 平均到每一秒上的请求也就不到1千个,这种压力一般的服务器还是可以承受的。
        • 第一个请求到来后,是不是需要在1亿里面给他分一部分钱,金额随机,假设第一个人分到了100,那是不是要在这1亿中减去100块,剩下99999900 块~
          • 第二个用户再来分,金额随机,这次分200块,那是不是就需要在剩下的99999900块中再减去200块,剩下99999700 块。
          • 等到第10w个用户来,一看还有1000w,那这1000w全成他的了。
          • 等于是在每个服务器中去分1亿,也就是10w个用户分了一个亿,最后总计有100个服务器,要分100亿。
          • 如果真这样了,虽说马云爸爸不会破产(据最新统计马云有2300亿人民币),那分红包的开发项目组,以及产品经理,可以GG了~
  • 分布式锁的优势
    • 分布式锁可以把整个集群就当作是一个应用一样去处理,那么也就需要这个锁,要独立于每一个服务之外,而不是在服务里面。
      • 为了解决这个问题,让1000万用户只分1亿,而不是100亿,这个时候分布式锁就派上用处了
        • 假设第一个服务器接收到用户1的请求后,那么这个时候,他就不能只在自己的应用中去判断还有多少钱可以分了,而需要去外部请求专门负责管理这1亿红包的人(服务),问他:哎,我这里要分100块,给我100。管理红包的妹子(服务)一看,还有1个亿,那好,给你100块,然后剩下99999900块。
        • 第二个请求到来后,被服务器2获取,继续去询问,管理红包的妹子,我这边要分10块,管理红包的妹子先查了下还有99999900,那就说:好,给你10块。那就剩下99999890块 
        • 等到第1000w个请求到来后,服务器100拿到请求,继续去询问,管理红包的妹子,你要100,妹子翻了翻白眼,对你说,就剩1块了,爱要不要,那这个时候就只能给你1块了(1块也是钱啊,买根辣条还是可以的)。
      • 这些请求编号1,2不代表执行的先后顺序,正式的场景下,应该是 100台服务器每个服务器持有一个请求去访问负责管理红包的妹子(服务),那在管红包的妹子那里同时会接收到100个请求,这个时候就需要在负责红包的妹子那里加个锁就可以了(抛绣球),你们100个服务器谁拿到锁(抢到绣球),谁就进来和我谈,我给你分,其他人就等着去吧
  • 分布式锁的实现
    • 数据库方式
    • redis分布式锁
      • 如果采用redis作为分布式锁,那么上图中负“责红包的妹子(服务)”,就可以替换成redis
      • 为什么redis可以实现分布式锁
        • 首先redis是单线程的,这里的单线程指的是网络请求模块使用了一个线程(所以不需考虑并发安全性),即一个线程处理所有网络请求,其他模块仍用了多个线程。
        • 实际操作的过程(整个处理逻辑基本都是一样的)
          • 服务器1要去访问发红包的妹子,也就是redis,那么他会在redis中通过"setnx key value" 操作设置一个key 进去,value是啥不重要,重要的是要有一个key,也就是一个标记,而且这个key你爱叫啥叫啥,只要所有的服务器设置的key相同就可以。
          • 如果再来一个请求去设置同样的key;这个时候会返回0,那就代表失败了
          • 就可以通过这个操作去判断是不是当前可以拿到锁,或者说可以去访问“负责发红包的妹子”,如果返回1,那我就开始去执行后面的逻辑,如果返回0,那就说明已经被人占用了,我就要继续等待。
          • 当服务器1拿到锁之后,进行了业务处理,完成后,还需要释放锁(删除key);删除成功返回1,那么其他的服务器就可以继续重复上面的步骤去设置这个key,以达到获取锁的目的
        • 通过上面的方式,我们好像是解决了分布式锁的问题,但是想想还有没有什么问题呢??
          • 可能会有死锁的问题发生,比如服务器1设置完之后,获取了锁之后,忽然发生了宕机。那后续的删除key操作就没法执行,这个key会一直在redis中存在,其他服务器每次去检查,都会返回0,他们都会认为有人在使用锁,我需要等。
          • 为了解决这个死锁的问题,我们就就需要给key 设置有效期了。
            • 第一种就是在set完key之后,直接设置key的有效期 "expire key timeout" ,为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
              • 这种方式相当于,把锁持有的有效期,交给了redis去控制。如果时间到了,你还没有给我删除key,那redis就直接给你删了,其他服务器就可以继续去setnx获取锁。
            • 第二种方式,就是把删除key权利交给其他的服务器,那这个时候就需要用到value值了,
              • 比如服务器1,设置了value 也就是 timeout 为 当前时间+1 秒 ,
              • 这个时候服务器2 通过get 发现时间已经超过系统当前时间了,那就说明服务器1没有释放锁,服务器1可能出问题了,服务器2就开始执行删除key操作,并且继续执行setnx 操作。
              • 但是这块有一个问题,也就是,不光你服务器2可能会发现服务器1超时了,服务器3也可能会发现,如果刚好,服务器2,setnx操作完成,服务器3就接着删除,是不是服务器3也可以setnx成功了?那就等于是服务器2和服务器3都拿到锁了,那就问题大了。这个时候怎么办呢?
                • 这个时候需要用到  “GETSET  key value” 命令了。这个命令的意思就是获取当前key的值,并且设置新的值。
                • 假设服务器2发现key过期了,开始调用 getset 命令,然后用获取的时间判断是否过期,如果获取的时间仍然是过期的,那就说明拿到锁了。
                • 如果没有,则说明在服务2执行getset之前,服务器3可能也发现锁过期了,并且在服务器2之前执行了getset操作,重新设置了过期时间。
              • 这块其实有一个小问题是,服务器3已经修改了有效期,拿到锁之后,服务器2,也修改了有效期,但是没能拿到锁,但是这个有效期的时间已经被在服务器3的基础上有增加一些,但是这种影响其实还是很小的,几乎可以忽略不计。
    • zookeeper分布式锁
      • ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。
        • 初次认识的人,可以理解成ZooKeeper就像是我们的电脑文件系统,我们可以在d盘中创建文件夹a,并且可以继续在文件夹a中创建 文件夹a1,a2。
          • 那我们的文件系统有什么特点??那就是同一个目录下文件名称不能重复,同样ZooKeeper也是这样的。
      • 在ZooKeeper所有的节点,也就是文件夹称作 Znode,而且这个Znode节点是可以存储数据的。
          • 可以通过“ create /zkjjj nice” 来创建一个节点,这个命令就表示,在跟目录下创建一个zkjjj的节点,值是nice。同样这里的值,和我在前面说的redis中的一样,没什么意义,你随便给
          • ZooKeeper可以创建4种类型的节点
            • 持久性节点
              • 持久性节点表示只要你创建了这个节点,那不管你ZooKeeper的客户端是否断开连接,ZooKeeper的服务端都会记录这个节点。
            • 持久性顺序节点
            • 临时性节点
              • 临时性节点刚好相反,一旦你ZooKeeper客户端断开了连接,那ZooKeeper服务端就不再保存这个节点。
            • 临时性顺序节点
          • 顺序性节点
            • 在创建节点的时候,ZooKeeper会自动给节点编号比如0000001 ,0000002 这种的。
          • zookeeper有一个监听机制,客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、被删除、子目录节点增加删除)等,zookeeper会通知客户端。
      • 结合我们上面的分红包场景,描述下在zookeeper中如何加锁。
        • 假设服务器1,创建了一个节点 /zkjjj ,成功了,那服务器1就获取了锁,服务器2再去创建相同的锁,那么他就会失败,这个时候他就就只能监听这个节点的变化
        • 等到服务器1,处理完业务,删除了节点后,他就会得到通知,然后去创建同样的节点,获取锁处理业务,再删除节点,后续的100台服务器与之类似
          • 注意这里的100台服务器并不是挨个去执行上面的创建节点的操作,而是并发的,当服务器1创建成功,那么剩下的99个就都会注册监听这个节点,等通知,以此类推。
      • 有没有注意到,这里还是有问题的,还是会有死锁的情况存在,对不对?
        • 当服务器1创建了节点后挂了,没能删除,那其他99台服务器就会一直等通知,那就完蛋了。。
        • 这个时候呢,就需要用到临时性节点了,我们前面说过了,临时性节点的特点是客户端一旦断开,就会丢失,也就是当服务器1创建了节点后,如果挂了。那这个节点会自动被删除,这样后续的其他服务器,就可以继续去创建节点,获取锁了。
      • 可能还需要注意到一点,就是惊群效应:
        • 举一个很简单的例子,当你往一群鸽子中间扔一块食物,虽然最终只有一个鸽子抢到食物,但所有鸽子都会被惊动来争夺,没有抢到..
        • 就是当服务器1节点有变化,会通知其余的99个服务器,但是最终只有1个服务器会创建成功,这样98还是需要等待监听,那么为了处理这种情况,就需要用到临时顺序性节点
          • 大致意思就是,之前是所有99个服务器都监听一个节点,现在就是每一个服务器监听自己前面的一个节点。
          • 假设100个服务器同时发来请求,这个时候会在 /zkjjj 节点下创建 100 个临时顺序性节点 /zkjjj/000000001,  /zkjjj/000000002,一直到  /zkjjj/000000100,这个编号就等于是已经给他们设置了获取锁的先后顺序了。
          • 当001节点处理完毕,删除节点后,002收到通知,去获取锁,开始执行,执行完毕,删除节点,通知003~以此类推。
    • 等等

问答:

分布式好~~还是集群好!!

这个很难说啊~我们现在见到的很多应用 像支付宝,都是采用分布式加集群的方式!!需要根据具体情况看了。

如果你的业务比较简单,比如就是负责给所有用户发送个邮件,没其他的业余,那就只用集群,用个Nginx~~负载均衡下~~应该就够了。

但是如果你这个系统既要发邮件,还要计算用户积分,还要处理其他等等~~那可能就需要再带上分布式了~这一类的框架就需要用到dubbo~或者springcloud

分布式锁不到不得已的情况下不要用。有些场景看似需要分布式锁去实现,实际上还是有别的思路去做。

如果一个负责红包的妹子不够用了,需要增加妹子,怎么锁呢?

增加一个老鸨,负责分配发红包的妹子

那锁是在老鸨那里控制还是妹子那里?

你这个问题比较刁钻了。。。

就按照我们这里的100台服务器来说,在同一时刻,每个服务只会有一个请求发给“负责发红包的妹子”(因为每个服务器内部也是加锁处理的),那这样在同一时刻只会有100个请求到达 “负责发红包的妹子” 我们就拿redis来说~~他的qps 可以达到上万的。所以完全可以满足。

如果真的到了你说的不行的情况下,我觉得那只能从以下几点优化了。

一个是硬件,cpu 内存,然后是网络带宽~

还不行,那可能需要再加一个mq,比如rabbit mq ~实现流量削峰~

如果你再说不行~~那这个我就没法说了,这些都是需要结合实际场景去考虑的~~~

问个问题,分布式是把系统的各个功能部署到各个服务器,如果是多个服务器都部署一份程序,然后用负载均衡的方法,根据服务器的负载量,去请求空闲的服务器,哪种方案更好点

分布式一个是服务拆分,从开发上更加方便,因为单个服务更加简单,

第二是合理利用计算机资源,有的是cpu密集型,有的是io密集型,有的对内存要求高,针对每个服务配置不同的硬件,从而降低成本,

但分布式存在的问题是运维复杂,要处理分布式相关的开发任务也挺复杂。

负载均衡主要还是为了动态扩容,应对突发的并发访问。

"但是这个有效期的时间已经被在服务器3的基础上有增加一些,但是这种影响其实还是很小的,几乎可以忽略不计"。
1、分布式下每台机器时间是否都是一致的呢,万一不同机器的value就是刚好一样呢
2、这个锁用来保证一个家的安全,但每个路人却都有钥匙,都有解锁的权利,这家安全吗~
高并发下,1瞬间能发生的事情很多,如果你真的在写瓜分一亿的代码..没什么可以“几乎忽略不计”

原文地址:https://www.cnblogs.com/panpanwelcome/p/10767751.html