每秒100W次的计数,架构原来可以这样设计

58沈剑 架构师之路

今天和大家聊聊计数系统。

画外音:文章较长,可提前收藏。

计数系统解决什么业务问题?

很多业务都有“计数”需求,以微博为例:

微博首页的个人中心部分,有三个重要的计数:

(1)关注了多少人的计数;

(2)粉丝的计数;

(3)发布博文的计数;

微博首页的博文消息主体部分,也有有很多计数,分别是一条博文的:

(1)转发计数;

(2)评论计数;

(3)点赞计数;

(4)甚至是浏览计数;

画外音:浏览计数有点难,每秒100W次PV,就有100W次计数

在业务复杂,计数扩展频繁,数据量大,并发量大的情况下,计数系统的架构演进与实践,是本文将要分享的问题。

如何最快速的实现业务计数呢?

count计数法。

典型的互联网架构,常常分为这么几层:

(1)调用层:处于端上的browser或者APP;

(2)站点层:拼装html或者json返回的web-server层;

(3)服务层:提供RPC调用接口的service层;

(4)数据层:提供固化数据存储的db,以及加速存储的cache;

针对上文微博计数的例子,主要涉及“关注”业务,“粉丝”业务,“微博消息”业务,一般来说,会有相应的db存储相关数据,相应的service提供相关业务的RPC接口:

(1)关注服务:提供关注数据的增删查改RPC接口;

(2)粉丝服务:提供粉丝数据的增删查改RPC接口;

(3)消息服务:提供微博消息数据的增删查改RPC接口,消息业务相对比较复杂,涉及微博消息、转发、评论、点赞等数据的存储;

对关注、粉丝、微博业务进行了初步解析,那首页的计数需求应该如何满足呢?

很容易想到,关注服务 粉丝服务 消息服务均提供相应接口,就能拿到相关计数数据。

例如,个人中心首页,需要展现博文数量这个计数,web层访问message-service的count接口,这个接口执行:

select count(*) from t_msg where uid = XXX

同理,也很容易拿到关注,粉丝的这些计数。

这个方案叫做“count”计数法,在数据量并发量不大的情况下,最容易想到且最经常使用的就是这种方法。

count计数法存在什么问题呢?

随着数据量的上升,并发量的上升,这个方法的弊端将逐步展现。

例如,微博首页有很多条微博消息,每条消息有若干计数,此时计数的拉取就成了一个庞大的工程:

整个拉取计数的伪代码如下:

list<msg_id> = getHomePageMsg(uid);// 获取首页所有消息

for( msg_id in list<msg_id>){ // 对每一条消息

getReadCount(msg_id); // 阅读计数

getForwordCount(msg_id); // 转发计数

getCommentCount(msg_id); // 评论计数

getPraiseCount(msg_id); // 赞计数

}

其中:

(1)每一个微博消息的若干个计数,都对应4个后端服务访问;

(2)每一个访问,对应一条count的数据库访问(count要了老命了);

其效率之低,资源消耗之大,处理时间之长,可想而知。

“count”计数法方案,可以总结为:

(1)多条消息多次查询,for循环进行;

(2)一条消息多次查询,多个计数的查询;

(3)一次查询一个count,每个计数都是一个count语句;

那如何进行优化呢?

计数外置法。

计数是一个通用的需求,有没有可能,这个计数的需求实现在一个通用的系统里,而不是由关注服务、粉丝服务、微博服务来分别来提供相应的功能呢?

画外音:各个业务系统具备的通用痛点,应该下沉统一解决。

可以抽象一个通用的计数服务。

通过分析,上述微博的业务可以抽象成两类:

(1)用户(uid)维度的计数:用户的关注计数,粉丝计数,发布的微博计数;

(2)微博消息(msg_id)维度的计数:消息转发计数,评论计数,点赞计数;

于是可以抽象出两个表,针对这两个维度来进行计数的存储:

t_user_count (uid, gz_count, fs_count, wb_count);

t_msg_count (msg_id, forword_count, comment_count, praise_count);

甚至可以更为抽象,一个表搞定所有计数:

t_count(id, type, c1, c2, c3, …)

通过type来判断,id究竟是uid还是msg_id,但并不建议这么做。

存储抽象完,再抽象出一个计数服务对这些数据进行管理,提供友善的RPC接口:

这样,在查询一条微博消息的若干个计数的时候,不用进行多次数据库count操作,而会转变为一条数据的多个属性的查询:

for(msg_id in list<msg_id>) {

select forword_count, comment_count, praise_count

from t_msg_count

where msg_id=$msg_id;

}

