今天和大家聊聊计数系统。
成都创新互联长期为上1000+客户提供的网站建设服务,团队从业经验10年,关注不同地域、不同群体,并针对不同对象提供差异化的产品和服务;打造开放共赢平台,与合作伙伴共同营造健康的互联网生态环境。为柘城企业提供专业的成都网站建设、网站建设,柘城网站改版等技术服务。拥有十多年丰富建站经验和众多成功案例,为您定制开发。
画外音:文章较长,可提前收藏。
很多业务都有“计数”需求,以微博为例:
微博首页的个人中心部分,有三个重要的计数:
微博首页的博文消息主体部分,也有有很多计数,分别是一条博文的:
画外音:浏览计数有点难,每秒100W次PV,就有100W次计数。
在业务复杂,计数扩展频繁,数据量大,并发量大的情况下,计数系统的架构演进与实践,是本文将要分享的问题。
count计数法。
典型的互联网架构,常常分为这么几层:
针对上文微博计数的例子,主要涉及“关注”业务,“粉丝”业务,“微博消息”业务,一般来说,会有相应的db存储相关数据,相应的service提供相关业务的RPC接口:
很容易想到,关注服务+粉丝服务+消息服务均提供相应接口,就能拿到相关计数数据。
例如,个人中心首页,需要展现博文数量这个计数,web层访问message-service的count接口,这个接口执行:
- select count(*) from t_msg where uid = XXX
同理,也很容易拿到关注,粉丝的这些计数。
这个方案叫做“count”计数法,在数据量并发量不大的情况下,最容易想到且最经常使用的就是这种方法。
随着数据量的上升,并发量的上升,这个方法的弊端将逐步展现。
例如,微博首页有很多条微博消息,每条消息有若干计数,此时计数的拉取就成了一个庞大的工程:
整个拉取计数的伪代码如下:
- list
= getHomePageMsg(uid);// 获取首页所有消息 - for( msg_id in list
){ // 对每一条消息 - getReadCount(msg_id); // 阅读计数
- getForwordCount(msg_id); // 转发计数
- getCommentCount(msg_id); // 评论计数
- getPraiseCount(msg_id); // 赞计数
- }
其中:
其效率之低,资源消耗之大,处理时间之长,可想而知。
“count”计数法方案,可以总结为:
计数外置法。
计数是一个通用的需求,有没有可能,这个计数的需求实现在一个通用的系统里,而不是由关注服务、粉丝服务、微博服务来分别来提供相应的功能呢?
画外音:各个业务系统具备的通用痛点,应该下沉统一解决。
可以抽象一个通用的计数服务。
通过分析,上述微博的业务可以抽象成两类:
于是可以抽象出两个表,针对这两个维度来进行计数的存储:
- 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
) { - 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发送一条异步消息,通知计数系统计数发生了变化即可:
如上图:
画外音:其实发送一条微博本来就会发MQ消息,计数系统只是新增一个订阅方。
这个方案称为“计数外置”,可以总结为:
计数外置,本质是数据的冗余,架构设计上,数据冗余必将引发数据的一致性问题,需要有机制来保证计数系统里的数据与业务系统里的数据一致,常见的方法有:
像关注计数,粉丝计数,微博消息计数,变化的频率很低,查询的频率很高,这类读多些少的业务场景,非常适合使用缓存来进行查询优化,减少数据库的查询次数,降低数据库的压力。
但是,缓存是kv结构的,无法像数据库一样,设置成
- t_uid_count(uid, c1, c2, c3)
缓存kv结构的value是计数,看来只能在key上做设计,很容易想到,可以使用uid:type来做key,存储对应type的计数。
对于uid=123的用户,其关注计数,粉丝计数,微博消息计数的缓存就可以设计为:
此时对应的counting-service架构变为:
如此这般,多个uid的多个计数,又可能会变为多次缓存的访问:
- for(uid in list
) { - memcache::get($uid:c1, $uid:c2, $uid:c3);
- }
这个“计数外置缓存优化”方案,可以总结为:
画外音:其实写多读少,一致性要求不高的计数,也可以先用缓存保存,然后定期刷到数据库中,以降低数据库的读写压力。
不要陷入思维定式,谁说value一定只能是一个计数,难道不能多个计数存储在一个value中么?
缓存kv结构的key是uid,value可以是多个计数同时存储。
对于uid=123的用户,其关注计数,粉丝计数,微博消息计数的缓存就可以设计为:
这样多个用户,多个计数的查询就可以一次搞定:
- memcache::get($uid1, $uid2, $uid3, …);
然后对获取的value进行分析,得到关注计数,粉丝计数,微博计数。
如果计数value能够事先预估一个范围,甚至可以用一个整数的不同bit来存储多个计数,用整数的与或非计算提高效率。
这个“计数外置缓存批量优化”方案,可以总结为:
考虑完效率,架构设计上还需要考虑扩展性,如果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)数据库扩展性优化,可以由列扩展优化为行扩展即:
优化为:
【本文为专栏作者“58沈剑”原创稿件,转载请联系原作者】
当前题目:每秒100W次的计数,架构原来可以这样设计!
网站网址:http://www.shufengxianlan.com/qtweb/news40/110990.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联