顶层架构 - 消息集群推送方案

news2025/5/18 18:49:16

一、推送基础概念简述

        在即时通讯(IM)系统中,最基础的一件事就是“如何把消息推送给用户”。为了实现这个过程,我们要先了解两种常见的网络通信方式:HTTPWebSocket

1. HTTP 是什么?

HTTP 就像一次性对话:

  • 客户端发起请求,服务端回复一次后,这条连接就断了。

  • 它是“无状态”的,也就是说服务器不会记得你上一次说了什么。

它的好处是使用简单,适合访问网页、发评论这种“说一次就好”的请求。

2. WebSocket 是什么?

WebSocket 则像“打电话”:

  • 一旦连接建立,客户端和服务端可以一直保持联系,随时互相发消息。

  • 它同样是基于 TCP 的,但是比 HTTP 更适合 IM 这种“随时聊”的场景。

这种方式的好处是响应快,不用每次都重新连接。

3. 单机情况:UID 和 Channel 的绑定

在只有一台服务器的情况下,推送消息很简单:

  • 用户上线后会建立一个 WebSocket 连接,系统会记录下他的用户 ID(UID)和对应的连接通道(Channel)。

  • 当有新消息时,直接通过这个通道发出去就行。

就像是你进了聊天室,系统知道你在哪个窗口,喊你一声就能听到。

二、集群推送的基本挑战

        在上节中我们提到:在只有一台服务器时,系统只要记住用户的 UID 和对应的连接通道(Channel),推送消息非常简单。

但如果用户数量多到需要部署多台 WebSocket 服务器怎么办?这时候系统进入“集群模式”,问题也就随之来了。

1. WebSocket 的“局部性”问题

        和 HTTP 不同,WebSocket 是“长连接”,一旦用户连上了某台服务器,他的这条连接就只属于这一台服务器了。

也就是说:

  • 用户 A 连的是 Server1;

  • 如果你在 Server2 上发消息,是没办法直接推给他的。

这就像你给朋友打电话,他接电话的手机只在一台服务器上,你换台服务器就打不通了。

2. UID 和 Channel 不能中心化存储

        很多人第一反应是:“我用 Redis 把 UID 和 Channel 的关系存起来就好了!”

其实不行。为什么?

  • Channel 是服务器本地的一个连接对象,不能序列化,也不能跨服务器使用

  • 换句话说,哪怕你从 Redis 拿到了 UID 对应的 Channel 标识,你也没办法在别的机器上用。

3. 如何找到用户在哪台服务器?

那怎么办?

这时候我们用 Redis 存一张 “UID ↔ 所在服务器 IP” 的表。
比如:

  • UID 1001 在 Server1;

  • UID 1002 在 Server2。

我们叫这个模块“Router 路由服务”。当需要推送时,先查 Redis 知道用户在哪台服务器,然后转发过去。

4. 路由连接数爆炸问题

但是,问题又来了。

如果有 1000 台 WebSocket 服务器,为了支持转发消息,每台 Router 服务都得跟这 1000 台服务器建立连接。

再假设我们有很多台 Router,那连接数就会变得非常庞大,连接被系统自己占满了,真正给用户的反而不够了

这种情况叫“连接数爆炸”,是一种严重的性能隐患。

总结一下,在进入集群推送后,我们面临几个核心挑战:

  • WebSocket 连接是“黏在服务器”上的,不能随便切换;

  • Channel 是局部对象,无法跨服务器传递;

  • 路由虽然能找到人在哪,但会带来连接爆炸的问题。

三、路由层优化方案

        上节我们说到,虽然可以通过 Redis 知道用户在哪台服务器,但如果每个路由服务都要和所有 WebSocket 服务器建连接,会导致连接数爆炸,影响性能。

那怎么优化呢?答案是:加一层中转站,让大家不用“全连接”了。

1. 加一层“中间人”:消息中间件

