title: 点赞背后的技术大冒险:分布式事务与SAGA模式

date: 2025/05/07 00:12:40

updated: 2025/05/07 00:12:40

author: cmdragon

excerpt:

在微服务架构中,点赞操作涉及多个服务的数据更新,传统数据库事务在分布式系统中失效,需采用SAGA事务模式。SAGA将事务分解为多个本地事务,通过补偿机制保证最终一致性。每个操作需定义对应的补偿操作,补偿操作需幂等,并记录事务状态和实现超时机制。代码实现包括基础模型定义、事务上下文管理器和核心业务逻辑,测试验证正常和异常流程。生产环境中建议添加事务日志、实现定时补偿任务和服务降级策略。

categories:

  • 后端开发
  • FastAPI

tags:

  • 分布式事务
  • SAGA模式
  • 微服务架构
  • 补偿机制
  • Python实现
  • 事务管理
  • 数据库操作


扫描二维码

关注或者微信搜一搜:编程智域 前端至全栈交流与成长

探索数千个预构建的 AI 应用,开启你的下一个伟大创意https://tools.cmdragon.cn/

1. 分布式事务的挑战与解决方案

在微服务架构中,点赞这类看似简单的操作可能涉及多个服务的数据更新。假设我们有两个微服务:

  • 文章服务(存储文章信息和点赞数)
  • 用户服务(记录用户点赞行为)

当用户点赞时,需要同时更新:

  1. 文章服务的点赞计数器
  2. 用户服务的点赞记录

传统数据库事务在分布式系统中失效,我们需要采用SAGA事务模式。这种模式将事务分解为多个本地事务,通过补偿机制保证最终一致性。

2. SAGA事务模式原理

2.1 执行流程示例

正常流程:

[文章服务+1] -> [用户服务创建记录]

异常处理:

[文章服务+1] -> [用户服务失败] -> [文章服务-1补偿]

2.2 补偿机制要点

  • 每个操作必须定义对应的补偿操作
  • 补偿操作需要幂等(重复执行结果一致)
  • 必须记录事务状态
  • 需要实现事务超时机制

3. 实现代码详解

3.1 基础模型定义

# 文章服务模型
class Article(Tortoise.Model):
id = fields.IntField(pk=True)
title = fields.CharField(max_length=255)
likes = fields.IntField(default=0) # 用户服务模型
class UserLikeRecord(Tortoise.Model):
id = fields.UUIDField(pk=True)
user_id = fields.BigIntField()
article_id = fields.BigIntField()
created_at = fields.DatetimeField(auto_now_add=True) # Pydantic响应模型
class LikeResponse(BaseModel):
article_id: int
current_likes: int
user_record_id: UUID

3.2 事务上下文管理器

class SagaTransaction:
def __init__(self):
self.compensation_actions = [] async def __aenter__(self):
return self async def __aexit__(self, exc_type, exc, traceback):
if exc_type is not None:
await self.compensate() def add_compensation(self, coro_func, *args):
self.compensation_actions.append((coro_func, args)) async def compensate(self):
for coro_func, args in reversed(self.compensation_actions):
try:
await coro_func(*args)
except Exception as e:
logging.error(f"Compensation failed: {str(e)}")

3.3 核心业务实现

@app.post("/articles/{article_id}/like", response_model=LikeResponse)
async def like_article(
article_id: int,
user_id: int = Header(..., alias="X-User-ID")
):
async with SagaTransaction() as saga:
# 第一步:更新文章点赞数
article = await Article.get(id=article_id)
original_likes = article.likes
article.likes += 1
await article.save() # 记录补偿操作(回滚点赞数)
saga.add_compensation(
self.compensate_article_likes,
article_id,
original_likes
) # 第二步:创建用户点赞记录
try:
record = await UserLikeRecord.create(
user_id=user_id,
article_id=article_id
)
except Exception as e:
# 自动触发补偿流程
raise HTTPException(500, "Like record creation failed") # 记录补偿操作(删除记录)
saga.add_compensation(
self.compensate_user_record,
record.id
) return LikeResponse(
article_id=article_id,
current_likes=article.likes,
user_record_id=record.id
) # 补偿方法示例
async def compensate_article_likes(article_id: int, original_count: int):
article = await Article.get(id=article_id)
article.likes = original_count
await article.save() async def compensate_user_record(record_id: UUID):
await UserLikeRecord.filter(id=record_id).delete()

4. 测试与验证

4.1 正常流程测试

async def test_successful_like():
async with AsyncClient(app=app, base_url="http://test") as ac:
response = await ac.post(
"/articles/1/like",
headers={"X-User-ID": "123"}
)
assert response.status_code == 200
data = response.json()
assert data["current_likes"] == 1

