分布式服务架构相关问题

1. 分布式服务接口的幂等性问题

1.1 什么是幂等性

所谓幂等性,就是说一个接口,多次发起同一个请求,你这个接口得保证结果是准确的,比如不能多扣款,不能多插入一条数据,不能将统计值多加了1。

1.2 保证幂等性的要点

* 对于每个服务请求必须有一个全局的唯一标识。
* 每次处理完请求之后,必须有一个标识去记录这个请求已经处理过了。
* 每次接收请求需要判断该请求或该请求需要访问的数据是否已经被处理过了。

1.3 保证幂等性的方案

* 利用全局缓存。当接收到请求时,记录一个唯一标识到redis缓存中,下一次重复请求过来时,如果查询到对应标识,则不进行处理或抛出异常
* 利用数据库的锁,高并发情况下不建议使用,会增大数据库的负载,甚至宕机。例如支付订单,当接收到请求时,可以利用订单状态,将待支付状态变更为支付中(由sql保证必须是待支付才能变更为支付中),若变更成功,可正常处理;若变更失败,则不进行处理或者抛出异常。

2. 分布式系统中的接口调用如何保证顺序性

  * 从业务上最好设计的系统不需要保证顺序性,一旦引入顺序性保障,会导致系统复杂度上升,而且会带来效率低下,热点数据压力过大等问题。
  * 请求中需要包含唯一ID和请求序列号(不同的方式需要不同的数据)

2.1 采用中间服务以及内存队列

就是把需要保证顺序的请求,发送给中间服务(可以是MQ,也可以是其他接入服务),然后服务内部在把请求放到内存队列中,线程从内存队列中获取消费,保证线程的顺序性

2.2 采用分布式锁

分布式锁能够保证强一致性,但是引入这种重量级的同步机制,增加了频繁的获取锁,释放锁的操作,会导致并发量降低。

3. zookeeper的使用场景

3.1 数据发布/订阅(配置管理)

数据发布/订阅模型,也就是配置中心,发布者把数据发布到 ZooKeeper 的节点上,供订阅者进行数据订阅,实现配置信息的集中管理和动态更新。例如zookeeper的注册中心。
* 配置信息的特性:
  (1)数据量小,比如配置文件
  (2)数据内容在运行时会经常发生动态变化(比如数据库的切换)
  (3)集群中各个服务器共享配置数据
* zookeeper的推拉结合方式:
   推:服务端会推送给注册了监控节点的客户端 Wathcer 事件通知
   拉:客户端获得通知后,然后主动到服务端拉取最新的数据

3.2 负载均衡

负载均衡用来把对某种资源的访问分摊给不同的设备,从而减轻单点的压力。在分布式环境中,为了保证高可用性,通常同一个服务的提供者会部署多份,而消费者就需要在这些服务器中选择一个来执行相关的业务逻辑。
* 实现思路:
(1)首先建立 Servers 节点,并建立监听器监视 Servers 子节点的状态(用于在服务器增添时及时同步当前集群中服务器列表)
(2)在每个服务器启动时,在 Servers 节点下建立临时子节点 Child Server,并在对应的字节点下存入服务器的相关信息(包括服务的地址,IP,端口等等)
(3)可以自定义一个负载均衡算法,在每个请求过来时从 ZooKeeper 服务器中获取当前集群服务器列表,根据算法选出其中一个服务器来处理请求

3.3 分布式协调/通知

zookeeper中特有watcher注册与异步通知机制,能够很好的实现分布式环境下不同系统之间的通知与协调,实现对数据变更的实时处理。使用方法通常是基于其临时节点的特性,不同机器在 zookeeper 的一个指定节点下创建临时子节点,不同机器之间可以根据这个临时节点来判断客户端机器是否存活。

一种典型的分布式系统机器间的通信方式是心跳。心跳检测是指分布式环境中,不同机器之间需要检测彼此是否正常运行。如果使用zookeeper,检测系统和被检测系统之间并不直接关联起来,而是通过zk上某个节点关联,大大减少系统耦合。

3.4 集群管理

一般用于在集群环境下,对机器在线率有较高要求的场景,能够快速对集群中机器变化作出响应。这时,往往有一个监控系统,实时检测集群机器是否存活。常用做法是:监控系统通过某种手段(比如ping)定时检测每个机器,或者每个机器自己定时向监控系统汇报在线状态。

如果使用zookeeper,可以创建一个节点,在该节点上注册一个 Watcher ,以后每动态加机器,那么就往该节点下创建一个EPHEMERAL类型的临时节点,这样监控系统就能够实时知道机器的增减情况。

3.5 Master选举

利用 ZooKeeper 创建节点 API 接口,提供了强一致性,能够很好保证在分布式高并发情况下节点的创建一定是全局唯一性。

集群机器都尝试创建节点,创建成功的客户端机器就会成为 Master,失败的客户端机器就在该节点上注册一个 Watcher 用于监控当前 Master 机器是否存活,一旦发现 Master 挂了,其余客户端就可以进行选举了。

3.6 分布式锁

分布式锁,这个主要zooKeeper保证了数据的强一致性。锁服务可以分为两类,一个是保持独占,另一个是控制时序。
保持独占:就是所有试图来获取这个锁的客户端,最终只有一个可以成功获得这把锁。其原理是把zk上的一个znode看作是一把锁,通过create znode的方式来实现。所有客户端都去创建 /distribute_lock 节点,最终成功创建的那个客户端也即拥有了这把锁。
控制时序:就是所有来获取这个锁的客户端,最终都是会被安排执行,只是有个全局时序了。/distribute_lock 已经预先存在,客户端在它下面创建临时有序节点(这个可以通过节点的属性控制:CreateMode.EPHEMERAL_SEQUENTIAL来指定)。节点/distribute_lock维持sequence,保证子节点创建的时序性,从而也形成了每个客户端的全局时序。

3.7 分布式队列

队列有两种,一种是先进先出常规队列,另一种是要等到队列成员准本就绪之后的才统一按序执行。

第一种队列:入队操作就是在某个节点下创建自增序的子节点,并把数据(队列大小)放入节点内。出队操作就是先找到该节点下序号最下的那个节点,取出数据,然后删除此节点。
创建完节点后,根据以下步骤确定执行顺序:
(1)通过 get_children() 接口获取该节点下所有子节点
(2)通过自己的节点序号确定在所有子节点中的顺序
(3)如果不是最小的子节点,那么进入等待,同时向比自己序号小的最后一个子节点注册 Watcher 监听
(4)接收到 Watcher 通知后重复1步骤

第二种队列其实是在FIFO队列的基础上作了一个增强。通常预先建立一个节点,并且赋值为n,表示队列大小,之后每次有队列成员加入后,就判断下是否已经到达队列大小,决定是否可以开始执行了。这种用法的典型场景是,分布式环境中,一个大任务Task A,需要在很多子任务完成情况下才能进行。这个时候,凡是一个子任务完成,那么就去某个节点下建立自己的临时时序节点(CreateMode.EPHEMERAL_SEQUENTIAL),当该节点发现自己下面的子节点满足指定个数,就可以进行下一步按序进行处理了。
posted @ 2022-04-06 14:28  OpenSir  阅读(39)  评论(0编辑  收藏  举报