1、分布式服务接口的幂等性如何设计?
所谓幂等性,就是说一个接口,多次发起同一个请求,你这个接口得保证结果是准确得。比如不能多扣款。不能多插入一条数据,不能将统计值多加了 1,这就是幂等性。
其实保证幂等性主要是三点:
- 对于每个请求必须有一个唯一的标识,举个例子:订单支付请求,肯定得包含订单 ID,一个订单 ID 最多支付一次。
- 每次处理完请求之后,必须有一个记录标识这个请求处理过了,比如说常见得方案是再
mysql 中记录个状态啥得,比如支付之前记录一条这个订单得支付流水,而且支付流水采用 order id 作为唯一键(unique key)。只有成功插入这个支付流水,才可以执行实际得支付扣款
- 每次接收请求需要进行判断之前是否处理过得逻辑处理,比如说,如果有一个订单已经支付了,就已经有了一条支付流水,那么如果重复发送这个请求,则此时先插入支付流水, order id 已经存在了,唯一键约束生效,报错插入不进去得。然后你就不用再扣款了。
2、分布式系统中的接口调用如何保证顺序性?
可以接入 MQ,如果是系统 A 使用多线程处理的话,可以使用内存队列,来保证顺序性, 如果你要 100%的顺序性,当然可以使用分布式锁来搞,会影响系统的并发性。
3、说说 ZooKeeper 一般都有哪些使用场景?
- 分布式协调:这个其实就是 zk 很经典的一个用法,简单来说,就好比,你系统 A 发送个请求到 mq,然后 B 消费了之后处理。那 A 系统如何指导 B 系统的处理结果?用 zk 就可以实现分布式系统之间的协调工作。A 系统发送请求之后可以在 zk 上对某个节点的值注册个监听器,一旦 B 系统处理完了就修改 zk 那个节点的值,A 立马就可以收到通知,完美解决。
- 分布所锁:对某一个数据联系发出两个修改操作,两台机器同时收到请求,但是只能一台机器先执行另外一个机器再执行,那么此时就可以使用 zk 分布式锁,一个机器接收到了请求之后先获取 zk 上的一把分布式锁,就是可以去创建一个 znode,接着执行操作,然后另外一个机器也尝试去创建那个 znode,结果发现自己创建不了,因为被别人创建了, 那只能等着,等等一个机器执行完了自己再执行。
- 配置信息管理:zk 可以用作很多系统的配置信息的管理,比如 kafka,storm 等等很多分布式系统都会选用 zk 来做一些元数据,配置信息的管理,包括 dubbo 注册中心不也支持
zk 么。
- HA 高可用性:这个应该是很常见的,比如 hdfs,yarn 等很多大数据系统,都选择基于
zk 来开发 HA 高可用机制,就是一个重要进程一般会主备两个,主进程挂了立马通过 zk
感知到切换到备份进程。
4、说说你们的分布式 session 方案是啥?怎么做的?
- Tomcat + redis
其实还挺方便的,就是使用 session 的代码跟以前一样,还是基于 tomcat 原生的
session 支持即可,然后就是用一个叫做 tomcat RedisSessionManager 的东西,让我们部署的 tomcat 都将 session 数据存储到 redis 即可.
- Spring Session + redis
分布式会话的这个东西重耦合在 tomcat,如果我要将 web 容器迁移成 jetty,不能重新把 jetty 都配置一遍.
所以现在比较好用的还是基于 java 的一站式解决方案,使用 spring session 是一个很好的选择,给 spring session 配置基于 redis 来存储 session 数据,然后配置一个 spring
session 的过滤器,这样的话,session 相关操作都会交给 spring session 来管了。接着在代
码中,就是用原生的 session 操作,就是直接基于 spring session 从 redis 中获取数据了。
5、分布式事务了解吗?
- XA 方案/两阶段提交方案第一个阶段(先询问)
第二个阶段(再执行)
- TCC 方案
TCC 的全程是:Try、Confirm、Cancel
这个其实是用到了补偿的概念,分为了三个阶段
Try 阶段:这个阶段说的是对各个服务的资源做检测以及对资源进行锁定或者预留
Confirm 阶段:这个阶段说的是在各个服务中执行实际的操作
Cancel 阶段:如果任何一个服务的业务方法执行出错,那么这里就需要进行补偿,就是执行已经成功的业务逻辑的回滚操作
- 本地消息表
- 可靠消息最终一致性方案
- 最大努力通知方案
6、那常见的分布式锁有哪些解决方案?
- Reids 的分布式锁,很多大公司会基于 Reidis 做扩展开发
- 基于 Zookeeper
- 基于数据库,比如 Mysql
7、ZK 和 Redis 的区别,各自有什么优缺点? 先说 Redis:
- Redis 只保证最终一致性,副本间的数据复制是异步进行(Set 是写,Get 是读,Reids 集群一般是读写分离架构,存在主从同步延迟情况),主从切换之后可能有部分数据没有复制过去可能会丢失锁情况,故强一致性要求的业务不推荐使用 Reids,推荐使用 zk。
- Redis 集群各方法的响应时间均为最低。随着并发量和业务数量的提升其响应时间会有明显上升(公有集群影响因素偏大),但是极限 qps 可以达到最大且基本无异常
再说 ZK:
- 使用 ZooKeeper 集群,锁原理是使用 ZooKeeper 的临时节点,临时节点的生命周期在
Client 与集群的 Session 结束时结束。因此如果某个 Client 节点存在网络问题,与
ZooKeeper 集群断开连接,Session 超时同样会导致锁被错误的释放(导致被其他线程错误地持有),因此 ZooKeeper 也无法保证完全一致。
- ZK 具有较好的稳定性;响应时间抖动很小,没有出现异常。但是随着并发量和业务数量的提升其响应时间和 qps 会明显下降。
8、MySQL 如何做分布式锁?
方法一:
利用 Mysql 的锁表,创建一张表,设置一个 UNIQUE KEY 这个 KEY 就是要锁的 KEY, 所以同一个 KEY 在 mysql 表里只能插入一次了,这样对锁的竞争就交给了数据库,处理同一个 KEY 数据库保证了只有一个节点能插入成功,其他节点都会插入失败。
DB 分布式锁的实现:通过主键 id 的唯一性进行加锁,说白了就是加锁的形式是向一张表中插入一条数据,该条数据的 id 就是一把分布式锁,例如当一次请求插入了一条 id 为 1 的数据,其他想要进行插入数据的并发请求必须等第一次请求执行完成后删除这条 id 为 1 的数据才能继续插入,实现了分布式锁的功能。
方法二:
使用流水号+时间戳做幂等操作,可以看作是一个不会释放的锁。
- Google:Chubby
Chubby 是一套分布式协调系统,内部使用 Paxos 协调 Master 与 Replicas。
Chubby lock service 被应用在 GFS, BigTable 等项目中,其首要设计目标是高可靠性, 而不是高性能。
Chubby 被作为粗粒度锁使用,例如被用于选主。持有锁的时间跨度一般为小时或天,而不是秒级。
Chubby 对外提供类似于文件系统的 API,在 Chubby 创建文件路径即加锁操作。
Chubby 使用 Delay 和 SequenceNumber 来优化锁机制。Delay 保证客户端异常释放锁时,Chubby 仍认为该客户端一直持有锁。Sequence number 指锁的持有者向 Chubby 服务端请求一个序号(包括几个属性),然后之后在需要使用锁的时候将该序号一并发给
Chubby 服务器,服务端检查序号的合法性,包括 number 是否有效等。
- 京东 SharkLock
SharkLock 是基于 Redis 实现的分布式锁。锁的排他性由 SETNX 原语实现,使用
timeout 与续租机制实现锁的强制释放。
- 蚂蚁金服 SOFAJRaft-RheaKV 分布式锁
RheaKV 是基于 SOFAJRaft 和 RocksDB 实现的嵌入式、分布式、高可用、强一致的 KV
存储类库。
RheaKV 对外提供 lock 接口,为了优化数据的读写,按不同的存储类型,提供不同的锁特性。RheaKV 提供 wathcdog 调度器来控制锁的自动续租机制,避免锁在任务完成前提前释放,和锁永不释放造成死锁。
- Netflix: Curator
Curator 是 ZooKeeper 的客户端封装,其分布式锁的实现完全由 ZooKeeper 完成。
在 ZooKeeper 创建 EPHEMERAL_SEQUENTIAL 节点视为加锁,节点的 EPHEMERAL 特性保证了锁持有者与 ZooKeeper 断开时强制释放锁;节点的 SEQUENTIAL 特性避免了加锁较多时的惊群效应。
在理论计算机科学中,CAP 定理(CAP theorem),又被称作布鲁尔定理(Brewer’s
theorem),它指出对于一个分布式计算系统来说,不可能同时满足以下三点:
- Consistency(一致性) 指数据在多个副本之间能够保持一致的特性(严格的一致性)
- Availability(可用性) 指系统提供的服务必须一直处于可用的状态,每次请求都能获取到非错的响应(不保证获取的数据为最新数据)
- Partition tolerance(分区容错性) 分布式系统在遇到任何网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务,除非整个网络环境都发生了故障
Spring Cloud 在 CAP 法则上主要满足的是 A 和 P 法则,Dubbo 和 Zookeeper 在 CAP
法则主要满足的是 C 和 P 法则。
CAP 仅适用于原子读写的 NOSQL 场景中,并不适合数据库系统。现在的分布式系统具有更多特性比如扩展性、可用性等等,在进行系统设计和开发时,我们不应该仅仅局限在 CAP 问题上。
现实生活中,大部分人解释这一定律时,常常简单的表述为:“一致性、可用性、分区容忍性三者你只能同时达到其中两个,不可能同时达到”。实际上这是一个非常具有误导性质的说法,而且在 CAP 理论诞生 12 年之后,CAP 之父也在 2012 年重写了之前的论文。
当发生网络分区的时候,如果我们要继续服务,那么强一致性和可用性只能 2 选 1。也就是说当网络分区之后 P 是前提,决定了 P 之后才有 C 和 A 的选择。也就是说分区容错性
(Partition tolerance)我们是必须要实现的。
BASE 理论由 eBay 架构师 Dan Pritchett 提出,在 2008 年上被分表为论文,并且 eBay
给出了他们在实践中总结的基于 BASE 理论的一套新的分布式事务解决方案。
BASE 是 Basically Available(基本可用) 、Soft-state(软状态) 和 Eventually
Consistent(最终一致性) 三个短语的缩写。BASE 理论是对 CAP 中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是基于 CAP 定理逐步演化而来的, 它大大降低了我们对系统的要求。
BASE 理论的核心思想是即使无法做到强一致性,但每个应用都可以根据自身业务特点, 采用适当的方式来使系统达到最终一致性。也就是牺牲数据的一致性来满足系统的高可用性, 系统中一部分数据不可用或者不一致时,仍需要保持系统整体“主要可用”。
针对数据库领域,BASE 思想的主要实现是对业务数据进行拆分,让不同的数据分布在不同的机器上,以提升系统的可用性,当前主要有以下两种做法:
- 按功能划分数据库
- 分片(如开源的 Mycat、Amoeba 等)。
分布式: 一个业务分拆多个子业务,部署在不同的服务器上
集群: 同一个业务,部署在多个服务器上。比如之前做电商网站搭的 redis 集群以及 solr 集群都是属于将 redis 服务器提供的缓存服务以及 solr 服务器提供的搜索服务部署在多个服务器上以提高系统性能、并发量解决海量存储问题。
基本可用
基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性。但是,这绝不等价于系统不可用。
比如:
- 响应时间上的损失:正常情况下,一个在线搜索引擎需要在 0.5 秒之内返回给用户相应的查询结果,但由于出现故障,查询结果的响应时间增加了 1~2 秒
- 系统功能上的损失:正常情况下,在一个电子商务网站上进行购物的时候,消费者几乎能够顺利完成每一笔订单,但是在一些节日大促购物高峰的时候,由于消费者的购物行为激
增,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面
软状态
软状态指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。
最终一致性
强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
分布式系统的一个难点是如何保证架构下多个节点在进行事务性操作的时候保持一致性。为实现这个目的,二阶段提交算法的成立基于以下假设:
- 该分布式系统中,存在一个节点作为协调者(Coordinator),其他节点作为参与者
(Cohorts)。且节点之间可以进行网络通信。
- 所有节点都采用预写式日志,且日志被写入后即被保持在可靠的存储设备上,即使节点损坏不会导致日志数据的消失。
- 所有节点不会永久性损坏,即使损坏后仍然可以恢复。
第一阶段(投票阶段)
- 协调者节点向所有参与者节点询问是否可以执行提交操作(vote),并开始等待各参与者节点的响应。
- 参与者节点执行询问发起为止的所有事务操作,并将 Undo 信息和 Redo 信息写入日志。
(注意:若成功这里其实每个参与者已经执行了事务操作)
- 各参与者节点响应协调者节点发起的询问。如果参与者节点的事务操作实际执行成功,则它返回一个”同意”消息;如果参与者节点的事务操作实际执行失败,则它返回一个”中止”消息。
第二阶段(提交执行阶段)
当协调者节点从所有参与者节点获得的相应消息都为”同意”:
- 协调者节点向所有参与者节点发出”正式提交(commit)”的请求。
- 参与者节点正式完成操作,并释放在整个事务期间内占用的资源。
- 参与者节点向协调者节点发送”完成”消息。
- 协调者节点受到所有参与者节点反馈的”完成”消息后,完成事务。如果任一参与者节点在第一阶段返回的响应消息为”中止”:
- 协调者节点向所有参与者节点发出”回滚操作(rollback)”的请求。
- 参与者节点利用之前写入的 Undo 信息执行回滚,并释放在整个事务期间内占用的资源。
- 参与者节点向协调者节点发送”回滚完成”消息。
- 协调者节点受到所有参与者节点反馈的”回滚完成”消息后,取消事务。
Try Confirm Cancel
- Try:尝试待执行的业务 ,这个过程并未执行业务,只是完成所有业务的一致性检查,并预留好执行所需的全部资源。
- Confirm:执行业务,这个过程真正开始执行业务,由于 Try 阶段已经完成了一致性检
查,因此本过程直接执行,而不做任何检查。并且在执行的过程中,会使用到 Try 阶段预留的业务资源。
Cancel:取消执行的业务,若业务执行失败,则进入 Cancel 阶段,它会释放所有占用的业务资源,并回滚 Confirm 阶段执行的操作。