精通 Python 设计模式——分布式系统模式

随着技术演进以及对可扩展且具备韧性的系统需求不断增长,理解支配分布式系统的基础模式变得尤为重要。

从管理节点之间的通信到保障容错(FT)一致性,本章将探讨一组关键的设计模式,帮助开发者构建稳健的分布式系统。无论你在搭建微服务还是实现云原生应用,掌握这些模式都能为你有效应对分布式计算的复杂性提供工具。

在本章中,我们将讨论以下主题:

  • 限流(Throttling)模式
  • 重试(Retry)模式
  • 熔断器(Circuit Breaker)模式
  • 其他分布式系统模式

技术要求

请先参见第 1 章中的通用要求。本章代码的额外环境需求如下:

  • 安装 Flask 与 Flask-Limiter:
    python -m pip install flask flask-limiter
  • 安装 PyBreaker:
    python -m pip install pybreaker

限流(Throttling)模式

限流是当今应用与 API 中常会用到的重要模式。在此语境中,限流指控制某个用户(或客户端服务)在给定时间内向某个服务或 API 发送请求的速率 ,以保护服务资源不被过度使用。

例如,我们可以限制某 API 的用户请求数为每日 1,000 次 。达到上限后,再次请求将返回 429(Too Many Requests) 错误,并附带"请求过多"的提示信息。

关于限流有许多需要理解的点:包括选用何种限制策略与算法 、如何度量服务使用情况 等。有关限流模式的技术细节,可参考 Microsoft 的云设计模式目录:
learn.microsoft.com/en-us/azure...

现实示例

  • 高速公路交通管理:红绿灯或限速用来调节车流。
  • 水龙头:调节水流量大小。
  • 演唱会售票:热门演出开售时,网站可能限制每位用户的单次购票数量,以防需求激增导致服务器崩溃。
  • 用电分时计费:一些电力公司按高峰/低谷分时计价,引导用户错峰用电。
  • 自助餐取餐:可能限制一次只取一盘,以保证公平并减少浪费。

实现限流的软件示例:

适用场景

当你需要确保系统持续稳定服务优化服务使用成本 、或应对突发流量时,建议采用该模式。实践中可实现如下规则:

  • 将某 API 的总请求数 限制为 N/天(如 N=1000)。
  • 针对某个 IP、国家或地区 限制 N/天
  • 已认证用户限制读写次数。

除速率限制外,限流也可用于资源分配,确保在多客户端之间公平分配资源。

实现限流模式

在实现之前需了解:限流有多种类型,例如速率限制(Rate-Limit)IP 级限制 (如白名单机制)、以及并发连接数限制 等。前两者较易上手,本节聚焦速率限制

下面以一个最小化的 Flask Web 应用为例,结合 Flask-Limiter 演示速率限制。

导入:

javascript 复制代码
from flask import Flask
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

创建 Flask 应用:

ini 复制代码
app = Flask(__name__)

定义 Limiter 实例 :传入键函数 get_remote_address、应用对象、默认限额等参数:

ini 复制代码
limiter = Limiter(
    get_remote_address,
    app=app,
    default_limits=["100 per day", "10 per hour"],
    storage_uri="memory://",
    strategy="fixed-window",
)

受默认限额保护的路由 /limited

python 复制代码
@app.route("/limited")
def limited_api():
    return "Welcome to our API!"

更严格的路由 /more_limited(每分钟 2 次):

less 复制代码
@app.route("/more_limited")
@limiter.limit("2/minute")
def more_limited_api():
    return "Welcome to our expensive, thus very limited, API!"

常规启动入口:

ini 复制代码
if __name__ == "__main__":
    app.run(debug=True)

测试

运行:python ch09/throttling_flaskapp.py(见图 9.1 -- throttling_flaskapp:Flask 应用启动示例)。

浏览器访问 http://127.0.0.1:5000/limited,可看到欢迎页面(图 9.2 -- /limited 响应)。

多次刷新后,第 第 10 次 会收到 Too Many Requests (429)错误(图 9.3 -- /limited 请求过多)。

再访问第二个路由:http://127.0.0.1:5000/more_limited图 9.4 -- /more_limited 响应)。

若在 1 分钟内刷新超过两次 ,将再次收到 Too Many Requests图 9.5 -- /more_limited 请求过多)。

同时,运行 Flask 服务器的控制台会显示每个收到的 HTTP 请求及响应状态码(图 9.6 -- 服务器控制台输出)。

借助 Flask-Limiter,你可以在 Flask 应用中实现多种限流策略。文档还介绍了不同的限流算法策略存储后端(如 Redis)的用法,便于按需选择与扩展。