我们可以在路由和 WebSocket 服务器之间,加一层叫做 消息中间件 的东西,常用的有:

  • Kafka

  • Redis 的发布订阅(Pub/Sub)

  • RabbitMQ 等等

这层中间件就像一个“公共广播站”,大家只要:

  • WebSocket 服务器:订阅广播频道(比如订阅“用户1001的推送”)

  • 路由服务:往广播频道发消息

就能做到“不需要直接连接,也能把消息推过去”。

2. 怎么发消息?

假设你要给 UID 为 1001 的用户发一条消息,流程是这样的:

  1. 路由服务查 Redis,发现 UID 1001 在 Server2;

  2. 路由服务就往“Server2 的频道”发一条消息;

  3. Server2 已经订阅了这个频道,收到后立刻推送给用户。

注意:

  • 这时候,路由服务 不需要 和 Server2 保持 TCP 连接;

  • 所有服务器只要订阅自己的频道即可,不会引发连接爆炸。

3. 多个用户怎么发?

如果你要群发给很多用户,比如 UID 列表是:[1001, 1002, 1003]

做法是:

  • 分别查每个用户所在的服务器;

  • 按服务器分组,比如 1001 和 1003 在 Server2,1002 在 Server1;

  • 给 Server1 和 Server2 各发一条消息,带上用户列表。

WebSocket 服务器拿到后,内部再去一个个推送。

小结一下:

我们通过“消息中间件”解决了连接数爆炸的问题:

  • 路由服务发消息 → 中间件;

  • WebSocket 服务器监听消息 → 收到后推送;

  • 不需要点对点连接,系统更加轻量稳定。

四、推送策略的两种选择

在给用户发消息时,我们有两种主要的推送方式,每种都有自己的适用场景:

1. 精准推送(点对点)

  • 什么是精准推送?
    就是给每个用户单独发送消息,确保消息只送到那个用户对应的服务器和连接。

  • 优点
    消息精准送达,适合私聊或者重要消息,避免不必要的数据浪费。

  • 缺点
    如果用户量很大,需要推送的次数就多,服务器压力大,可能导致延迟或宕机。

2. 集群广播(群发)

  • 什么是集群广播?
    给一个服务器(或多个服务器)广播消息,让服务器自己决定把消息发给哪些用户。

  • 优点
    推送次数少,服务器压力相对低,适合大群聊或通知类消息。

  • 缺点
    每台服务器要自己过滤哪些用户能收到消息,效率上可能稍有损耗。

什么时候用哪个?

  • 私聊和小范围消息,推荐用精准推送,保证消息不漏发。

  • 大群聊或者广播消息,推荐用集群广播,减少系统压力。

简单来说,就是:

  • 想“定点发射”,用精准推送;

  • 想“全网广播”,用集群广播。

五、消息过滤优化手段

在消息推送过程中,不是所有消息都要发给所有用户,过滤消息非常重要,能帮我们节省网络和服务器资源。

1. 把用户ID放在消息头部(Header)

  • 每条消息里带上目标用户的ID(UID)信息。

  • 服务器收到消息后,先看UID,只给对应的用户处理,不用把消息内容全部解析,节省时间。

2. 服务器端过滤

  • 有些消息队列系统支持在服务器端过滤消息,只有符合条件的消息才会被发送给对应的连接。

  • 这样可以减少不必要的数据传输,降低网络负担。

3. 分组或标签过滤

  • 给用户分组或打标签,比如“兴趣爱好”、“地理位置”等。

  • 发送消息时只给相关组的用户推送,避免无效发送。

4. 动态过滤的挑战

  • 动态变化的用户群体和消息目标,让过滤变得复杂。

  • 一些消息系统对动态过滤支持不好,需要通过自定义改造来实现。

六、定制化消息中间件的探索方向

        消息中间件是负责帮我们传递消息的“邮递员”,但有时候现成的“邮递员”不完全符合我们的需求,所以我们会考虑自己定制或改造它们。

1. 适配业务特点