4.2 异常流程测试

async def test_failed_transaction():
with patch("UserLikeRecord.create", side_effect=Exception("DB Error")):
response = await ac.post(
"/articles/1/like",
headers={"X-User-ID": "123"}
)
assert response.status_code == 500 # 验证补偿是否执行
article = await Article.get(id=1)
assert article.likes == 0

5. 课后Quiz

Q1:为什么补偿操作需要设计为幂等?

A. 提高系统性能

B. 防止重复补偿导致数据错误

C. 减少数据库连接数

D. 满足HTTP协议规范

正确答案:B

解析:网络重试可能导致补偿操作被多次触发,幂等设计确保多次执行结果一致,避免数据不一致。

Q2:以下哪些情况需要触发补偿机制?(多选)

A. 用户服务数据库连接超时

B. 文章不存在返回404错误

C. 用户重复点赞

D. 数据库主从同步延迟

正确答案:A

解析:404属于业务校验错误应在事务开始前检查,重复点赞属于业务逻辑错误,主从同步属于基础架构问题。只有跨服务操作失败需要补偿。

6. 常见报错与解决方案

报错1:TransactionManagementError - 事务已关闭

TransactionManagementError: Transaction already closed

原因:异步上下文管理器中过早关闭数据库连接

解决方案

  1. 检查事务作用域范围
  2. 确保所有数据库操作在同一个事务上下文中
  3. 更新Tortoise-ORM到最新版本

报错2:HTTP 422 Unprocessable Entity

{
"detail": "Field required"
}

原因:请求体缺少必要字段或类型不匹配

解决方案

  1. 检查请求头是否包含X-User-ID
  2. 验证URL参数类型是否正确
  3. 使用Swagger文档测试接口格式

报错3:TimeoutError - 数据库操作超时

TimeoutError: Connection pool exhausted

原因:数据库连接池不足或查询未优化

解决方案

  1. 增加连接池大小配置:
TORTOISE_CONFIG["connections"]["default"]["pool_size"] = 20
  1. 为高频查询字段添加索引
  2. 使用select_related优化关联查询

7. 生产环境建议

  1. 添加事务日志
class TransactionLog(Tortoise.Model):
transaction_id = fields.UUIDField()
service_name = fields.CharField(max_length=50)
action_type = fields.CharField(max_length=20) # main/compensation
status = fields.CharField(max_length=10) # pending/done/failed
created_at = fields.DatetimeField(auto_now_add=True)
  1. 实现定时补偿任务
async def check_hanging_transactions():
# 查找超过5分钟未完成的事务
pending = await TransactionLog.filter(
status="pending",
created_at__lt=datetime.now() - timedelta(minutes=5)
) for transaction in pending:
# 执行补偿逻辑
await retry_compensation(transaction)
  1. 服务降级策略
  • 当连续补偿失败超过阈值时,触发人工干预警报
  • 提供强制完成事务的管理员接口
  • 实现事务状态查询接口供前端展示

