分布式和微服务的关系分布式场景常见问题及解决⽅案
⼀、分布式锁
  分布式锁是在分布式场景下⼀种常见技术,通常通过基于redis和zookeeper来实现,本⽂主要介绍redis分布式锁和zookeeper分布式锁的实现⽅案和对⽐:
  (1)基于redis的普通实现
  这个⽅案的加锁主要实现是基于redis的”SET key 随机值 NX PX 过期时间(毫秒)”指令,NX代表只有key不存在时才设置成
功,PX代表在过期时间后会⾃动释放。
  这个⽅案的释放锁是通过lua脚本删除key的⽅式,判断value⼀样则删除key。
  使⽤随机值的原因是如果某个获取到锁的客户端阻塞了很长时间,导致了它获取到的锁已经⾃动释放,此时可能有其他客户端已经获取到了锁,如果直接删除是有问题的,所以要通过随机值加上lua脚本去判断如果value相等时再删除。
  这个⽅案存在⼀个问题就是,如果采⽤redis单实例可能会存在单点故障问题,但如果采⽤普通主从⽅式,如果主节点挂了key还没来得及同步到从节点,此时从节点被切换到了主节点,由于没有同步到数据别⼈就会拿到锁。
  (2)redis的RedLock算法
  这个⽅案是redis官⽅推荐的分布式锁的解决⽅案,假设有5个redis master实例,然后执⾏如下步骤去获取⼀把锁:
  1)获取当前时间戳,单位是毫秒
  2)跟上⾯类似,轮流尝试在每个master节点上创建锁,过期时间较短,⼀般就⼏⼗毫秒
  3)尝试在⼤多数节点上建⽴⼀个锁,⽐如5个节点就要求是3个节点(n / 2 +1)
  4)客户端计算建⽴好锁的时间,如果建⽴锁的时间⼩于超时时间,就算建⽴成功了
  5)要是锁建⽴失败了,那么就依次删除这个锁
  6)只要别⼈建⽴了⼀把分布式锁,你就得不断轮询去尝试获取锁
  这个⽅案实现起来较为⿇烦,不过貌似有很多开源的插件封装了这个算法,但是这个算法好像在国外是有争议的。
  (3) zookeeper的临时节点
  这个⽅案的实现是通过在获取锁时在zookeeper上创建临时节点(常⽤为临时顺序节点),如果创建成功(如果是临时顺序节点则为当前最⼩),就代表获取了到了这个锁;如果创建创建失败(如果是临时顺序节点,当前⾃⼰节点不是最⼩),则注册监听这个锁。释放锁时会删除这个临时节点,测试由于注册了,其他等待锁的线程则会收到释放锁的消息,然后再去尝试获取锁。
  (4)redis分布式锁和zookeeper分布式锁的对⽐
  通过对⽐redis分布式锁和zk分布式锁可以发现,redis分布式锁类似于⾃旋锁的⽅式需要⾃⼰不断尝试去获取锁,这个是⽐较耗性能的。zk获取不到锁的话则可以注册,不需要不断尝试,这样的活性能开销较⼩;其次,redis锁有⼀个问题就是,如果获取到锁的客户端崩溃了或者没有正常释放锁则会导致只能等到过期时间完了才能获取到锁,⽽zk建⽴的由于是临时节点,客户端崩溃了或者挂了,临时节点会⾃动删除,此时会⾃动释放锁;最后,这个redis的实现⽅式如果采⽤RedLock算法的话较为复杂并且还存在争议,普通的算法存在单点故障和主从同步的问题,所以⼀般来说,个⼈认为zk分布式锁要⽐redis分布式锁更可靠并且易⽤。