不同业务对消息传递的要求不同,比如速度、可靠性、扩展性等。定制中间件能更好地满足这些需求。

2. 优化性能

定制消息中间件可以减少不必要的开销,比如精简消息格式、减少网络传输,提升整体效率。

3. 灵活过滤和路由

自定义过滤和路由规则,让消息能更精准快速地送达目标用户,减少资源浪费。

4. 解决扩展性问题

随着用户量和消息量增长,普通中间件可能瓶颈明显。定制化设计能更好地支持大规模集群和多层路由。

5. 可控性和维护性

自己定制中间件,可以更方便地监控、调试和升级,减少对第三方依赖。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2378697.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

报表控件stimulsoft教程:如何在报表和仪表板中创建热图

Stimulsoft Ultimate (原Stimulsoft Reports.Ultimate)是用于创建报表和仪表板的通用工具集。该产品包括用于WinForms、ASP.NET、.NET Core、JavaScript、WPF、PHP、Java和其他环境的完整工具集。无需比较产品功能,Stimulsoft Ultimate包含了…

win32相关(字符编码)

字符编码 ASCII编码 ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是最基础的字符编码标准,用于在计算机和其他设备中表示文本 基本概念 7位编码: ASCII使用7位二进制数&#x…

使用Langfuse和RAGAS,搭建高可靠RAG应用

大家好,在人工智能领域,RAG系统融合了检索方法与生成式AI模型,相比纯大语言模型,提升了准确性、减少幻觉且更具可审计性。不过,在实际应用中,当建好RAG系统投入使用时,如何判断接收信息是否正确…

android studio导入项目

如果 gradle-8.0-bin.zip 没有下载成功 可以点击进入这个网站:https://services.gradle.org/distributions/ 找到和自己本版相同的gradle-8.0-bin.zip文件找到自己版本进行下载; 如果下载依赖失败, 可以手动下载依赖编译过程中的jar https://repo.maven.apache.org/…

Autosar Nvm下电存储实现方式-基于ETAS工具

文章目录 前言Autosar Nvm相关定义Nvm Ram Block States状态切换Nvm_WriteAll函数NvBlock配置生成代码分析及使用总结前言 Nvm中存储的数据,一般有两种存储方式,一个是立即存,一个是下电存,之前介绍过立即存的配置,本文介绍下电存的配置及实现 Autosar Nvm相关定义 Nvm…

c# 数据结构 树篇 入门树与二叉树的一切

事先声明,本文不适合对数据结构完全不懂的小白 请至少学会链表再阅读 c# 数据结构 链表篇 有关单链表的一切_c# 链表-CSDN博客 数据结构理论先导:《数据结构(C 语言描述)》也许是全站最良心最通俗易懂最好看的数据结构课(最迟每周五更新~~&am…

Python Bug 修复案例分析:asyncio 事件循环异常引发的程序崩溃 两种修复方法

在 Python 异步编程的工作中,asyncio库为我们提供了高效处理并发任务的强大工具。然而,asyncio在使用过程中也可能因为一些细节处理不当而引发 Bug。下面,我们就来深入分析一个因asyncio事件循环异常导致程序崩溃的典型案例。兴趣的友友可以借…

题单:递归求和

宣布一个重要的事情,我的洛谷有个号叫 题目描述 给一个数组 a:a[0],a[1],...,a[n−1]a:a[0],a[1],...,a[n−1] 请用递归的方式出数组的所有数之和。 提示:递推方程 f(x)f(x−1)a[x]f(x)f(x−1)a[x]; 输入格式 第一行一个正整数 n (n≤100)n (n≤100)…

怎么在excel单元格1-5行中在原来内容前面加上固定一个字?

环境: WPS 2024 问题描述: 怎么在excel单元格1-5行中在原来内容前面加上固定一个字? 解决方案: 1.在Excel中,如果您想在单元格的内容前面添加一个固定的字,可以通过以下几种方法实现: 方法…

