当前位置: 首页 > news >正文

系统设计中的幂等性

1. 基本概念

幂等性(Idempotence)是系统设计中经常提到的概念。

如果某个操作执行一次或多次都能产生相同的结果,那么它就是幂等的

2. 代码示例

下面这段代码是幂等的。无论你调用多少次,show_my_button 的最终状态都是False。

def hide_my_button(self):self.show_my_button = False

再看一个例子:

def toggle_my_button_visibility(self):self.show_my_button = not self.show_my_button

这个方法不是幂等的,因为每次调用都会翻转状态:第一次隐藏,第二次又显示。

3. 常见误区

3.1 幂等性与返回值无关

很多初学者会误解幂等性,认为“多次调用返回值相同”才叫幂等。这是错误的,来看一个例子:

def hide_my_button(self):has_something_changed = self.show_my_buttonself.show_my_button = Falsereturn has_something_changed

如果第一次调用时 show_my_button 是 True,返回值是 True;再次调用时返回值变成了 False。但这个方法依然是幂等的,因为无论调用多少次,show_my_button 的最终状态总是 False。幂等性关注的是操作的副作用或系统状态的最终结果,而不是方法的返回值。

3.2 幂等与纯函数

这两个概念也容易混淆,所以简单解释一下。
纯函数:给定相同的输入,总是返回相同的输出,且没有任何副作用。

# 这是一个纯函数。square(3) 将始终是相同的数字。
def square(my_number):return my_number ** 2# 这不是纯函数。square(3) 几乎不会产生相同的结果
def square_with_randomness(my_number):return (my_number ** 2) * random.uniform(0, 1)

幂等函数不一定是纯函数。幂等函数可以有副作用:

# 如果每次保存相同的 name,最终数据库的状态保持一致 → 幂等
def save_name(name):my_database.save(name) # 写入数据库, 有副作用return name

4. 幂等性的问题

幂等设计也可能引入问题:如果某条“毒消息”导致消费者每次崩溃,那么该消息永远留在队列中。于是服务不断重启、崩溃、重试,形成死循环。
解决办法:引入 死信队列(DLQ),将处理失败多次的消息转移到 DLQ,以便后续人工排查。

5. 系统设计中的幂等性

在分布式系统中,网络是不可靠的,服务会失败,消息可能重复投递,所以幂等性是保证数据一致性和系统健壮性的关键。以下场景都依赖幂等性:

  • 消息队列的重复消费
  • RESTful API 请求重试
  • 数据库写入与 UPSERT
  • 分布式系统故障恢复
5.1 消息处理

假设我们有一个事件驱动系统:

  • Service A 往消息队列里推送事件
  • Service B 消费这些事件(假设会执行一些计算操作)并写入数据库

如果Service B在计算过程中崩溃,或者Service B与数据库之间存在网络分区,或其他情况发生,那消息和事件将永远丢失。

解决方案:不立即从队列中删除消息,而是等待Service B完成(包括写入数据库),然后再删除消息。

但这带来了一个新问题:同一条消息可能被读取两次。Service B执行计算并写入数据库,但随后发生了一些情况(比如崩溃)。在消息从队列中删除之前,服务已经崩溃。会发生什么?当 Service B 重启后,将继续从最后那条消息开始消费,因此该消息被消费了两次!但这并不成问题,如果 Service B 的处理逻辑是幂等的,那么即使消息被重复消费,最终结果也是一致的。

缺点是需要一些额外的复杂性(需要保证操作幂等)和一些计算资源(可能不必要地多次执行相同操作)。但这些缺点和丢失消息比起来不值一提。

5.2 API

如果你正在构建REST API,其实已经在处理幂等性了。HTTP协议实际上定义了哪些方法应该是幂等的:

HTTP 方法幂等性说明
GET查询资源,多次调用结果相同
PUT完全替换资源,重复 PUT 没有副作用
DELETE删除资源,删除已不存在的资源结果也相同
POST通常用于创建资源,天然非幂等

POST请求:通常在设计上不是幂等的。每个POST通常创建某些内容。但你可以使用幂等键使它们幂等。其工作原理如下:随请求发送一个唯一ID(通常在头中),服务器记住"已经处理过这个ID,因此将返回相同的结果,而不是再次执行工作"。