⼆、分布式session
  在传统的单体应⽤下,我们可以通过session去存储⼀些数据,但是在分布式和为微服务结构下,如果需要使⽤session就需要采取⼀些⼿段去维护,以下提供2个⽅案去实现分布式session:
  (1)tomcat+redis
  由于我们传统的应⽤基本都是通过tomcat去部署的,我们可以利⽤tomcat的RedisSessionManager来将session的数据都存在redis 中。
  但这种⽅案现在不怎么使⽤了,因为移植性很差,如果要换web容器就尴尬了。
  (2)spring session + redis
  给sping session配置基于redis来存储session数据,然后配置⼀个spring session的过滤器,这样的话,session相关操作都会交给spring session来管了。接着在代码中,就⽤原⽣的session操作,就是直接基于spring sesion从redis中获取数据了。
  这个⽅案现在还是⽐较主流的,因为现在主流的开发基本都是基于spring开源的⼀些框架,所以说如果换技术栈的影响不会很⼤。
三、分布式事务
  现在分布式系统成为了主流,但使⽤分布式也随之带来了⼀些问题和痛点,分布式事务就是最常见的⼀个问题,本⽂主要介绍分布式事务的⼀些常见解决⽅案。
  (1)两阶段提交⽅案/XA⽅案
  两阶段提交,有⼀个事务管理器的概念,负责协调多个数据库(资源管理器)的事务,事务管理器先问问各个数据库你准备好了吗?如果每个数据库都回复ok,那么就正式提交事务,在各个数据库上执⾏操作;如果任何⼀个数据库回答不ok,那么就回滚事务。
  这种分布式事务⽅案,⽐较适合单块应⽤⾥,跨多个库的分布式事务,⽽且因为严重依赖于数据库层⾯来搞定复杂的事务,效率很低,绝对不适合⾼并发的场景。如果要玩⼉,那么基于spring + JTA就可以搞定。
  这个⽅案很少⽤,⼀般来说某个系统内部如果出现跨多个库的这么⼀个操作,是不合规的。我可以给⼤家介绍⼀下, 现在微服务,⼀个⼤的系统分成⼏百个服务,⼏⼗个服务。⼀般来说,我们的规定和规范,是要求说每个服务只能操作⾃⼰对应的⼀个数据库。如果你要操作别的服务对应的库,不允许直连别的服务的库,违反微服务架构的规范,如果你要操作别⼈的服务的库,你必须是通过调⽤别的服务的接⼝来实现,绝对不允许你交叉访问别⼈的数据库!
  (2)TCC⽅案
  TCC的全程是:Try、Confirm、Cancel。
  这个其实是⽤到了补偿的概念,分为了三个阶段:
  1)Try阶段:这个阶段说的是对各个服务的资源做检测以及对资源进⾏锁定或者预留
  2)Confirm阶段:这个阶段说的是在各个服务中执⾏实际的操作
  3)Cancel阶段:如果任何⼀个服务的业务⽅法执⾏出错,那么这⾥就需要进⾏补偿,就是执⾏已经执⾏成功的业务逻辑的回滚操作。
  这种⽅案说实话⼏乎很少⽤⼈使⽤,因为这个事务回滚实际上是严重依赖于你⾃⼰写代码来回滚和补偿了,会造成补偿代码巨⼤,⾮常之恶⼼。
  ⽐较适合的场景:这个就是除⾮你是真的⼀致性要求太⾼,是你系统中核⼼之核⼼的场景,⽐如常见的就是资⾦类的场景,那你可以⽤TCC⽅案了,⾃⼰编写⼤量的业务逻辑,⾃⼰判断⼀个事务中的各个环节是否ok,不ok就执⾏补偿/回滚代码。
  (3)本地消息表
  本地消息表⽰国外的ebay搞出来的这么⼀套思想
  本地消息表来实现分布式事务的思路⼤致如下:
  1)A系统在⾃⼰本地⼀个事务⾥操作同时,插⼊⼀条数据到消息表
  2)接着A系统将这个消息发送到MQ中去
  3)B系统接收到消息之后,在⼀个事务⾥,往⾃⼰本地消息表⾥插⼊⼀条数据,同时执⾏其他的业务操作,如果这个消息已经被处理过了,那么此时这个事务会回滚,这样保证不会重复处理消息
  4)B系统执⾏成功之后,就会更新⾃⼰本地消息表的状态以及A系统消息表的状态
  5)如果B系统处理失败了,那么就不会更新消息表状态,那么此时A系统会定时扫描⾃⼰的消息表,如果有没处理的消息,会再次发送到MQ中去,让B再次处理
  6)这个⽅案保证了最终⼀致性,哪怕B事务失败了,但是A会不断重发消息,直到B那边成功为⽌。
  这个⽅案最⼤的弊端在于依赖于数据库消息表来保证事务,但是在⾼并发场景下,数据库就成了瓶颈。
  (4)可靠消息最终⼀致性⽅案
  这个⽅案的⼤致思路为:
  1)A系统先发送⼀个prepared消息到mq,如果这个prepared消息发送失败那么就直接取消操作别执⾏了
  2)如果这个消息发送成功过了,那么接着执⾏本地事务,如果成功就告诉mq发送确认消息,如果失败就告诉mq回滚消息
  3)如果发送了确认消息,那么此时B系统会接收到确认消息,然后执⾏本地的事务
  4)mq会⾃动定时轮询所有prepared消息回调你的接⼝,问你,这个消息是不是本地事务处理失败了,所有没发送确认消息?那是继续重试还是回滚?⼀般来说这⾥你就可以查下数据库看之前本地事务是否执⾏,如果回滚了,那么这⾥也回滚吧。这个就是避免可能本地事务执⾏成功了,别确认消息发送失败了。
  5)这个⽅案⾥,要是系统B的事务失败了咋办?重试咯,⾃动不断重试直到成功,如果实在是不⾏,要么就是针对重要的资⾦类业务进⾏回滚,⽐如B系统本地回滚后,想办法通知系统A也回滚;或者是发送报警由⼈⼯来⼿⼯回滚和补偿。
  这个⽅案是⽬前国内公司采⽤较多的⼀种⽅案。
  (5)最⼤努⼒通知⽅案
  思路:
  1)系统A本地事务执⾏完之后,发送个消息到MQ
  2)这⾥会有个专门消费MQ的最⼤努⼒通知服务,这个服务会消费MQ然后写⼊数据库中记录下来,或者是放⼊个内存队列也可以,接着调⽤系统B的接⼝
  3)要是系统B执⾏成功就ok了;要是系统B执⾏失败了,那么最⼤努⼒通知服务就定时尝试重新调⽤系统B,反复N次,最后还是不⾏就放弃。
  (6)究竟如何来使⽤分布式事务?
  咨询过⼀些互联⽹公司的⼤佬,在他们的业务场景下起码⼏百个服务,复杂的分布式⼤型系统,⾥⾯其实也没⼏个分布式事务。
  其实⽤任何⼀个分布式事务的这么⼀个⽅案,都会导致你那块⼉代码会复杂10倍。很多情况下,系统
A调⽤系统B、系统C、系统D,我们可能根本就不做分布式事务。如果调⽤报错会打印异常⽇志或者通过返回值判断是否需要回滚。每个⽉也就那么⼏个bug,很多bug是功能性的,体验性的,真的是涉及到数据层⾯的⼀些bug,⼀个⽉就⼏个,两三个?如果你为了确保系统⾃动保证数据100%不能错,上了⼏⼗个分布式事务,代码太复杂;性能太差,系统吞吐量、性能⼤幅度下跌。
  99%的分布式接⼝调⽤,⼀些⼤佬给的建议不要做分布式事务,直接就是监控(发邮件、发短信)、记录⽇志(⼀旦出错,完整的⽇志)、事后快速的定位、排查和出解决⽅案、修复数据。这样⽐你做50个分布式事务,成本要来的低上百倍,低⼏⼗倍。
  所以在要⽤分布式事务的时候要权衡,分布式事务⼀定是有成本,代码会很复杂,开发很长时间,性能和吞吐量下跌,系统更加复杂更加脆弱反⽽更加容易出bug;但是好处就是如果做好了,TCC、可靠消息最终⼀致性⽅案,⼀定可以100%保证你那快数据不会出错。
---------------------

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。