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

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

一、推送基础概念简述

        在即时通讯(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. 可控性和维护性

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

相关文章:

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

一、推送基础概念简述 在即时通讯(IM)系统中,最基础的一件事就是“如何把消息推送给用户”。为了实现这个过程,我们要先了解两种常见的网络通信方式:HTTP 和 WebSocket。 1. HTTP 是什么? HTTP 就像一次性…...

Python训练打卡Day26

函数专题1:函数定义与参数 知识点回顾: 函数的定义变量作用域:局部变量和全局变量函数的参数类型:位置参数、默认参数、不定参数传递参数的手段:关键词参数传递参数的顺序:同时出现三种参数类型时 到目前为…...

构建优雅对象的艺术:Java 建造者模式的架构解析与工程实践

一、建造者模式的本质与核心价值 在面向对象的软件设计中,创建复杂对象一直是一个需要精心处理的问题。当一个对象的构建需要多个步骤,并且这些步骤具有不同的组合方式时,传统的构造函数方式会显得力不从心。建造者模式(Builder …...

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

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

(8)python开发经验

文章目录 1 下载python2 pip安装依赖无法访问3 系统支持4 下载python文档5 设置虚拟环境6 编译安装python 更多精彩内容👉内容导航 👈👉Qt开发 👈👉python开发 👈 1 下载python 下载地址尽量不要下载最新版…...

0x08.Redis 支持事务吗?如何实现?

回答重点 Redis 支持事务,但它的事务与 MySQL 等关系型数据库的事务有着本质区别。MySQL 中的事务严格遵循 ACID 特性,而 Redis 中的事务主要保证的是命令执行的原子性和隔离性,即所有命令在一个不可分割的操作中顺序执行,不会被其他客户端的命令请求所打断。 最关键的区…...

win32相关(字符编码)

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

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

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

VSCode + Cline AI辅助编程完全指南

VSCode Cline AI辅助编程完全指南 在当今AI快速发展的时代,程序员可以通过AI工具极大地提高工作效率。本教程将详细介绍如何使用VSCode结合Cline(Claude AI助手)进行AI辅助编程,帮助你提高开发效率,解决复杂问题。 …...

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)…...

融智学视域下的系统性认知增强框架——基于文理工三类AI助理赋能HI四阶跃迁路径

融智学视域下的系统性认知增强框架 ——基于文理工三类AI助理赋能HI四阶跃迁路径 一、如何排除50个认知偏差:消除50类偏差的精准矫正系统 1. 技术架构 文科AI: 构建文化语义场(Cultural Semantic Field, CSF),通过…...

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

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

使用 Vue Tour 封装一个统一的页面引导组件

项目开发过程中需要实现用户引导功能,经过调研发现一个好用的 Vue 插件 vue-tour,今天就来分享一下我是如何基于 vue-tour 封装一个统一的引导组件,方便后续在多个页面复用的。 📦 第一步:安装 vue-tour 插件 首先安装…...

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框架(如…...

2025年01月10日浙江鑫越系统科技前端面试

目录 vue2 和 vue3 的区别vue 怎么封装组件js 怎么把一个数组置空怎么组件自己调用自己的组件v-bind:attribute 和 v-bind“{attribute}” 的区别var let const 的区别this 指向作用域链闭包原型链事件循环 1. vue2 和 vue3 的区别 Vue 2 和 Vue 3 在多个方面存在区别&#…...

JavaScript【5】DOM模型

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

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

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

鸿蒙OSUniApp 实现的表单验证与提交功能#三方框架 #Uniapp

UniApp 实现的表单验证与提交功能 前言 在移动端应用开发中,表单是用户与应用交互的重要媒介。一个好的表单不仅布局合理、使用方便,还应该具备完善的验证与提交功能,以确保用户输入的数据准确无误。本文将分享如何在 UniApp 中实现表单验证…...

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

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

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

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

Oracle 11.2.0.4 pre PSU Oct18 设置SSL连接

Oracle 11.2.0.4 pre PSU Oct18 设置SSL连接 1 说明2 客户端配置jdk环境3服务器检查oracle数据库补丁4设置ssla 服务器配置walletb 上传测试脚本和配置文件到客户端c 服务器修改数据库侦听和sqlnet.orad 修改客户端的sqlnet.ora和tnsnames.ora的连接符e 修改java代码的数据连接…...

服务器连接多客户端

一、epoll 核心函数详解 1. epoll_create/epoll_create1 - 创建 epoll 实例 c #include <sys/epoll.h> int epoll_create(int size); // Linux 2.6.8前需指定size&#xff08;>1&#xff09;&#xff0c;后续版本可忽略 int epoll_create1(int flags); // 推荐使用…...

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

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

服务器死机了需要检查哪些问题

在这个数字化的时代&#xff0c;服务器就像是我们信息世界的“大管家”&#xff0c;可要是它突然死机了&#xff0c;那可真是让人头疼。今天咱们就来聊聊&#xff0c;服务器死机了&#xff0c;到底需要检查哪些问题。 一、硬件问题 电源供应&#xff1a;检查电源是否稳定&…...