doupoa
一个不甘落后的热血青年!
Ping通途说

Redis中选择Pub/Sub与Stream及Gunicorn负载均衡基本原理

0. 前言

生产服务器通过Gunicorn开多个Worker来实现负载均衡,其中有一个业务是需要上传文件到第三方平台的,平台没有提供像OSS临时令牌的功能,因此正常的上传流程就是需要将文件上传到生产服务器,然后生产服务器校验后再上传到平台服务器。

但生产服务部署在云,又是小水管,随便一个大流量直接把主业务的水管堵了。因此我提出了利用公司内网环境和大带宽的优势,做一个附属服务,通过Websocket连接生产服务进行任务下发和状态汇报等,重要的事件或数据还是走HTTP。

运营使用后台系统将文件上传到文件服务器,服务器通过HTTPs从服务端安全获得账户令牌,随后就是上传文件,随便把需要素材的批量任务也做了,最后上报结果。

该方案存在的问题:

文件服务器只会通过Websocket连接到其中一个worker上,其他worker在收到后台下发任务指令的请求时会直接告诉后台“我没连上”,即使侥幸的将请求发送到了有连接的worker,但文件服务器在获取令牌前服务器需要校验文件服务器ID是否与已连接的一致,这样一对比哦豁没有拒绝返回令牌,这样文件服务器会直接判定任务失败。现在生产服务器中有2个worker,这样成功发送并执行的概率就只有1/4。

搁着玩抽奖游戏呢?

1. Redis中选择Pub/Sub或Stream

1.1 Redis Pub/Sub

Pub/Sub是一种通信模式,任意一方(即使跨Redis数据库)可以发起和订阅一个频道(Channel),当有消息时会推送给每一个订阅者,消息不会存储,因此可以承接每秒百万次请求。

就像老师用手机展示一张图片,所有想看的学生可以接过来看并传给其他人,最终手机还是会还给老师(流出频道并销毁),所有学生都不会知道这张图片的制作者是谁。

1.2 选择Pub/Sub的场景

  • 实时广播场景:如在线聊天、实时通知、实时数据推送等,需要将消息即时广播给多个订阅者,且对消息的可靠性要求不高。例如,用户在线状态变化、实时股票行情推送等场景,Pub/Sub能快速将消息发送给所有在线订阅者。
  • 轻量级事件通知:对于一些简单的事件通知,如系统状态变更、任务完成通知等,Pub/Sub可以快速实现事件的发布和订阅,无需复杂的消费逻辑和持久化需求。
  • 临时性通信需求:如果只是临时需要在多个组件或服务之间进行简单的消息传递,且不需要长期存储消息,Pub/Sub的无状态特性可以满足需求,且资源消耗较低。

1.3 Redis Stream

专为消息队列设计,支持持久化、消费者组、ACK确认机制,确保消息可靠消费。每条消息按顺序存储,每个消息有唯一ID,支持范围查询和回溯消费,支持多播,同一消息可被多个消费者组独立消费,组内消费者竞争消费。

所以这实际上就是一个高性能Queue,比List安全(List无持久化),且高效。

现在这个项目的日志之前就是塞Redis List中的,存入的日志通过一个10秒的定时器取出其1000条存入数据库。但有一天心血来潮想对接口进行压测,过量的日志瞬间塞满Redis,服务延迟飙升到400-2000ms。

检查写入的日志发现,每10秒一个周期,延迟会突然爆降至20-50ms,然后又会逐渐上升。虽然业务逻辑上和编程语言本身肯定有一定关系,但Redis日志缓存的类型肯定脱不了关系。

1.4 选择Stream的场景

  • 需要消息持久化:对于关键业务数据、日志记录、交易流水等需要长期存储和可靠处理的消息,Stream支持消息持久化到RDB和AOF文件,即使Redis重启或宕机,消息也不会丢失。
  • 多消费者组消费:当需要多个消费者组独立消费同一份消息流,且每个消费者组需要独立跟踪消费进度时,Stream的消费者组机制可以满足需求。例如,一个消息需要同时触发邮件通知、短信通知、数据分析等多个业务逻辑,每个业务逻辑可以作为一个消费者组独立消费消息。
  • 消息重放和回溯:如果需要支持消息的重放、回溯或离线消费,Stream可以通过消息ID读取历史消息,而Pub/Sub无法实现这一功能。例如,在数据修复、故障排查等场景中,可能需要重新消费历史消息。
  • 高可靠性和顺序性要求:对于对消息的可靠性和顺序性要求较高的场景,如金融交易、订单处理等,Stream通过消费者确认机制(ACK)和消息ID的有序性,可以保证消息的可靠传递和顺序处理。

