下载APP 编程狮,随时随地学编程
返回 首页

架构师之路

QQ状态同步究竟是推还是拉?

前面两篇讲即时通讯核心技术的文章

微信为什么不丢消息?

http如何像tcp一样实时的收消息?

反馈还可以,故继续即时通讯这一个系列吧,今天聊聊即时通讯中的“状态”。


需求缘起

在线状态一致性”(好友在线状态,群友在线状态)是即时通讯领域较难解决的一个技术问题,如何精准实时的获得好友、群友的在线状态,是今天将要探讨的话题。


好友状态一致性

问题一:用户uid-A登录时,如何获取自己全部好友的在线状态?

回答:

用户在线状态

(1)服务器要存储所有用户的在线状态(往往存储在保证高可用的缓存集群里) -> 保证状态可查


用户离线状态
(2)用户状态实时变更,任何用户登录时,需要将服务端自己的在线状态置为online;任何用户登出时,需要将服务端自己的状态置为offline -> 保证服务端状态存储的一致性与实时性

好友状态获取的一致性与实时性

(3)uid-A登录时,先去数据库拉取自己的好友列表,再去缓存获取所有好友的在线状态 -> 保证登录时好友状态获取的一致性与实时性


问题二:用户uid-A的好友uid-B状态改变时(由登录、登出、隐身等动作触发),uid-A如何知道这一事件?

方案一:uid-A向服务器轮询拉取uid-B(其实是自己的全部好友)的状态,例如每1分钟一次

缺点

(1)如果uid-B的状态改变,uid-A获取不实时,可能有1分钟时延

(2)如果uid-B的状态不改变,uid-A会有大量无效的轮询请求,占用服务器资源


方案二:uid-B状态改变时(由登录、登出、隐身等动作触发),服务器不仅在缓存中修改uid-B的状态,还要将这个状体改变的通知推送给uid-B的在线反向好友(反向好友是指:加了uid-B为好友的人,而不是uid-B的好友,这个细节要注意)
在线反向好友推送

优点:

(1)实时

缺点:

(2)当在线好友量很大时,任何一个用户状态的改变,会扩散成N个实时通知,这个N叫做“消息风暴扩散系数”。

假设一个im系统平均每个用户有200个反向好友,平均有20%的反向好友在线,那么消息风暴扩散系数N=40,这意味着,任何一个状态的变化会变成40个推送请求。


群友状态一致性

问题三:群友状态一致性有什么不同,和好友状态一致性相比复杂在哪里?为什么不能采用实时推送

回答:

理论上群友状态也可以通过实时推送的方式实现,以保证实时性。但实际上,群友状态一般都是采用拉取的方式获得,因为群友状态“消息风暴扩散系数”N实在太大,全部实时获取系统往往承受不了

假设平均每个用户加了20个群,平均每个群有200个用户,依然假设20%的用户在线,那么为了保证群友状态的实时性,每个用户登录,就要将自己的状态改变通知发送给20*200*20%=800个群友,N=800,意味着,任何一个状态的变化会变成800个推送请求。

XXX系统使用的是群友状态推送,不存在的这样的问题?那很可能是,XXX系统的用户量和活跃度还不够高吧。


问题四:轮询拉取群友状态也会给服务器带来过大的压力,还有什么优化方式?

回答:

群友的数据量太大,虽然每个用户平均加入了20个群,但实际上并不会每次登录都进入每一个群。不采用轮询拉取,而采用按需拉取,延时拉取的方式,在真正进入一个群时才实时拉取群友的在线状态,是既能满足用户需求(用户感觉是状态是实时、一致的,但其实是进入群才拉取的),又能降低服务器压力。这是一种常见方法。


关于更多按需拉取,延时拉取的讨论,可移步《微信为啥这么省流量》。

延伸讨论:系统消息/开屏广告的推送与拉取

问题五:系统消息/开屏广告一般采用推送还是拉取?

回答:

不考虑APP端的push(APP端的push,不需要启动APP,不依赖client与server之间的TCP长连接),个人强烈建议系统消息/开屏广告这类消息采用“拉取”的方式,原因是:

(1)这类业务对消息的实时性往往要求不高

(2)如果集中推送,“消息风暴扩散系数”过大,容易引发系统抖动;而拉取的方式,可以摊平这个抖动,用户登录时均匀的发起请求

(3)如果集中推送,往往不在意用户是否“在线”,往往会造成大量离线垃圾消息;而拉取的方式,保证只有在线的用户才会收到请求

(4)…

有不同的建议,欢迎评论讨论。


总结与建议

状态的实时性与一致性是一个较难解决的技术问题,不同的业务接受度,不同的数据量并发量在线量,实现方式不同,个人建议的方式是:

(1)好友状态,如果对实时性要求较高,可以采用推送的方式同步;如果实时性要求不高,可以采用轮询拉取的方式同步

(2)群友的状态,由于消息风暴扩散系数过大,可以采用按需拉取,延时拉取的方式同步

