小小的在线状态绿点,难道牵出的是一整套分布式状态难题?

2026-10-10 07:271阅读0评论工具资源
  • 内容介绍
  • 文章标签
  • 相关推荐

摸鱼。 产品经理:「隔壁老王的IM,头像下面有个绿点,咱们也加一个,easy吧?」

简单来说... 如果对方只想到:存个布尔值, 上线置 true、下线置 false,一张 Redis 表搞定,半天的活,那确实 easy!但实际情况是 在 IM 业务场景里尤其是在服务端的 IM 架构设计,不容简单的是它要在「全部看得见它的人」眼里同时也、实时、准确地亮起或熄灭。任意一件没处理良好,用户都会觉得「当前这个在线状态不准」——而不准比没有更糟,这是因为它误导人。

IM分布式架构系列(19)  小小的在线状态绿点|牵出一整套分布式状态难题

一、 在线状态为哪些是个不容简单题

IM 的在线状态核心问题在于:系统怎么了解某用户此刻在不在线、在哪几个端、对外该体现成哪些。 C位出道。 它的位置很特殊——既是给消息投递用的内部路由依据,又是给终端用户看的产品数据。

1. 让在线状态翻车的三个场景

场景一:上线扇出风暴。 一个 500 良好友的用户打开 App, 若系统老实把「我上线了」推给各个在线良好友,这一下就是上百个推送;早较高峰几十万人同时也上线,当前这个扇出会把推送链路瞬间顶起来。状态本身一个字节,扇出却是 O,动手。。

场景二:状态自己不会熄灭。 上线时置成在线, 但「下线」往往收不到——手机没电、进电梯断网、进程被杀,都不会规矩地发一条「我下线了」。只靠显式事件维护,「永远在线」的幽灵状态越攒越更多,他破防了。。

闹笑话。 场景三:更多端各说各话。 手机、电脑同时也在线,电脑退出后整体状态应当还是「在线」。若状态只是用户级布尔值, 电脑退出一置 false,手机端就被误判离线, 消息走离线推送——用户明明盯着手机屏幕, 却收到一条厂商推送。

2. 分布式周边环境下对绿点的苛刻要求

为了让当前这个绿点不掉链子, 分布式架构必须要满足:,本质上…

  • 读要全局可见;
  • 写要迅速、扛抖动;
  • 扩散要可控;
  • 状态能自愈。

二、存储选型与推/拉模型

放 Redis 接近是中等规模项目的共识——内存级延迟扛得住较高频变更, 又全局可见。但具体怎么存?这里有四个常见方案的权衡:

- 顺便聊个题外话:很更多开发者在发布技术手段博客后会苦恼“为哪些百度不收录”,其实这通常涉及三个维度:一是站点地图未提交引起爬虫找不到路;二是页面内容质量较低或反复率较高被判定为较低质页;三是崭新站缺乏较高质量外链引导。提议通过百度搜索资源条件平台提交链接并优化 TDK 标签来提升抓取概率。 -
方案 结构 更多端可分辨 过期粒度 优势/适用
用户级 String online: → 1/0 否 只能整 key 过期 最简洁, 不支持更多端
用户级 Hash online: → {mobile:host, pc:host} 是 只能整 key 过期 查整人状态迅速, 但过期粒度粗
设备级 String online:: → host是 每设备独立 TTL 主流选择, 更多端语义整洁
全局 ZSet online:all → member=uid, score=ts 否 靠 score 当时间段戳扫 适合「批量查谁在线」

1. 设备级 String 的实操细节

on_user_online:    SET online:: = host  EX 90 // 90 秒过期on_heartbeat:    EXPIRE online:: 90 // 每心跳续期on_user_offline:    DEL online::// 迅速路径立刻熄灭

这里的设计关键在于:TTL 要比心跳间隔较大、但不能较大太更多。设较小了简单误判离线, 设较大了幽灵状态滞留太久。经验上取心跳间隔的 2~3 倍是稳妥起点。

2. 推 vs 拉:经典的扩散不容简单题

当一个人上线时, 怎样通知他的良好友?

  • 纯拉: 服务端逻辑极简, 但无效申请更多且实时性差。适合良好友更少且对实时性不敏感的场景。
  • 纯推: 将变更推给全部「反向良好友」。优势是实时 a , 但不足是在较高峰期会引发巨较大的写压力和网络风暴。
  • 折中方案: 这是较大更多数成熟产品的选择。登录时一次性拉回当前良好友列表的状态解决寒冷启动;之后只对「此刻正看着良好友列表」的用户推增量更崭新。

三、从订阅模型到同步服务

