Skip to content

dev msg_cache

wujun4code edited this page Nov 22, 2016 · 4 revisions

比微信更深一步 - DoChat 聊天记录缓存策略

标签(空格分隔): 开发日志


广大的微信用户都应该知道:微信截止2016年11月的版本,全平台所有客户端(iOS,Android,Mac OS,Windows)的聊天记录只会从本地缓存读取,大家应该有过换手机的体验,新手机上登录微信,它第一次也仅仅是发送你未读的消息给你,不论你怎么拉取,它都不会从服务端帮你加载你的聊天记录。这个体验总的来说并不是很好,多年以前 QQ 会员才会帮你云端存储聊天记录,我有一个大学同学,为了自己的保存自己多年征战情场的撩妹证据,硬生生一直充 QQ 会员到今天。

那么我在实现 DoChat 的聊天记录获取的时候,就想一定要做到先从本地缓存读取,本地读取完了,就立即去服务端获取,当然这得需要服务端有 API 帮我存着记录并且要易于获取(:这句话的意思就是 API 得好用)。

插一句话,因为 DoChat 的起初的创意是想借用微信的 UI,加上 LeanCloud 的聊天系统做成一个开源开放的 Demo 给 LeanCloud 用户做参考,现在发现我可以在这个项目里面实现一些微信不关心的细节。

初级需求:首次加载 20 条聊天记录

首先,我们的需求是 「Bill」打开与「代码胖」的对话,需要在页面上显示 20 条聊天记录

我用图来介绍一下,几种情况的逻辑时序图:

1.本地一条都没有

2.本地有足够数量的缓存,提供首次加载

会不会出现本地有记录,但是不够 20 条?

上述逻辑说的很清楚,除非修改首次加载的页面大小,比如忽然换成了首次加载 30 条,就有可能出现。但是这个逻辑后面会有详述。

进阶需求:用户下拉列表,要求刷新更多的聊天记录

1.本地数量有限,不够多次加载是用

用户翻页到第三页:

为什么会出现这种情况?

因为当前用户在当前客户端发送过消息,因为我们在发消息和接收消息的之后,也是会缓存到本地的,比如该用户首次登录之后,做了如下的事情,本地就可能出现 48 条缓存:

  1. 首次加载,从服务端拉取了 20 条存在了本地(本地现有 20 条)
  2. TA 又向上翻了一页,又从服务端拉取了 20 条 又存在了本地(本地现有 40 条)
  3. 然后他发送了4条消息,对方回复了4条消息,这都被缓存下来了。(本地现有 48 条)
  4. TA 下线了,关机了,睡觉了,总之第二天再打开 App,就会出现这种情况了。

逻辑漏洞:用户离线之后,对方发来消息,然后再打开应用就尴尬了……

为什么会尴尬? 因为之前的逻辑是,一切都遵照本地的 Sqlite 为准,都是以它的最近一条为基准往前回溯,那么如果 当前 Sqlite 里面最新的一条并不是实际意义上的「最新」的一条,就会造成「断片」的效果,如下图:

第一天 Bill 下线之前,本地同步了服务端全部的消息,总共 48 条:

sqlite:messages[0,...,47]
//其中最早的一条是 messages[47].timestamp 为 '2016-11-26 10:00:00';
//而最新的一条是  messages[0].timestamp 为 '2016-11-26 21:00:00';

然后 Bill 睡觉去了。。。然后在第二天上午 10点 Bill 起床开机之前,对方又给他发了 200 条消息: 第二天 早上 10点, Bill 开机,开始从本地 sqlite 最新的一条 也就是昨晚最新的一条:2016-11-26 21:00:00 往前回溯,然后目前这段逻辑,只能找回 48 条,后来对方发的 200 条就都没了。(注意,这里还没开始阐述离线推送的,只针对当前逻辑)。

那怎么办?

解决方案1:离线推送

跟微信一样,你有几条未读,我就都通过推送服务推给你,这样保证消息一定会被用户读取,这种解决方案针对微信目前的单点登录的行为已经满足需求。

单点登录:微信的手机端是互斥的,微信的 Web版与 Windows 以及 Mac OS 版也是互斥的。

换言之,微信目前发现用户在一台手机上和一个桌面版( Web版等同于桌面版)上登录的情况下,会同步两端的消息,但是不会在桌面版保存聊天记录,更不会针对桌面版做离线推送。它的核心还是在手机端。换言之,微信认为用户的手机不是会经常更换的,以手机上的离线推送+本地缓存简单的策略保证消息的抵达。

但是上面的这种偷懒的方式完全因为微信是一个纯面向移动互联的产品,它的目标并不是取代 QQ。

用离线推送,就可以解决离线收消息的问题,但是这里要实现的是一个面向多点登录设备同步消息的功能,因此我们需要在此之上继续深挖。

解决方案2:主动拉取,合并本地

这个设计思路很简单粗暴:首次进入聊天页面,一律从服务端拉取,最新的 20 条,然后在后台启动线程,同步本地,如下图:

根据上述逻辑,用户翻页的时候情况如下:

终极解决方案:推拉结合

首先,我们需要针对移动设备做离线消息推送。保证与微信一样的离线体验,打开 App 之后,直接从服务端拉去最新的 20 条数据。然后以这 20 条数据当中最早的消息为 flag,向前回溯,逐步拉回本地存入 sqlite,直到某条消息的 id 在本地 sqlite 已经存在。