重试(Retry)模式

在分布式系统语境下,重试越来越常见。想想微服务或云端基础设施:各组件彼此协作,但往往由不同团队开发、部署与运维。

在云原生应用的日常运行中,部分组件可能会遭遇所谓的瞬时性故障 (transient faults/failures)------看起来像 Bug,但并非应用本身导致,而是网络外部服务器/服务性能 等你无法掌控的约束所致。结果就是:你的应用可能出现异常行为(用户的感知至少如此),甚至在某些位置卡住 。应对这类风险的办法,是引入重试逻辑 :再次调用该服务(可能立刻 ,也可能等待几秒后)以越过这次短暂问题。

现实类比

  • 打电话 :拨打朋友电话但占线或网络不佳,通常会稍等再拨,而不是立刻放弃。
  • ATM 取现 :因临时的网络拥堵/连接问题导致交易失败,稍后再次尝试,往往就能成功。

在软件领域,也有帮助实现重试模式的工具/技术:

适用场景

  • 与外部组件/服务通信时,因网络故障服务器过载 导致的瞬时失败需要被缓解。
  • 注意:不建议 用重试来处理应用逻辑自身错误 引发的内部异常。还需分析外部服务的响应:若频繁出现"忙碌"故障,通常意味着服务端需扩容或修复
  • 微服务 架构相关:服务间通过网络通信,重试能避免短暂故障触发系统级失败
  • 数据同步 :在系统间同步数据时,重试可应对临时不可用的一方。

重试模式的实现示例

下面用"数据库连接"为例,实现一个装饰器驱动的重试机制。

导入与日志:

arduino 复制代码
import logging
import random
import time

logging.basicConfig(level=logging.DEBUG)

重试装饰器 :自动对被装饰的函数进行最多 attempts 次重试。

python 复制代码
def retry(attempts):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(attempts):
                try:
                    logging.info("Retry happening")
                    return func(*args, **kwargs)
                except Exception as e:
                    time.sleep(1)
                    logging.debug(e)
            return "Failure after all attempts"
        return wrapper
    return decorator

模拟数据库连接 :随机抛出"临时错误",并用装饰器设置最多 3 次重试。

java 复制代码
@retry(attempts=3)
def connect_to_database():
    if random.randint(0, 1):
        raise Exception("Temporary Database Error")
    return "Connected to Database"

测试代码:

python 复制代码
if __name__ == "__main__":
    for i in range(1, 6):
        logging.info(f"Connection attempt #{i}")
        print(f"--> {connect_to_database()}")

运行:

bash 复制代码
python ch09/retry/retry_database_connection.py

可能输出:

ruby 复制代码
INFO:root:Connection attempt #1
INFO:root:Retry happening
--> Connected to Database
INFO:root:Connection attempt #2
INFO:root:Retry happening
DEBUG:root:Temporary Database Error
INFO:root:Retry happening
DEBUG:root:Temporary Database Error
INFO:root:Retry happening
DEBUG:root:Temporary Database Error
--> Failure after all attempts
INFO:root:Connection attempt #3
INFO:root:Retry happening
--> Connected to Database
INFO:root:Connection attempt #4
INFO:root:Retry happening
--> Connected to Database
INFO:root:Connection attempt #5
INFO:root:Retry happening
DEBUG:root:Temporary Database Error
INFO:root:Retry happening
DEBUG:root:Temporary Database Error
INFO:root:Retry happening
DEBUG:root:Temporary Database Error
--> Failure after all attempts

当发生临时数据库错误时,会触发最多三次 重试;若三次均失败,则认定本次操作失败。总体来看,重试模式 是处理分布式系统中此类场景的可行手段;但如果错误频繁(如示例中的多次 DB 错误),很可能意味着存在更持久/更严重的问题,应当在服务端或架构层面予以修复或扩容。

熔断器(Circuit Breaker)模式

实现容错(FT)的一种方式是重试 ,我们刚刚已看到。但当与外部组件通信导致的故障很可能是长期存在 时,继续使用重试机制会影响应用的响应性 :对极可能失败的请求反复尝试只是在浪费时间与资源 。此时就需要另一种模式:熔断器模式(Circuit Breaker)

在熔断器模式中,你会用一个特殊的(熔断器)对象包裹 脆弱的函数调用或外部服务的集成点,并监控失败 。一旦失败次数达到阈值,熔断器就会跳闸(打开) ,随后对熔断器的所有调用都会立刻返回错误 ,而不会再去执行被保护的调用。

