分布式系统幂等解决方案

《服务器开发技术、方法与实用解决方案》

一、幂等概述

幂等操作的特点是任意多次执行所产生的影响与一次执行的影响相同
幂等函数是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数或方法不会影响系统状态,使用者无需担心重复执行会对系统造成改变

软件系统幂等的目标:

  • 避免非预期重复请求产生副作用,如重复扣款、重复退款
  • 支持服务调用方主动发起重复请求以获得确定性结果,如失败重试、超时重试。对于可重试失败,其结果具有不确定性,最终可能成功,也可能失败,因此对于可重复请求的响应可以不一致,但必须保证不产生副作用。

如系统A调用系统B服务,系统B调用系统C的服务超时而对A返回可重试失败。系统B通过定时任务对该笔业务进行重试,最终成功。系统A采用相同参数发起第二次调用,服务返回成功结果

二、幂等实现

1. 副作用分析

  • 服务调用维度:下游服务是否支持幂等,重复调用的响应是怎样,会产生什么副作用
  • 服务响应维度:对上游重复调用是否会产生副作用;对于不可重试异常、可重试异常、正常执行、重复执行等情况,在响应层面是否需要区分

2. 幂等号设计

随请求发送的具有唯一标识能力的字段,被称为幂等号,幂等号具有唯一性、不变性和传递性的特点

非业务幂等号:如UUID、时间戳。由于非业务幂等号难以通过业务上下文追溯,因此必须持久化,从而保证请求与幂等号的关系有迹可循

业务幂等号:由业务元素组合而成的幂等号,如用户ID+活动ID+商品ID。服务调用方可以不感知业务幂等号,被调用方根据请求参数及业务上下文获取所需参数拼接即可。

3. 幂等数据持久化设计

在真实环境中,任何一次请求的响应都存在不确定性,结果可能是执行成功、可重试异常、不可重试异常、超时异常等,为了确保重复请求得到正确的处理,还需要以下辅助信息:

  • 处理结果标志:如成功、失败、终止
  • 请求关键参数:对于涉及资金安全的业务,重试时通常需要对比关键参数
  • 业务上下文:如请求上游业务流水号、下游业务流水号
  • 处理成功需要返回的信息:如业务流水号、结果码
  • 处理失败需要返回的信息:如错误码、错误描述、重试标志等(如返回不可重试标记,调用方识别后可通过更换幂等号进行进一步重试)
  • 重试相关信息:如重试次数、重试间隔、最近重试时间、业务时间

4. 幂等处理流程设计

在设计幂等处理流程前,应充分评估应用场景是否存在并发情况,若存在,则必须特殊处理。如在进入业务流程前,需先基于幂等号尝试获取全局锁

三、幂等策略

1. 唯一索引策略

使用数据库的唯一索引,可以确保只会插入一条幂等记录。

具体步骤:

  1. 基于幂等号查询幂等记录
  2. 记录存在,根据记录进一步处理
  3. 记录不存在,尝试插入幂等记录
  4. 插入成功,则执行业务逻辑
  5. 插入失败,则处理异常
  6. 返回结果

但是,该方式对于识别重复请求后的业务逻辑没有足够的保障能力。如插入幂等记录成功,但执行后续失败的情况。如果不回滚,是否支持重试;如果回滚,可能需要支持分布式事务

因此单独利用唯一索引只能应对非常简单的场景:除插入幂等记录外,不存在写操作;除插入幂等记录外,存在写操作,但可容忍数据不一致。在实际应用中,这样简单的场景非常少,因此唯一索引策略极少单独使用,大都是联合事务机制和锁机制使用

2. 悲观锁策略

使用悲观锁+事务机制可实现业务幂等
如 select * from table_name where user_id = ‘xxx’ and biz_no = ‘yyy’ for update

悲观锁策略本质是通过将请求串行化来实现幂等,如果业务处理逻辑耗时较多,可能会导致大量线程长时间等待,浪费资源。

3. 分布式锁策略

分布式锁也是通过将请求串行化来实现幂等。但分布式锁更加轻量,对获取锁失败的请求处理更加灵活。

在系统收到请求时,先尝试获取分布式锁,若获取成功,则继续执行业务逻辑;如获取失败,可舍弃请求直接返回,也可继续重试

对获取锁成功后的业务逻辑执行并没有可靠的保障。业务逻辑中可能涉及多次读写操作,任何一次操作都可能失败,从而衍生出数据一致性问题。因此在实际应用中,需要结合事务机制和重试机制才能形成完整方案。事务机制用于保证业务逻辑的数据一致性;重试机制则是基于持久化的幂等记录进行失败重试,保证最终一致性

四、幂等号生成

实际应用中,幂等号可由客户端生成,也可由服务端生成。最显著的差别在于不变性,客户端因为不具备持久化能力,无法保证幂等号的不变性,一旦用户退出页面、关闭会话、重启客户端等,原来的幂等号就会丢失

通常采用Token机制来实现客户端与服务端之间操作的幂等性:

  1. 用户进入界面时,客户端从服务端申请到一个token作为幂等号,并将其存放于客户端会话中。服务端则会将token存储于分布式缓存或数据库中
  2. 用户首次点击提交时,会将该token和表单数据一并提交到服务端,服务端判断该 token 是否存在,若存在且未处理,则执行业务逻辑
  3. 用户重复点击提交时,会将该token和表单数据一并提交到服务端,,服务端判断该 token 是否存在,若存在且已处理成功,则直接返回成功信息,不必重复处理

你可能感兴趣的:(系统架构设计,网络)