def create_user(request):idempotency_key = request.headers.get('Idempotency-Key')# 是否已经处理过这个确切的请求?if idempotency_key and already_processed(idempotency_key):return get_cached_response(idempotency_key)# 没有,创建用户user = User.create(request.data)# 缓存响应以备下次使用if idempotency_key:cache_response(idempotency_key, user)return user
5.3 数据库

在数据库操作中,常用的幂等性设计包括:

UPSERT操作(如果存在则INSERT或UPDATE)自然是幂等的。使用相同数据运行update 10次,每次都会得到相同的结果。记录要么创建一次,要么多次更新为相同的值。

5.4 分布式系统

在分布式系统中,幂等性是重试机制的安全基础。

  • 网络调用失败可能导致请求被重发
  • 微服务之间的链路存在超时重试
  • 跨机房、跨服务调用可能会收到重复事件

如果操作不是幂等的,每次重试可能引入数据污染和状态异常。

6. 完整示例:订单处理系统

下面通过一个具体示例将所有内容整合起来。有一个简单的订单处理流水线:订单来自Web应用程序,由订单服务验证,进入队列,然后由订单处理器服务处理并写入数据库。

  • Web App → 提交订单请求
  • API Gateway → 路由、鉴权、限流
  • Order Service → 校验订单并发送消息到 MQ
  • MQ → 存储订单消息
  • Order Processor Service → 消费消息并写入数据库
  • Orders DB → 持久化订单数据
  • Dead-Letter Queue → 收集失败的消息
  • Notification Service → 给用户发送通知

这里的关键是 Order Service 要是幂等的。怎么使Order Service幂等?

Order Service从 MQ 消费消息并执行实际的业务逻辑。当处理消息(即订单事件)时,我们希望:

  1. 检查它是否已被处理
  2. 如果没有,将其插入Orders DB,告知Notification Service发送通知

这是幂等的,因为已经检查它是否已被处理。可以通过在订单表中执行SELECT操作,仅当不存在时才插入。可以对通知服务执行类似操作,或在通知服务内部使用其自己的数据库执行。

注意需要处理并发问题(有两个不同的订单处理服务实例处理同一条消息,并且它们同时执行SELECT),处理消息两次不是一个合理的做法,更好的方案是将此逻辑包装到事务中。

http://www.dtcms.com/a/351215.html

相关文章:

  • QPSK调制解调通信仿真程序调试与分析
  • UbuntuV24.04安装mpdecimal库(libmpdec),从源码编译
  • 广告推荐模型3:域感知因子分解机(Field-aware Factorization Machine, FFM)
  • 机器人 - 无人机基础(6) - 状态估计(ing)
  • 1 线性模型
  • 支持向量机(SVM)
  • Java 大视界 -- Java 大数据机器学习模型在金融市场波动预测与资产配置动态调整中的应用
  • 网站开发用什么语言好
  • CentOS扩容非LVM分区
  • PortSwigger靶场之Blind SQL injection with out-of-band interaction通关秘籍
  • Redis--2
  • 在 TencentOS 3 上部署 OpenTenBase:从底层原理到生产级实践的深度指南
  • DBeaver下载安装使用
  • 支持向量机(SVM)算法总结
  • 大数据毕业设计选题:基于大数据的用户贷款行为数据分析系统Spark SQL核心技术
  • 迷你版Shell:源码详解与行为解析
  • 【Linux 34】Linux-主从复制
  • 嵌入式学习日记(34)HTTP协议
  • 支持向量机核心知识总结
  • 读懂支持向量机(SVM)
  • CI/CD 全链路实践:从 Git 基础到 Jenkins + GitLab 企业级部署
  • Flask 之上下文详解:从原理到实战
  • IDEA-Maven和Tomcat乱码问题
  • 2025改版:npm 新淘宝镜像域名地址
  • Uniapp(Vue2)Api请求封装
  • 企业级集群部署gpmall商城:MyCat+ZooKeeper+Kafka 环境部署与商城应用上线流程
  • VxWorks 核心数据结构详解 【消息队列、环形缓冲区、管道、FIFO、双缓冲区、共享内存】
  • Debian Buster 软件源失效问题
  • 在分布式环境下正确使用MyBatis二级缓存
  • 虚拟滚动优化——js技能提升