(3)系统消息/开屏广告等对实时性要求不高的业务,可以采的方式获取消息

(4)“消息风暴扩散系数”是指一个消息发出时,变成N个消息的扩散系数,这个系数与业务及数据相关,一定程度上它的大小决定了技术采用推送还是拉取


目录

架构师 通用设计与方法论

架构 秒杀系统优化思路
架构 细聊分布式ID生成方法
互联网架构,如何进行容量设计?
线程数究竟设多少合理
单点系统架构的可用性与性能优化
一分钟了解负载均衡的一切
lvs为何不能完全替代DNS轮询
如何实施异构服务器的负载均衡及过载保护?
究竟啥才是互联网架构“高并发”
究竟啥才是互联网架构“高可用”
100亿数据1万属性数据架构设计
架构设计中常见“反向依赖”与解耦方案
典型数据库架构设计与实践

架构师 典型架构实践

TCP接入层的负载均衡、高可用、扩展性架构
配置”也有架构演进?看完深有痛感
跨公网调用的大坑与架构优化方案
DNS在架构设计中的巧用
session一致性架构设计实践
互联网智能广告系统简易流程与架构
计数系统架构实践一次搞定

架构师 数据库与缓存

数据库软件架构设计些什么
细聊冗余表数据一致性
缓存架构设计细节二三事
缓存与数据库一致性优化
主从DB与cache一致性优化
DB主从一致性架构优化4种方法
多库多事务降低数据不一致概率
mysql并行复制降低主从同步延时的思路与启示
互联网公司为啥不使用mysql分区表?
即使删了全库,保证半小时恢复
啥,又要为表增加一列属性?
这才是真正的表扩展方案
一分钟掌握数据库垂直拆分
单KEY业务,数据库水平切分架构实践
数据库秒级平滑扩容架构方案
100亿数据平滑数据迁移,不影响服务
58到家数据库30条军规解读
再议58到家数据库军规
业界难题-“跨库分页”的四种方案
用uid分库,uname上的查询怎么办?
mysql-proxy数据库中间件架构

架构师 服务化与微服务

互联网架构为什么要做服务化?
微服务架构多“微”才合适?
为什么说要搞定微服务架构,先搞定RPC框架?
微服务架构之RPC-client序列化细节
RPC-client异步收发核心细节

架构师 消息系统

http如何像tcp一样实时的收消息?
微信为什么不丢消息?
微信为啥不丢“离线消息”?
群消息这么复杂,怎么能做到不丢不重?
QQ状态同步究竟是推还是拉?
微信多点登录与QQ消息漫游架构随想
消息“时序”与“一致性”为何这么难?
58到家通用实时消息平台架构细节(Qcon2016)
微信为啥这么省流量?
应用层/安全层/传输层如何进行协议选型?

架构师 消息总线架构

到底什么时候该使用MQ?
1分钟实现“延迟消息”功能
消息总线能否实现消息必达?
消息总线真的能保证幂等?
10w定时任务,如何高效触发超时
58到家MQ如何快速实现流量削峰填谷

架构师 搜索架构

深入浅出搜索架构引擎、方案与细节(上)
如何迅猛的实现搜索需求
百度如何能实时检索到15分钟前新生成的网页?

架构师 架构实践

好架构是进化来的,不是设计来的(58架构演进)
58同城推荐系统架构设计与实现
从0开始做互联网推荐-以58转转为例
从0开始做垂直O2O个性化推荐-以58到家美甲为例
58到家入驻微信钱包的技术优化
创业公司快速搭建立体化监控之路(WOT2016)
巧用CAS解决数据一致性问题
百度咋做长文本去重
如何快速实现高并发短文检索
如何实现超高并发的无锁缓存?
“id串行化”到底是怎么实现的?
从IDC到云端架构迁移之路(GITC2016)

架构师 一致性问题

库存扣多了,到底怎么整
库存扣减还有这么多方案?
浅谈CAS在分布式ID生成方案上的应用
CAS下ABA问题及优化方案

架构师 一分钟系列

一张“神图”看懂单机/集群/热备/磁盘阵列(RAID)
一分钟学awk够用(产品经理都懂了)
十分钟学perl够用(客服MM都懂了)
一分钟sed入门(一分钟系列)
一分钟了解两阶段提交2PC(运营MM也懂了)
30秒懂SQL中的join(2幅图+30秒)
连接池原来这么简单(一分钟系列)
一分钟实现分布式锁
这才是真正的分布式锁
一分钟一幅图TCP/IP搞定
一分钟理解负载LoadAverage
1分钟了解Leader-Follower线程模型
1分钟了解四层/七层反向代理

架构师 通用素质

罗振宇送给新员工的四句话
职场中的选择与拒绝
心态:晋升的为什么不是你
你的收入取决于你的努力程度
“老公,我穿这衣服好看吗”终于破解了
一分钟经理人
如何精确理解leader布置的任务
如何快速精确的和leader沟通
架构师到底该不该写代码
运维说给研发测试的心底话
如何做一场B格满满的技术大会演讲

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }