当前位置: 首页 > 科技观察

每秒计数100W次,架构可以这样设计!

时间:2023-03-20 11:48:21 科技观察

今天跟大家聊聊计数系统。画外音:文章较长,可以提前收藏。计数系统解决什么业务问题?许多企业都有“计数”的需求。以微博为例:微博首页的个人中心部分有3个重要的统计:关注人数统计;粉丝数量;博文数量Count;微博首页的博文消息主体部分也有多项统计,分别是一篇博文:转发计数;评论数;喜欢计数;甚至观看次数;有100W计数。在业务复杂、计数频繁扩容、数据量大、并发量大的情况下,计数系统的架构演进与实践是本文将要分享的问题。如何最快实现业务盘点?计数计数法。一个典型的互联网架构往往分为几层:调用层:浏览器或端APP;site层:html或json组装返回的web-server层;服务层:提供RPC调用接口的服务层;数据层:提供db用于固化数据存储,提供cache用于加速存储;上面微博统计的例子,主要涉及“关注”业务、“粉丝”业务、“微博新闻”业务。一般来说,会有相应的db存储相关的数据,相应的service为相关业务提供RPC接口:Followservice:提供RPC接口,用于对相关数据进行增删改查;粉丝服务:提供粉丝数据增删改查的RPC接口;消息服务:提供微博消息数据增删改查RPC接口。消息业务相对复杂,涉及微博消息、转发、评论、点赞等数据的存储;初步分析了follower、follower、微博业务,首页的统计需求应该如何满足??很容易想到下面的服务+粉丝服务+消息服务都提供了相应的接口,可以获取相关的统计数据。比如个人中心的首页需要显示博文条数。web层访问message-service的count接口,该接口执行:selectcount(*)fromt_msgwhereuid=XXX。同样,很容易得到关注度和粉丝数。这种方案叫做“count”计数法,这是最容易想到的,也是并发数据量不大的时候最常使用的方法。计数计数法有什么问题?随着数据量的增加,并发量的增加,这种方式的弊端就会逐渐显现出来。比如微博首页有很多条微博消息,每条消息有几个计数。这时候获取counts就成了一个浩大的工程:整个获取count的伪代码如下:list=getHomePageMsg(uid);//获取首页所有的消息for(msg_idinlist){//对于每条消息getReadCount(msg_id);//阅读计数getForwordCount(msg_id);//转发计数getCommentCount(msg_id);//评论计数getPraiseCount(msg_id);//点赞计数}其中:每条的若干计数微博消息对应4次后台服务访问;每次访问对应count次的数据库访问(count已死);其他可想而知,效率低,资源消耗大,处理时间长。“count”计数方法方案可以归纳为:多次查询多条消息,进行for循环;多次查询一条消息,查询多个计数;一次查询一个计数,每个计数是一个计数语句;那么如何优化呢?计数外部方法。计数是一般要求。是否可以在通用系统中实现这种统计需求,而不是通过关注服务、粉丝服务、微博服务提供相应的功能?画外音:每个业务系统都有共同的痛点,要统一解决。可以抽象出通用计数服务。通过分析,上述微博的业务可以抽象为两类:用户(uid)维度的统计:用户的关注数、关注数、微博发布数;微博消息(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,…)根据类型判断id是uid还是msg_id,但不建议这样做。将存储抽象出来后,再抽象出一个统计服务来管理这些数据,并提供友好的RPC接口:这样查询一条微博的多个统计时,就不需要进行多次数据库统计操作,而是进行转化intoone数据多个属性的查询:for(msg_idinlist){selectforword_count,comment_count,praise_countfromt_msg_countwheremsg_id=$msg_id;}甚至微博首页所有消息的计数可以转换成IN语句(不用查询多次)批量查询:select*fromt_msg_countwheremsg_idIN($msg_id1,$msg_id2,$msg_id3,...);IN查询可以命中msg_id聚簇索引,效率很高。那么,当一条微博被转发、评论、点赞时,计数服务是如何同步改变计数的呢?如果使用业务服务来调用计数服务,势必会导致业务系统与计数系统的耦合。可以通过MQ解耦。当业务发生变化时,向MQ发送异步消息,通知计数系统计数发生变化:如上图:用户发布了一条新微博;msg-service向MQ发送消息;counting-service接收来自MQ的消息;counting-service修改uid发布微博消息数;画外音:其实发微博会发MQ消息,统计系统只是增加了一个订阅者。这种方案称为“countingexternal”,可以概括为:通过counting-service单独保存counting;MQ同步计数变化;多条消息的多次统计,一批IN查询完成;外部计数可能存在哪些新问题呢?外部计数本质上是数据冗余。在架构设计上,数据冗余必然导致数据一致性问题。需要一种机制来保证统计系统中的数据与业务系统中的数据保持一致。常见的方法包括:对于一致性要求比较高的业务,要有定期的检查和修复机制,比如关注量、粉丝量、微博消息量等;对于一致性要求比较低的业务,即使出现数据不一致,业务也可以Accept,比如微博浏览量,微博转发量等;外部计数在很大程度上解决了计数和访问的性能问题,但是还有优化的空间吗?像关注数,粉丝数,微博消息数,变化频率很低,查询频率很高。这种读多读少的业务场景,非常适合使用缓存来优化查询,减少数据库查询次数,减轻数据库压力。但是缓存是kv结构的,不能像数据库那样设置成t_uid_count(uid,c1,c2,c3)这样的schema。如何设计kv?cachekv结构体的值是一个count,而且好像只能用在Key上Designing,很容易想到可以用uid:type作为key来存储对应类型的count。对于uid=123的用户,followcount、followercount、微博消息数的缓存可以设计如下:此时对应的counting-service架构变为:某某,多个uid的多个count,并且可能就会变成多次缓存访问:for(uidinlist){memcache::get($uid:c1,$uid:c2,$uid:c3);}这种“计数外部缓存优化”的方案可以概括为:使用缓存保存读多写少的计数;画外音:其实对于写多读少、一致性要求不高的counts,也可以先保存在缓存中,然后周期性的flush到数据库中,减少数据库的读写压力。使用id:type作为缓存的key,使用count作为缓存的value;多次读取缓存,查询多个uid的计数;使用缓存可以大大减轻数据库的压力,但是多缓存交互还是存在优化空间,请问有什么办法进一步优化吗?不要陷入刻板思维,谁说value只能是一个count,一个value不能存多个count?cachekv结构的key是uid,value可以是多个Counts同时存储。对于uid=123的用户,followcount,followercount,微博消息数的缓存可以设计如下:这样可以一次查询多个用户,多个count:memcache::get($uid1,$uid2,$uid3,...);然后分析得到的值,得到followcount,followercount,Weibocount。如果计数值可以提前预知一个范围,你甚至可以用一个整数的不同位来存储多个计数,使用整数AND或NOT计算来提高效率。这种“countingexternalcachebatchoptimization”方案可以总结为:以id为key,使用多个相同id的counts拼接为value;可以一次完成多个具有多个ID的计数查询;在考虑效率之后,架构设计还需要考虑可扩展性。如果uid除了followcount,followercount,微博数还需要增加一个count,需要对系统做哪些改动?之前的数据库结构是:t_user_count(uid,gz_count,fs_count,wb_count)这种设计使用列来存储计数。如果增加了一个XX计数,需要将数据库的表结构改为:t_user_count(uid,gz_count,fs_count,wb_count,XX_count)在数据量很大的情况下,显然不可取的改变结构数据库架构。有没有更好的扩展方式?不要陷入思维定势,谁说属性只能通过扩展列来扩展,是否可以通过扩展行来扩展?答案是肯定的,表结构可以这样设计:t_user_count(uid,count_key,count_value)如果需要增加新的计数XX_count,只需要增加一行,不改变表结构:总结小计数,在数据量大、并发量大的情况下,架构实践思路是:(1)外部计数:从“计数计数法”升级为“计数外部法”;(2)读多写少,甚至写多但一致性要求低的计数,需要缓存优化,降低数据库压力;(3)cachekv设计优化可以从[key:type]->[count]优化到[key]->[c1:c2:c3]即:优化为:(4)数据库扩展性优化,可以是由列扩优化为行扩,即:优化为:【本文为专栏作者《58神剑》原创稿件,转载请联系原作者】点此查看作者更多好文