现实示例

  • 水/电分配回路:断路器(空气开关)在异常时跳闸,保护线路与设备。

  • 软件中的做法:

    • 电商结算:支付网关宕机时,熔断器停止进一步的支付尝试,避免系统雪崩。
    • 有速率限制的 API:当 API 达到限额时,熔断器阻止继续请求,以免触发惩罚。

适用场景

当你的系统组件在与外部组件、服务或资源通信时,需要对长期性故障具备容错能力,推荐使用熔断器模式。下面我们通过示例理解它如何应对这些场景。


实现熔断器模式

假设你要在一个容易出错 (例如受不稳定网络影响)的函数上使用熔断器。我们用 pybreaker 库(pypi.org/project/pyb...)演示。

本实现改编自这个仓库中的脚本:github.com/veltra/pybr...

导入:

javascript 复制代码
import pybreaker
from datetime import datetime
import random
from time import sleep

定义熔断器 :例如在该函数连续失败五次后自动打开(示例代码中设置了参数):

ini 复制代码
breaker = pybreaker.CircuitBreaker(fail_max=2, reset_timeout=5)

被保护的"脆弱函数" (用装饰器语法包裹):

python 复制代码
@breaker
def fragile_function():
    if not random.choice([True, False]):
        print(" / OK", end="")
    else:
        print(" / FAIL", end="")
        raise Exception("This is a sample Exception")

主程序:

python 复制代码
def main():
    while True:
        print(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), end="")
        try:
            fragile_function()
        except Exception as e:
            print(" / {} {}".format(type(e), e), end="")
        finally:
            print("")
            sleep(1)

运行:python ch09/circuit_breaker.py

图 9.7 -- 使用熔断器的程序输出

从输出可以看到,熔断器按预期工作:当其处于打开 状态时,fragile_function() 的调用会立即失败 (抛出 CircuitBreakerError),而不会 尝试真正的受保护操作。超时 5 秒 后,熔断器允许下一次 调用通过:若该次成功,熔断器闭合 ;若失败,则再次打开并等待下一次超时。

其他分布式系统模式

除了本章涵盖的模式,还有许多可用的分布式系统模式,例如:

  • 命令查询职责分离(CQRS) :将读写职责分离,通过针对性的数据模型与操作优化数据访问与可扩展性。
  • 两阶段提交(2PC) :分布式事务协议,通过"准备(prepare)→提交(commit)"两阶段,确保多参与资源间的原子性与一致性
  • Saga :由一系列本地事务 构成的分布式事务,通过补偿机制在部分失败或中止时维持一致性。
  • Sidecar(边车) :在主服务旁部署辅助服务,增强监控、日志、安全等能力,而无需直接修改主应用。
  • 服务注册中心(Service Registry) :集中管理与发现服务,使服务能动态注册/发现彼此,促进通信与可扩展性。
  • 隔舱(Bulkhead) :借鉴船舱隔离思想,对系统资源/组件进行分区隔离,避免故障蔓延,增强容错与韧性。

这些模式针对分布式系统的不同挑战,提供架构与实现层面的策略与最佳实践,帮助设计在动态且不可预测环境中依然稳健、可扩展的系统。

小结

本章深入探讨了分布式系统模式,重点介绍了限流(Throttling)重试(Retry)熔断器(Circuit Breaker) 。这些模式对于构建稳健、容错、高效的分布式系统至关重要:

  • 通过限流,你能有效管理服务负载与资源分配。
  • 掌握重试 的实现,让你的操作在面对瞬时故障时更可靠
  • 学会熔断器 ,即可在优雅处理长期性故障的同时保护系统。

需要牢记:这些模式并非孤立的银弹,而是可组合 的工具,应根据系统的具体需求与约束 加以裁剪与搭配。核心在于理解其原理 ,以便灵活应用,打造韧性强、效率高的分布式系统。

最后,我们简要介绍了其他分布式系统模式,受篇幅所限未能一一展开。
下一章 将聚焦测试相关的模式

相关推荐
IT小番茄2 小时前
Docker容器间互联的Zabbix监控项目知识整理[十一]
架构
小刘大王2 小时前
while循环与死循环
架构·前端框架
数据智能老司机2 小时前
精通 Python 设计模式——并发与异步模式
python·设计模式·编程语言
数据智能老司机2 小时前
精通 Python 设计模式——测试模式
python·设计模式·架构
数据智能老司机2 小时前
精通 Python 设计模式——性能模式
python·设计模式·架构
未来影子2 小时前
SpringAI(GA):MCP Server 服务鉴权(过滤器版)
架构
poemyang3 小时前
技术圈的“绯闻女孩”:Gossip是如何把八卦秘密传遍全网的?
后端·面试·架构
c8i3 小时前
drf初步梳理
python·django
每日AI新事件3 小时前
python的异步函数
python