OpenHarmony 开源鸿蒙南向开发——linux下使用make交叉编译第三方库——mqtt库

准备工作 请依照这篇文章搭建环境 OpenHarmony 开源鸿蒙南向开发——linux下使用make交叉编译第三方库——环境配置_openharmony交叉编译-CSDN博客 下载 wget ftp://ftp.gnutls.org/gcrypt/gnutls/v3.5/gnutls-3.5.9.tar.xz 解压 tar -xf mkdir ./out cd ./out Cmake命…

数据结构 -- 顺序查找和折半查找

查找的基本概念 基本概念 查找:在数据集合中寻找满足某种条件的数据元素的过程 查找表(查找结构):用于查找的数据集合称为查找表,它由同一类型的数据结构元素(或记录)组成 关键字&#xff1…

信息收集+初步漏洞打点

目标:理解信息收集在渗透测试中的意义,熟悉常用工具用法,完成基本打点测试 一.理论学习: 模块内容说明信息收集分类主动信息收集 vs 被动信息收集目标发现子域名、IP、端口、子站点、目录、接口技术指纹识别Web框架(如…

JavaScript【5】DOM模型

1.概述: DOM (Document Object Model):当页面被加载时,浏览器会创建页面的文档对象模型,即dom对象;dom对象会被结构化为对象树,如一个HTML文档会被分为head,body等部分,而每个部分又…

Cloudflare防火墙拦截谷歌爬虫|导致收录失败怎么解决?

许多站长发现网站突然从谷歌搜索结果中“消失”,背后很可能是Cloudflare防火墙误拦截了谷歌爬虫(Googlebot),导致搜索引擎无法正常抓取页面。 由于Cloudflare默认的防护规则较为严格,尤其是针对高频访问的爬虫IP&…

如何在 Windows 11 或 10 的 CMD 中检查固件

检查 Windows 11 或 10 中现有设备的硬件固件版本,可以帮助用户安装和更新准确的驱动程序,进行故障排除活动,确保兼容性以及维护系统性能。因此,在本教程中,我们将讨论如何在命令提示符(CMD)中使用一些命令查找 Windows 服务器或桌面中硬件固件版本的方法。由于本教程将…

进阶-数据结构部分:3、常用查找算法

飞书文档https://x509p6c8to.feishu.cn/wiki/LRdnwfhNgihKeXka7DfcGuRPnZt 顺序查找 查找算法是指:从一些数据之中,找到一个特殊的数据的实现方法。查找算法与遍历有极高的相似性,唯一的不同就是查找算法可能并不一定会将每一个数据都进行访…

基于QT和FFmpeg实现自己的视频播放器FFMediaPlayer(一)——项目总览

在音视频开发的学习过程中,开发一款视频播放器是FFmpeg进阶的最好实战方法。本文将基于 QT 和 FFmpeg 着手实现自定义视频播放器 FFMediaPlayer,作为系列文章的开篇,我们先来整体了解项目的设计思路、架构与配置。 一、软件设计五大原则​ …

【HCIA】浮动路由

前言 我们通常会在出口路由器配置静态路由去规定流量进入互联网默认应该去往哪里。那么,如果有两个运营商的路由器都能为我们提供上网服务,我们应该如何配置默认路由呢?浮动路由又是怎么一回事呢? 文章目录 前言1. 网络拓扑图2. …

使用instance着色

本节我们学习使用instance着色器进行着色 //拾取var handler new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);handler.setInputAction(function(movement){console.log(movement);var pickedObject viewer.scene.pick(movement.position);if(Cesium.defined(picke…

【NLP 72、Prompt、Agent、MCP、function calling】

命运把我们带到哪里,就是哪里 —— 25.5.13 一、Prompt 1.User Prompt 用户提示词 当我们与大模型进行对话时,我们向大模型发送的消息,称作User Prompt,也就是用户提示词,一般就是我们提出的问题或者想说的话 但是我们…