为了进一步压较低扩散投入成本, 我们引入了订阅模型谁想实时了解某人状态就显式订阅他,脑子呢?。

subscribe // A 进入聊天页订阅 娱乐on_status_change:   subscribers = get_subscribers // 只找订阅者   push_to // 精准投递

某钉 PPM 模型带来的启示

在一些企业级 IM 中采用了所谓的「推优先模型 」。它将在线状态直接耦合进同步决策流中:同步服务在发送消息前先查用户的各端在线状态 $\rightarrow$ 如果手机端在则推手机 $\rightarrow$ 如果全部离线则走厂商推送通道。

这揭示了一个反直觉的展示状态和投递状态能够不一致。给 B 看的绿点能够容忍几秒延迟 , 但决定消息走哪个通道的判定必须要尽有可能准确且具备降级机制 ,中肯。。

四、怎样把一个绿点做稳?

最后说一句。 ))}{li}分级实时性策略: 不要追求全部人的绿点都绝对实时。聊天页正在交互的对象 $\rightarrow$ 较高优先级实时推;良好友列表 $\rightarrow$ 中优先级合并推;群成员 $\rightarrow$ 较低优先级按需拉取迅速照。}去抖动窗口机制: 在薄弱网周边环境下「上线$\rightarrow$下线$\rightarrow$上线」会在较短时间段内频繁发生。服务端应设置一个 $200 \sim 500ms$ 的去抖动窗口 de , 只推送最终还是态 a ,避免链路被无意义的状态变化波动填满 a 。}解耦展示态与事实态: 「隐身」「勿扰」这类 状态绝较大更多数应当是客户端展示态 a ,而不应作用于服务端的投递判定 a 。服务端只管物理上的 Online/Offline , 而 Invisible 等标志位作为独立属性由前端合并渲染 de 。}}

最后再来看的一点思考

**可观测性**是在线服务的生命线 a 。这是因为分布式系统没有天然的正确性校验 , 你很不容简单一眼看出此时有更多更少个幽灵账号 de 。提议沉重点监控三个指标 :**** 、**** 以及 **** a ,整起来。。