甚至,可以将微博首页所有消息的计数,转变为一条IN语句(不用多次查询了)的批量查询:

select * from t_msg_count

where msg_id IN

($msg_id1, $msg_id2, $msg_id3, …);

IN查询可以命中msg_id聚集索引,效率很高。

那么,当有微博被转发、评论、点赞的时候,计数服务如何同步的进行计数的变更呢?

如果让业务服务来调用计数服务,势必会导致业务系统与计数系统耦合。可以通过MQ来解耦,在业务发生变化的时候,向MQ发送一条异步消息,通知计数系统计数发生了变化即可:

如上图:

(1)用户新发布了一条微博;

(2)msg-service向MQ发送一条消息;

(3)counting-service从MQ接收消息;

(4)counting-service变更这个uid发布微博消息计数;

画外音:其实发送一条微博本来就会发MQ消息,计数系统只是新增一个订阅方。

这个方案称为“计数外置”,可以总结为:

(1)通过counting-service单独保存计数;

(2)MQ同步计数的变更;

(3)多条消息的多个计数,一个批量IN查询完成;

计数外置可能存在什么新的问题呢?

计数外置本质是数据的冗余,架构设计上,数据冗余必将引发数据的一致性问题,需要有机制来保证计数系统里的数据与业务系统里的数据一致,常见的方法有:

(1)对于一致性要求比较高的业务,要有定期check并fix的机制,例如关注计数,粉丝计数,微博消息计数等;

(2)对于一致性要求比较低的业务,即使有数据不一致,业务可以接受,例如微博浏览数,微博转发数等;

计数外置很大程度上解决了计数存取的性能问题,但是否还有优化空间呢?

像关注计数,粉丝计数,微博消息计数,变化的频率很低,查询的频率很高,这类读多些少的业务场景,非常适合使用缓存来进行查询优化,减少数据库的查询次数,降低数据库的压力。

但是,缓存是kv结构的,无法像数据库一样,设置成

t_uid_count(uid, c1, c2, c3)

这样的schema,如何来对kv进行设计呢?

缓存kv结构的value是计数,看来只能在key上做设计,很容易想到,可以使用uid:type来做key,存储对应type的计数。

对于uid=123的用户,其关注计数,粉丝计数,微博消息计数的缓存就可以设计为:

此时对应的counting-service架构变为:

如此这般,多个uid的多个计数,又可能会变为多次缓存的访问:

for(uid in list<uid>) {

memcache::get($uid:c1, $uid:c2, $uid:c3);

}

这个“计数外置缓存优化”方案,可以总结为:

(1)使用缓存来保存读多写少的计数;

画外音:其实写多读少,一致性要求不高的计数,也可以先用缓存保存,然后定期刷到数据库中,以降低数据库的读写压力。

(2)使用id:type的方式作为缓存的key,使用count来作为缓存的value;

(3)多次读取缓存来查询多个uid的计数;

缓存的使用能够极大降低数据库的压力,但多次缓存交互依旧存在优化空间,有没有办法进一步优化呢?

不要陷入思维定式,谁说value一定只能是一个计数,难道不能多个计数存储在一个value中么?

缓存kv结构的key是uid,value可以是多个计数同时存储。

对于uid=123的用户,其关注计数,粉丝计数,微博消息计数的缓存就可以设计为:

这样多个用户,多个计数的查询就可以一次搞定:

memcache::get($uid1, $uid2, $uid3, …);

然后对获取的value进行分析,得到关注计数,粉丝计数,微博计数。

如果计数value能够事先预估一个范围,甚至可以用一个整数的不同bit来存储多个计数,用整数的与或非计算提高效率。

这个“计数外置缓存批量优化”方案,可以总结为:

(1)使用id作为key,使用同一个id的多个计数的拼接作为value;

(2)多个id的多个计数查询,一次搞定;

考虑完效率,架构设计上还需要考虑扩展性,如果uid除了关注计数,粉丝计数,微博计数,还要增加一个计数,这时系统需要做什么变更呢?

之前的数据库结构是:

t_user_count(uid, gz_count, fs_count, wb_count)

这种设计,通过列来进行计数的存储,如果增加一个XX计数,数据库的表结构要变更为:

t_user_count(uid, gz_count, fs_count, wb_count, XX_count)

在数据量很大的情况下,频繁的变更数据库schema的结构显然是不可取的,有没有扩展性更好的方式呢?

不要陷入思维定式,谁说只能通过扩展列来扩展属性,能不能通过扩展行来扩展属性呢?

答案是肯定的,完全可以这样设计表结构:

t_user_count(uid, count_key, count_value)

