> 原文出处:http://weibo.com/p/1001643880172431480781
> 作者:唐扬,[@唐扬TY](http://weibo.com/n/%E5%94%90%E6%89%ACTY)
![](https://box.kancloud.cn/2015-09-14_55f6685f4e461.jpg)
未读提醒功能在各种社交平台服务中较为常见,在微博中这些功能由Unread服务来提供。看似简单的功能,当请求量级达到一定规模后,成本、性能、稳定性的平衡将是架构设计的重点。
**大纲**
[TOC]
## 一 微博中Unread服务业务场景
在以timeline为核心的微博业务中, 未读数场景出现的频率较高,它可以是这样的…
![](https://box.kancloud.cn/2015-09-14_55f6686235481.jpg)
也可以是这样的…
![](https://box.kancloud.cn/2015-09-14_55f66862b2ad7.jpg)
## 二 从架构角度对各种业务场景的抽象
通过分析和比较这些未读场景,我们抽象了unread服务中设计到的三种主要操作:
1. incr:增加未读数
2. reset:未读数清零
3. get:获取未读数
我们发现unread服务中get操作是典型的无触发操作,即不需要用户执行任何操作都会对服务器造成请求。正是这个特点给unread服务带来如下问题:
1. 高并发:高峰期单一业务的qps达到10万+
2. 性能要求高:接口4个9的响应时间在10ms
为了解决上述问题,unread架构针对不同的业务场景设计了不同的方案,保证了服务的高性能、高可用和可扩展。本文主要针对三种典型的未读数场景介绍微博平台是如何设计解决方案的。
### 场景一. 一对一行为未读提醒场景
在这种场景下,用户的某一个操作只会影响一个用户的未读数字。典型的场景有:@未读提醒、评论未读提醒、赞评论提醒等等。
针对这种场景,我们采用最简单的解决方案:为每一个用户存储一份未读数字,如下图
![](https://box.kancloud.cn/2015-09-14_55f668633120e.jpg)
在设计的实现中,由于存储容量可控,我们采用redis存储未读数。相比于通常使用的mysql+mc的存储解决方案,redis有以下的优势:
1. 存储一体化,避免了缓存和持久化存储之间一致性的问题
2. 快速恢复
这个设计方案主要基于如下的考虑:
1. 简单直观
2. 性能能够达到SLA,每次操作只需要访问一次资源
### 场景二. 全量打点场景
打点主要指微博官方客户端中的一些弱提醒功能,见下图中的红点
![](https://box.kancloud.cn/2015-09-14_55f66863b3125.jpg)
而全量打点指对全量用户都增加未读提醒红点。考虑到目前微博的用户量,如果采用第一种场景的方案,打点过程会存在很大的延迟。因此我们采用了基于tag的解决方案。
1. 存储公共的时间戳global
2. 每一个用户存储一个时间戳
3. 打点时,更新global时间戳为当前时间
4. 消点时,更新用户的时间戳为global时间戳
5. 如果用户时间戳小于global时间戳,则有点;否则没有点
![](https://box.kancloud.cn/2015-09-14_55f66863d2132.jpg)
这个方案适用于用户间存在共享存储,且共享存储有限的场景。在这种场景下,我们为每一个用户存储一个tag用来记录用户在共享存储中的已读位置,这样就可以通过比较这个已读位置获得用户的未读数。
在实际的应用过程中,我们通常会使用本地缓存来解决访问共享存储的极端峰值。这种基于已读位置的解决方案虽然能很好的解决全量打点的问题,但是面对访问量最大的微博未读数场景却是无能为力,原因有二:
1. 用户的feed是无限的,不存在共享存储,全部存储下来的成本很高
2. 在高并发下获取未读数操作性能衰减严重
我们采用了下面这种方案来解决微博未读数问题。
### 场景三. 微博未读数场景
众所周知,微博未读数就是微博主feed未读数,当我关注的人发表一条微博,我的微博未读数提醒就会加一。
对于微博微博数场景,我们采用了基于snapshot的解决方案,具体如图所示:
![](https://box.kancloud.cn/2015-09-14_55f6686440c28.jpg)
1. 我们会存储每一个用户发微博数,用户发微博时只会增加自身的微博数
2. 用户reset时会触发一次快照操作,存储当时用户的关注关系以及每一个关注者的微博数。
3. 用户的未读数由当前用户所有关注人的微博数和快照中所有关注人的微博数的差值组成
基于快照的方案近乎完美的解决了微博未读数的问题,目前微博平台单机qps接近5万,平均响应时间小于10ms
在基于快照的方案中,我们采用空间换时间的方式,额外存储了一份snapshot来换取性能上达到SLA要求。因此这个方案主要的适用场景是快照的存储有限或者是允许丢失。
在微博未读数实现中,我们采用memcache存储快照,使用本地内存存储用户的微博数,采用这种方式是基于以下考虑:
1. 存储用户微博数的空间有限,使用本地内存可以减少一次网络开销
2. 快照中需要存储关系,理论上需要N*N的空间消耗(N为用户数),利用memcache的LRU机制可以淘汰不活跃用户的快照,节省空间开销
memcache的容量评估也是这个方案实现中需要考虑的重要点。我们主要从两个方面来考虑:
1. 容量:根据存储的单个用户快照大小和MAU评估
2\. QPS:依据前端QPS和每次调用带来的对memcache的调用次数估算出memcache的QPS,以此QPS估算需要多少个memcache实例
以上就是我对于微博平台不同的未读场景设计的未读架构的理解。在设计的过程中,我们没有拷贝相似场景的方案,而是权衡需求、性能、成本和扩展性等多方面的因素,设计出最合适的架构。希望通过以上介绍能给大家以后工作有所帮助和启发。
**------------------新兵训练营简介**------------------
微博平台新兵训练营活动是微博平台内部组织的针对新入职同学的团队融入培训课程,目标是团队融入,包括人的融入,氛围融入,技术融入。当前已经进行4期活动,很多学员迅速成长为平台技术骨干。
微博平台是非常注重团队成员融入与成长的团队,在这里有人帮你融入,有人和你一起成长,也欢迎小伙伴们加入微博平台,欢迎私信咨询。
**------------------讲师简介**------------------
唐扬,[@唐扬TY](http://weibo.com/n/%E5%94%90%E6%89%ACTY),微博平台及大数据部技术专家,负责微博平台架构及基础架构研发工作,在高并发可扩展架构设计方面有丰富的实战经验。新兵训练营第一期学员。