1.CAP
在经典的CAP理论中:
C: 一致性
A: 高可用
P: 容错性
在目前这个微服务中,注册中心选择中ZK和Eureka比较常见的,
zookeeper在设计之初是为了解决各个服务之间的数据(状态)都保持一致,所以使用zk做注册中心时,会将这一特性带过来,所以大家常说使用zk的服务,而当zk内部发生选举,或者有一半节点挂掉时,没有办法对外提供服务,所以不是高可用性的,是CP.
Eureka在设计之初明确就是作为一个注册中心,它主要保证的可用性和容错性,即AP;
数据不一致性在注册服务中中会给eureka带来什么问题,无非就是某一个节点被注册的服务多,某个节点注册的服务少,在某一个瞬间可能导致某些ip节点被调用数少,某些ip节点调用数少的问题。也有可能存在一些本应该被删除而没被删除的脏数据。
从注册中心的角度上来说,还是更偏向于高可用的Eureka.
2.分布式锁
目前分布式锁有三种实现方式:
1.基于关系型数据库(mysql)
2.基于redis
3.基于zookeeper
2.1基于数据库
利用数据库中的 UNIQUE KEY作为唯一主键,查询不存在,则insert数据,持有锁,使用完成后,delete该数据,则表示释放锁.
关系数据库谈不上CAP,不属于分布式的范畴内.
2.2基于redis
setnx key value expire_time
//设置成功则获取到锁
在redis集群中,一般使用的是主从+sentinel,一旦主redis出现问题,可以让sentinel做故障迁移,保证可用性.
当集群中的主redis设置了锁成功,然后该redis出现故障,这时还没有同步给从redis,就被sentinel做了故障转移.这时设置的锁,会丢失.故不能保证一致性.
所以说基于redis的分布式锁是AP.
2.3基于zookeeper
zk所具有的特性:
1.有序节点
//在一个 /lock 父节点下
//创建的每个节点都是有序的(如:0001,0002,0003...)
2.临时节点
//创建临时节点,当会话到期,
//客户端不发送心跳时,节点会被自动删除.
3.事件监听
//在读取数据时,我们可以对节点设置监听,当节点的数据发生变化
//(1,节点创建 2,节点删除 3,节点数据变成 4,自节点变成)时,zookeeper会通知客户端
使用zk做分布式锁,就是让多个线程去争抢去特定目录下创建节点,节点号最小的持有锁.
treadA ---> /lock/0001
treadB ---> /lock/0002//这时treadA获取到锁,
//当treadA使用 完后释放,
//创建 的节点会被自动删除,
//thradB创建的/lock/0002成为了最下的节点,
//这时threadB获取到了锁.
根据zk的特性可以保证集群中不同的节点,数据的一致性,也就是说集群内所有的zk都会记录/lock下的节点信息.保证了一致性.
当时当发生选举,无法集群无法提供服务,则不能保证可用性.
(这个实际上是zk的leader通过二阶段提交写请求来保证的数据一致性,这个也是zk的集群规模大了的一个瓶颈点)
基于zk的分布式锁,具有CP.
2.4 使用场景
再实际的技术选型中,要看实际的业务场景,比如在不是很要求强一致性的业务场景下,比如:点赞,这时可以考虑使用基于redis的分布式锁.
但是当在金融相关的业务中,对数据的一致性要求很高,这个时候,要采用给予zookeeper的分布式锁,来保证强一致性.
有句话说的好,小孩子才做选择,大人是全都要!,如何做到全都要呢.
这就要说到牺牲强一致性,做妥协,实现最终一致性.
3.最终一致性的解决方案
1.两阶段提交(2PC)
2.补偿事务(TCC)
3.本地消息表
4.MQ事务消息
3.1 两段式提交
一般时基于关系型数据库的事务,第一段所有sql提交前获取事务,都获取到后,统一commit,一旦有一个出现问题,则回滚.
这种方式的问题很多,再互联网公司中,高并发的情况下性能太差,网络出现波动对事物提交也有很大的影响.
3.2 补偿事物
TCC是服务化的两阶段变成模型,每个业务服务都必须实现 try,confirm,calcel三个方法,这三个方式可以对应到SQL事务中Lock,Commit,Rollback。
相比两阶段提交,TCC解决了几个问题:
同步阻塞,引入了超时机制,超时后进行补偿,并不会像两阶段提交锁定了整个资源,将资源转换为业务逻辑形式,粒度变小。
因为有了补偿机制,可以由业务活动管理器进行控制,保证数据一致性。
1). try阶段
try只是一个初步的操作,进行初步的确认,它的主要职责是完成所有业务的检查,预留业务资源
2). confirm阶段
confirm是在try阶段检查执行完毕后,继续执行的确认操作,必须满足幂等性操作,如果confirm中执行失败,会有事务协调器触发不断的执行,直到满足为止
3). cancel是取消执行,在try没通过并释放掉try阶段预留的资源,也必须满足幂等性,跟confirm一样有可能被不断执行.
3.3 本地消息表
本地消息表这种实现方式应该是业界使用最多的,其核心思想是将分布式事务拆分成本地事务进行处理。
对于本地消息队列来说,核心就是将大事务转变为小事务,还是用上面下订单扣库存的例子说说明:
当我们去创建订单的时候,我们新增一个本地消息表,把创建订单和扣减库存写入到本地消息表,放在同一个事务(依靠数据库本地事务保证一致性)
配置一个定时任务去轮训这个本地事务表,扫描这个本地事务表,把没有发送出去的消息,发送给库存服务,当库存服务收到消息后,会进行减库存,并写入服务器的事务表,更新事务表的状态。
库存服务器通过定时任务或直接通知订单服务,订单服务在本地消息表更新状态。
这里须注意的是,对于一些扫描发送未成功的任务,会进行重新发送,所以必须保证接口的幂等性。
本地消息队列是BASE理论,是最终一致性模型,适用对一致性要求不高的情况。
(https://juejin.im/post/5d720e86f265da03cc08de74)