1.5 总结

  • 如果场景对实时性要求高、消息可靠性要求低,且不需要持久化和重放功能,优先选择Pub/Sub。
  • 如果场景需要消息持久化、多消费者组消费、消息重放、高可靠性和顺序性保障,优先选择Stream。

2.Gunicorn的分流算法和原理

Gunicorn没有自己实现负载均衡算法,而是直接使用操作系统内核(UNIX)连接分发功能。

Gunicorn采用Pre-fork Worker模型,即一个Master主服务管理下级多个Worker。

启动时所有worker进程共享同一个监听 ,当新连接到达时,操作系统内核会自动将连接分发给等待accept()的worker进程。

因此可以通过查看系统进程列表发现会有一个Gunicorn主服务仅占用内存20多MB,其他进程内存使用量大致相同,这些就是Worker。

2.1 操作系统内核是如何分发请求的?

底层连接分发

Gunicorn启动时,主进程会绑定网络端口并监听连接请求。

操作系统内核负责接收网络连接,并根据配置将连接分配给不同的Worker进程。

例如,通过SO_REUSEPORT等 socket 选项,内核可以实现基于哈希算法(如hash(client_ip, client_port) % total_workers)的连接分发,将相同客户端的请求尽量分配到同一Worker进程,以保持连接的稳定性。

这一部分在gunicorn/sock.py中体现,设置SO_REUSEADDR重用地址,可选设置SO_REUSEPORT重用端口,设置为非阻塞模式 (sock.setblocking(0))以及配置backlog队列大小

Python
# ...

    def set_options(self, sock, bound=False):
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        if (self.conf.reuse_port
                and hasattr(socket, 'SO_REUSEPORT')):  # pragma: no cover
            try:
                sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
            except OSError as err:
                if err.errno not in (errno.ENOPROTOOPT, errno.EINVAL):
                    raise
        if not bound:
            self.bind(sock)
        sock.setblocking(0)
 
        # make sure that the socket can be inherited
        if hasattr(sock, "set_inheritable"):
            sock.set_inheritable(True)
 
        sock.listen(self.conf.backlog)
        return sock

# ...

资源管理与调度

操作系统内核负责管理系统的CPU、内存等资源,调度Worker进程的运行。

它决定了哪个Worker进程在何时获得CPU时间片来处理请求,对整体的请求处理效率和并发能力有基础性影响。

2.2 Gunicorn自身的机制

Worker进程管理

Gunicorn的主进程负责创建和管理工作进程(Worker)。

主进程根据配置的Worker数量启动相应数量的Worker进程,每个Worker进程独立处理请求。

主进程会监控Worker进程的状态,当Worker进程异常退出时,主进程会自动重启新的Worker进程,确保服务的稳定性。

并发模型配置

Gunicorn支持多种并发模型,如同步(sync)、异步(gevent、eventlet)、多线程(gthread)等。

对于不同的需求,官方列出了以下推荐方案:

  • CPU密集型应用:使用sync worker
  • I/O密集型应用:使用async worker (gevent/eventlet)
  • 需要长连接:使用gthread或async worker
  • 直接暴露互联网:建议使用async worker配合缓冲代理

0
0
赞赏

doupoa

文章作者

诶嘿

发表回复

textsms
account_circle
email

Ping通途说

Redis中选择Pub/Sub与Stream及Gunicorn负载均衡基本原理
0. 前言 生产服务器通过Gunicorn开多个Worker来实现负载均衡,其中有一个业务是需要上传文件到第三方平台的,平台没有提供像OSS临时令牌的功能,因此正常的上传流程就是需要将文件上传…
扫描二维码继续阅读
2026-03-06

Optimized by WPJAM Basic