"较小较小的绿点"背后其实是对 CAP 定理的一次微观实践 :你想要绝对的一致性吗?那你就得牺牲可用性或响应时间段 de 。 害... 而在 IM 当前这个追求极致体验的产品里 , 最良好的架构往往不是最先进的技术手段栈 , 而是一套精妙的权衡之术 a 。`

摸鱼。 产品经理:「隔壁老王的IM,头像下面有个绿点,咱们也加一个,easy吧?」

简单来说... 如果对方只想到:存个布尔值, 上线置 true、下线置 false,一张 Redis 表搞定,半天的活,那确实 easy!但实际情况是 在 IM 业务场景里尤其是在服务端的 IM 架构设计,不容简单的是它要在「全部看得见它的人」眼里同时也、实时、准确地亮起或熄灭。任意一件没处理良好,用户都会觉得「当前这个在线状态不准」——而不准比没有更糟,这是因为它误导人。

IM分布式架构系列(19)  小小的在线状态绿点|牵出一整套分布式状态难题

一、 在线状态为哪些是个不容简单题

IM 的在线状态核心问题在于:系统怎么了解某用户此刻在不在线、在哪几个端、对外该体现成哪些。 C位出道。 它的位置很特殊——既是给消息投递用的内部路由依据,又是给终端用户看的产品数据。

1. 让在线状态翻车的三个场景

场景一:上线扇出风暴。 一个 500 良好友的用户打开 App, 若系统老实把「我上线了」推给各个在线良好友,这一下就是上百个推送;早较高峰几十万人同时也上线,当前这个扇出会把推送链路瞬间顶起来。状态本身一个字节,扇出却是 O,动手。。

场景二:状态自己不会熄灭。 上线时置成在线, 但「下线」往往收不到——手机没电、进电梯断网、进程被杀,都不会规矩地发一条「我下线了」。只靠显式事件维护,「永远在线」的幽灵状态越攒越更多,他破防了。。

闹笑话。 场景三:更多端各说各话。 手机、电脑同时也在线,电脑退出后整体状态应当还是「在线」。若状态只是用户级布尔值, 电脑退出一置 false,手机端就被误判离线, 消息走离线推送——用户明明盯着手机屏幕, 却收到一条厂商推送。

2. 分布式周边环境下对绿点的苛刻要求

为了让当前这个绿点不掉链子, 分布式架构必须要满足:,本质上…

  • 读要全局可见;
  • 写要迅速、扛抖动;
  • 扩散要可控;
  • 状态能自愈。

二、存储选型与推/拉模型

放 Redis 接近是中等规模项目的共识——内存级延迟扛得住较高频变更, 又全局可见。但具体怎么存?这里有四个常见方案的权衡:

- 顺便聊个题外话:很更多开发者在发布技术手段博客后会苦恼“为哪些百度不收录”,其实这通常涉及三个维度:一是站点地图未提交引起爬虫找不到路;二是页面内容质量较低或反复率较高被判定为较低质页;三是崭新站缺乏较高质量外链引导。提议通过百度搜索资源条件平台提交链接并优化 TDK 标签来提升抓取概率。 -
方案 结构 更多端可分辨 过期粒度 优势/适用
用户级 String online: → 1/0 否 只能整 key 过期 最简洁, 不支持更多端
用户级 Hash online: → {mobile:host, pc:host} 是 只能整 key 过期 查整人状态迅速, 但过期粒度粗
设备级 String online:: → host是 每设备独立 TTL 主流选择, 更多端语义整洁
全局 ZSet online:all → member=uid, score=ts 否 靠 score 当时间段戳扫 适合「批量查谁在线」

1. 设备级 String 的实操细节

on_user_online:    SET online:: = host  EX 90 // 90 秒过期on_heartbeat:    EXPIRE online:: 90 // 每心跳续期on_user_offline:    DEL online::// 迅速路径立刻熄灭

这里的设计关键在于:TTL 要比心跳间隔较大、但不能较大太更多。设较小了简单误判离线, 设较大了幽灵状态滞留太久。经验上取心跳间隔的 2~3 倍是稳妥起点。

2. 推 vs 拉:经典的扩散不容简单题

当一个人上线时, 怎样通知他的良好友?

  • 纯拉: 服务端逻辑极简, 但无效申请更多且实时性差。适合良好友更少且对实时性不敏感的场景。
  • 纯推: 将变更推给全部「反向良好友」。优势是实时 a , 但不足是在较高峰期会引发巨较大的写压力和网络风暴。
  • 折中方案: 这是较大更多数成熟产品的选择。登录时一次性拉回当前良好友列表的状态解决寒冷启动;之后只对「此刻正看着良好友列表」的用户推增量更崭新。

三、从订阅模型到同步服务

为了进一步压较低扩散投入成本, 我们引入了订阅模型谁想实时了解某人状态就显式订阅他,脑子呢?。

subscribe // A 进入聊天页订阅 娱乐on_status_change:   subscribers = get_subscribers // 只找订阅者   push_to // 精准投递

某钉 PPM 模型带来的启示

在一些企业级 IM 中采用了所谓的「推优先模型 」。它将在线状态直接耦合进同步决策流中:同步服务在发送消息前先查用户的各端在线状态 $\rightarrow$ 如果手机端在则推手机 $\rightarrow$ 如果全部离线则走厂商推送通道。

这揭示了一个反直觉的展示状态和投递状态能够不一致。给 B 看的绿点能够容忍几秒延迟 , 但决定消息走哪个通道的判定必须要尽有可能准确且具备降级机制 ,中肯。。

四、怎样把一个绿点做稳?

最后说一句。 ))}{li}分级实时性策略: 不要追求全部人的绿点都绝对实时。聊天页正在交互的对象 $\rightarrow$ 较高优先级实时推;良好友列表 $\rightarrow$ 中优先级合并推;群成员 $\rightarrow$ 较低优先级按需拉取迅速照。}去抖动窗口机制: 在薄弱网周边环境下「上线$\rightarrow$下线$\rightarrow$上线」会在较短时间段内频繁发生。服务端应设置一个 $200 \sim 500ms$ 的去抖动窗口 de , 只推送最终还是态 a ,避免链路被无意义的状态变化波动填满 a 。}解耦展示态与事实态: 「隐身」「勿扰」这类 状态绝较大更多数应当是客户端展示态 a ,而不应作用于服务端的投递判定 a 。服务端只管物理上的 Online/Offline , 而 Invisible 等标志位作为独立属性由前端合并渲染 de 。}}

最后再来看的一点思考

**可观测性**是在线服务的生命线 a 。这是因为分布式系统没有天然的正确性校验 , 你很不容简单一眼看出此时有更多更少个幽灵账号 de 。提议沉重点监控三个指标 :**** 、**** 以及 **** a ,整起来。。

"较小较小的绿点"背后其实是对 CAP 定理的一次微观实践 :你想要绝对的一致性吗?那你就得牺牲可用性或响应时间段 de 。 害... 而在 IM 当前这个追求极致体验的产品里 , 最良好的架构往往不是最先进的技术手段栈 , 而是一套精妙的权衡之术 a 。`