(完整示例代码需配合PostgreSQL数据库运行,安装依赖:fastapi uvicorn tortoise-orm httpx python-multipart

余下文章内容请点击跳转至 个人博客页面 或者 扫码关注或者微信搜一搜:编程智域 前端至全栈交流与成长,阅读完整的文章:点赞背后的技术大冒险:分布式事务与SAGA模式 | cmdragon's Blog

往期文章归档:

点赞背后的技术大冒险:分布式事务与SAGA模式的更多相关文章

  1. 分布式事务 Seata Saga 模式首秀以及三种模式详解 | Meetup#3 回顾

    https://mp.weixin.qq.com/s/67NvEVljnU-0-6rb7MWpGw 分布式事务 Seata Saga 模式首秀以及三种模式详解 | Meetup#3 回顾 原创 蚂蚁金 ...

  2. 通过Dapr实现一个简单的基于.net的微服务电商系统(十九)——分布式事务之Saga模式

    在之前的系列文章中聊过分布式事务的一种实现方案,即通过在集群中暴露actor服务来实现分布式事务的本地原子化.但是actor服务本身有其特殊性,场景上并不通用.所以今天来讲讲分布式事务实现方案之sag ...

  3. 分布式事务:Saga模式

    1 Saga相关概念 1987年普林斯顿大学的Hector Garcia-Molina和Kenneth Salem发表了一篇Paper Sagas,讲述的是如何处理long lived transac ...

  4. Dubbo学习系列之十四(Seata分布式事务方案AT模式)

    一直说写有关最新技术的文章,但前面似乎都有点偏了,只能说算主流技术,今天这个主题,我觉得应该名副其实.分布式微服务的深水区并不是单个微服务的设计,而是服务间的数据一致性问题!解决了这个问题,才算是把分 ...

  5. [跨数据库、微服务] FreeSql 分布式事务 TCC/Saga 编排重要性

    前言 FreeSql 支持 MySql/SqlServer/PostgreSQL/Oracle/Sqlite/Firebird/达梦/Gbase/神通/人大金仓/翰高/Clickhouse/MsAcc ...

  6. Dubbo学习系列之十五(Seata分布式事务方案TCC模式)

    上篇的续集. 工具: Idea201902/JDK11/Gradle5.6.2/Mysql8.0.11/Lombok0.27/Postman7.5.0/SpringBoot2.1.9/Nacos1.1 ...

  7. TX-LCN分布式事务之LCN模式

    什么是LCN模式 LCN模式是TX-LCN分布式事务模式的一种,L-lock-锁定事务单元.C-confirm-确认事务模块状态. notify-通知事务单元 原理 LCN模式是通过Spring AO ...

  8. 分布式事务(Seata) 四大模式详解

    前言 在上一节中我们讲解了,关于分布式事务和seata的基本介绍和使用,感兴趣的小伙伴可以回顾一下<别再说你不知道分布式事务了!> 最后小农也说了,下期会带给大家关于Seata中关于sea ...

  9. AspNetCore&MassTransit Courier实现分布式事务

    在之前的一篇博文中,CAP框架可以方便我们实现非实时.异步场景下的最终一致性,而有些用例总是无法避免的需要在实时.同步场景下进行,可以借助Saga事务来解决这一困扰.在一些博文和仓库中也搜寻到了.Ne ...

  10. 关于如何实现一个Saga分布式事务框架的思考

    关于Saga模式的介绍,已经有一篇文章介绍的很清楚了,链接在这里:分布式事务:Saga模式. 关于TCC模式的介绍,也已经有一篇文章介绍的很清楚了,链接在这里:关于如何实现一个TCC分布式事务框架的一 ...

随机推荐

  1. Hetao P2071 打字游戏 题解 [ 绿 ] [ 最小生成树 ] [ 动态规划 ] [ 编辑距离 ]

    打字游戏:MST 套 dp 好题. 首先看这个数据范围,\(O(n^4)\) 把每两个字符串之前的编辑距离求一下很显然吧. 然后我们观察一下每一个 node 的性质,发现他要么自己打完,要么从别人那里 ...

  2. Nginx 拒绝错误SNI请求以防止源站被扫描

    Nginx 1.19.4 版本更新了一个新的配置,允许使用 ssl_reject_handshake 这个参数来拒绝错误 SNI 请求的握手,可以防止被类似Censys互联网扫码工具扫描出源站ip 在 ...

  3. Processing 使用pixels[]像素数组绘制矩形rect和圆形ellipse

    余温 两次绘制了棋盘格,有了一些经验了,顺着学习态势,我们再接再厉,挖一些技巧.这一次要使用pixels[]数组绘制矩形rect和圆形ellipse,也就是代替rect()和ellipse()两个函数 ...

  4. win11 输入法自定义短语输出日期时间变量

    自定义短语中输入%yyyy%-%MM%-%dd% %HH%:%mm%:%ss%

  5. Kubernetes 编译 kubeadm 修改证书有效期到 100 年

    前言 kubeadm 生成的客户端证书在 1 年后到期.过期后,会导致服务不可用,使用过程中会出现:x509: certificate has expired or is not yet valid. ...

  6. NumPy学习9

    今天学习了NumPy排序和搜索功能 17, NumPy排序和搜索功能 numpy_test9.py : import numpy as np ''' 17, NumPy排序和搜索功能 NumPy 提供 ...

  7. .NET 10 Preview 2 增强了 Blazor 和.NET MAUI

    .NET 团队 3.18 发布了.NET 10 Preview 2(https://devblogs.microsoft.com/dotnet/dotnet-10-preview-2/)..NET 1 ...

  8. C++基础学习--随记

    博客地址:https://www.cnblogs.com/zylyehuo/ 参考"C++基础与深度解析" 一.预备知识 // c++常用工具 /usr/bin/time //查看 ...

  9. BUUCTF---世上无难事

    1.题目 给出一大串密文,说flag藏在其种并且是32位 2.解题 结合题目所给信息,flag在其中32位,并且语句通顺,想来是移位密码,需要加偏移量Mod的那种,最后数字应该代表了key is XX ...

  10. 【软件】Ubuntu下QT的安装和使用

    [软件]Ubuntu下QT的安装和使用 零.前言 QT是应用得比较广泛的程序框架,是因为其跨平台特性比较好,且用C/C++作为开发语言,性能也比较好,故本文介绍如何安装和使用QT,用的版本是QT 6. ...