说说你对CAP 的理解
CAP 模型,在一个分布式系统里面,不可能同时满足三个点
- 一致性(Consistency),访问分布式系统中的每一个节点都能获得最新的数据。
- 可用性(Availability),每次请求都能获得一个有效的访问,但不保证数据是最新的。
- 分区容错性(Partition tolerance),分区相当于对通信耗时的要求,系统如果不
能在时限范围内达成数据一致,就意味着发生了分区的情况。在CAP 模型中只能满足CP 或者AP,之所以不能满足CA,因为网络通信的不确定性可能会导致分区容错,也就是分区容错性必然是存在的,因此我们只能在一致性和可用性之间做选择。
服务注册中心应该是AP 还是CP
在CAP 模型中只能满足CP 或者AP,之所以不能满足CA,因为网络通信的不确定性可能会导致分区容错.
也就是分区容错性必然是存在的,因此我们只能在一致性和可用性之间做选择。
再回到注册中心,服务注册中心的本质是为了提供服务地址的统一管理,以及提供一个服务动态感知的能力。
所以,注册中心应该要保证高可用性,也就是无论什么情况下,应用都能正常从注册中心获取到目标服务的通信地址。
当注册中心不可用的时候,不能影响服务之间的正常通信。因此,从这个角度来说,注册中心应该是AP 模型。
分布式锁的理解,以及分布式锁的实现.
分布式锁,是一种跨进程的跨机器节点的互斥锁,它可以用来保证多机器节点对于共享资源访问的排他性。
分布式锁和线程锁本质上是一样的,线程锁的生命周期是单进程多线程,分布式锁的声明周期是多进程多机器节点。
只要能够满足这些特性的技术组件都能够实现分布式锁。 一般有两种方式,一种是轮询,一种是事件回调。
轮询:一直轮询锁是否释放,浪费性能。
关系型数据库,可以使用唯一约束来实现锁的排他性,如果要针对某个方法加锁,就可以创建一个表包含方法名称字段,并且把方法名设置成唯一的约束。那抢占锁的逻辑就是:往表里面插入一条数据,如果已经有其他的线程获得了某个方法的锁,那这个时候插入数据会失败,从而保证了互斥性。但是如果锁一直不释放,就很容易死锁,mysql显然捉襟见肘。
Redis,它里面提供了SETNX 命令可以实现锁的排他性,当key 不存在就返回1,存在就返回0。然后还可以用expire 命令设置锁的失效时间,从而避免死锁问题。当然有可能存在锁过期了,但是业务逻辑还没执行完的情况。所以这种情况,可以写一个定时任务对指定的key 进行续期。
事件回调:注册事件,锁是否后通知回调。
etcd机制,但是避免羊群时间,但是有id、perfix机制可以保障。
谈谈你对分布式ID 的理解
首先,分布式ID,是因为随着业务的发展,数据或者ID 的生成会在不同的地方,比如 常见的数据的分库分表,在分库分表的情况下,假如不同的表生成的ID 不能保证唯一性,那么就会导致一个ID 不能保证一条数据的唯一性,就会带来问题。所以分布式ID 的主要目的:不论何时何地,生成的ID 都是唯一的。
实现的方式有很多:
1.Mysql 的全局ID 生成表
2.UUID(有些版本会重复)
3.Redis 的incr 自增ID
但是除了唯一性以外,我们还需要考虑更多的其他因素:
1.比如是否趋势递增,否则在Mysql 会导致主键索引树的分裂与合并。
2.安全性,通过ID 是否会导致数据泄露
3.高可用以及高性能,满足业务的需求,不能生成一个ID 很慢或者动不动不可用。
所以,为了性能与可靠性以及成本,市面上还有一种雪花算法是用得比较多,他是一种基于位运算来实现的,并且实现简单,没有太多外部依赖。
谈谈常用的分布式ID 设计方案
分布式全局ID 的的解决方案有很多,比如 使用Mysql 的全局表、 redis 的自增id、UUID 等等 这些方案只是解决基础的id 唯一性问题,在实际生产环境中,需要构建一个全局唯一id还需要考虑更多的因素:
- 有序性, 有序的ID 能够更好的确认数据的位置,以及B+数的存储结构中,范围查询的效率更高,并且可以提升B+树数据维护的效率。
- 安全性,避免恶意爬去数据造成数据泄露
- 可用性,ID 生成系统的可用性要求非常高,一旦出现故障就会造成业务不可用的问题
- 性能,全局id 生成系统需要满足整个公司的业务需求,涉及到亿级别的调用,对性能要求较高
目前市面上主流的解决方案是基于Twitter 早期开源的Snowflake 雪花算法。或者使用redis自增特性+时间的方式进行生成。
如何解决TCC 中的悬挂问题
TCC,其实就是(Try-Confirm-Cancel),也就是把一个事务拆分成两个阶段,Try 这个阶段,是实现业务的检查,预留必要的业务资源。 Confirm,真正执行业务逻辑,只需要使用try 阶段预留的业务资源进行处理就行。Cancel,如果事务执行失败,就通过cancel 方法释放try 阶段预留的资源。
悬挂问题,指的是TCC 执行Try 接口出现网络超时时候,使得TCC 触发Cancel 接口回滚,但可能在回滚之后,这个超时的Try 接口才被真正执行,也就导致Cancel 接口比Try接口先执行。从而造成Try 接口预留的资源一直无法释放,这种情况就是悬挂。
我们在应用的时候都是采用一些成熟的框架,比如Seata,这些框架本身就帮我们解决了。
于悬挂问题,我认为只需要保证Cancel 接口执行完以后,Try 接口不允许在执行就可以了。所以,我们可以在Try 接口里面,先判断Cancel 接口有没有执行过,如果已经执行过,就不再执行。是否执行过的这个判断,可以在事务控制表里面插入一条事务控制记录来标记这个事务的回滚状态。然后在Try 接口中只需要读取这个状态来判断就行了。