如果需要新增一个计数XX_count,只需要增加一行即可,而不需要变更表结构:

总结

小小的计数,在数据量大,并发量大的时候,其架构实践思路为:

(1)计数外置:由“count计数法”升级为“计数外置法”;

(2)读多写少,甚至写多但一致性要求不高的计数,需要进行缓存优化,降低数据库压力;

(3)缓存kv设计优化,可以由[key:type]->[count],优化为[key]->[c1:c2:c3]

即:

优化为:

(4)数据库扩展性优化,可以由列扩展优化为行扩展

即:

优化为:

===【全文完】===

架构师之路 架构师之路,坚持撰写接地气的架构文章591篇原创内容

公众号

(0)

相关推荐

  • 架构杂谈《五》

    保证最终一致性的模式 在大规模.高并发服务化系统中,一个功能被拆分成多个具有功能单一的子功能,一个流程会有多个系统的多个单一功能的服务组合实现,如果使用两阶段提交协议和三阶段提交协议,确实能解决系统间 ...

  • 58 | 性能设计篇之“缓存”

    前面分享了<分布式系统设计模式>系列文章的前两部分--弹力设计篇和管理设计篇.今天开始这一系列的最后一部分内容--性能设计篇,主题为<性能设计篇之"缓存"> ...

  • 面试官:缓存一致性问题怎么解决?

    关于Redis的其他的一些面试问题已经写过了,比如常见的缓存穿透.雪崩.击穿.热点的问题,但是还有一个比较麻烦的问题就是如何保证缓存一致性. 对于缓存和数据库的操作,主要有以下两种方式. 先删缓存,再 ...

  • Java分布式面试题集合

    (给ImportNew加星标,提高Java技能) 转自:乐知者, 链接:cnblogs.com/expiator/p/10201004.html 分布式分为分布式缓存(Redis).分布式锁(Redi ...

  • 每秒100W请求,携程如何支撑十一假期,抢票系统的?

    快来看看这三道大厂面试题,你知道现在的面试有多难吗??? 01 来自于阿里: 「请寻求最优解,不要只是粗暴wait()」 有一个总任务A,分解为子任务A1 A2 A3 ...,任何一个子任务失败后要快 ...

  • 腾讯音乐:全民K歌推荐系统架构及粗排设计

    编辑整理:张振.于洋 出品平台:DataFunTalk 导读:腾讯音乐娱乐集团( TME) 目前有四大移动音乐产品:QQ音乐.酷狗音乐.酷我音乐和全民K歌,总月活超8亿.其中,全民K歌与其他三款产品有 ...

  • 立体架构与空间的设计

    巧妙运用空间架构无疑是每个优秀花艺师的首选.既能增添区域本身的空间感,又能增添作品的视觉感.空间立体结构有3个必备的条件: 1.点:在一定区域范围内,由视觉效果孙凝聚到的部分,称为中心点. 2.线:在 ...

  • 独家 | 0.2秒设计力提出人:解读日本食品包装设计的8大关键词和3大趋势

    作者:Momo(Wenky) 编辑:Bobo 你知道设计界的0.2秒法则吗? 日本设计专家笹田史仁曾在他的著作<0.2秒的设计力>中提到,其实顾客在走进商店前,很少先想好买哪个品牌的产品. ...

  • 电子电器架构的发展及设计开发

    自1886年1月29日,卡尔.本茨的三轮汽车奔驰1号成功注册专利到现在,汽车发展已经有了百年的历史. 当平顺的变速箱加调校和已经无可挑剔的电机传动出现,平顺相应和线性加速已经不值得去追求了:当更加平顺 ...

  • 数据仓库架构以及数据模型的设计

    数据仓库架构以及数据模型的设计

  • 1秒也不想停下音乐的人,设计出了这个玩意儿

    文/大智 在外戴耳机,回家想功放. 拔出耳机线,放大手机音量,或者蓝牙连接无线音响. 一般我们都默认这么操作,不过一位名叫 Deepanjan Sinha 的设计师却觉得这样相当折腾. 他设计了一款名 ...

  • 如何在微服务架构下进行数据设计?

    作者:唐建法 && Mongoing中文社区 来自:http://www.mongoing.com/ 微服务是一个软件架构模式,对微服务的讨论大多集中在容器或其他技术是否能很好的实施微 ...

  • 华为mate20秒变“浴霸”,三摄像头究竟该如何设计?

    不过很快,外媒又曝光了另外一种方案,采用的是上竖排三摄像头设计,不过由于并列了后置指纹识别模块,看起来非常的"长",同样也让人觉得不是十分的舒适.无论是"浴霸" ...