1。需求的由来很多企业都有一个“计数”的需求。以微博为例:微博首页的个人中心部分有3个重要的统计:关注人数统计粉丝数统计发表博文数主页博文留言主体部分微博也有很多统计,就是一篇博文:转发数,评论数,点赞数,甚至浏览数。在大的情况下,计数系统的架构演进和实践是本文将要讨论的问题。2.业务分析统计的初步实现典型的互联网架构往往分为几层:调用层:浏览器或端APP站点层:由返回的html或json组装而成的web-server层服务层:提供RPC调用接口service层数据层:提供db用于固化数据存储,cache用于加速存储。以《起源》微博统计为例,主要涉及“关注”业务、“粉丝”业务、“微博新闻”业务。即会有一个对应的db存放相关数据,对应的service为相关业务提供RPC接口:followservice:提供RPC接口,用于对相关数据进行增删改查Fanservice:提供RPC接口消息服务粉丝数据增删改查:提供微信增删查改博客消息数据的RPC接口。消息业务相对复杂,涉及微博消息的存储、转发、评论、点赞等数据。初步分析了粉丝、粉丝、微博业务。首页的统计要求应该如何满足?很容易想到关注服务+粉丝服务+消息服务都提供了相应的接口,可以获取相关的统计数据。比如个人中心的首页需要显示博文条数。web层访问message-service的count接口,该接口执行:selectcount(*)fromt_msgwhereuid=XXX。同样,很容易得到关注度和粉丝数。这种方案称为“计数”计数法。这种方法最容易想到,在数据量不大的时候用的最多。但是随着数据量的增加,并发量的增加,这种方式的弊端就会逐渐显现出来。比如微博首页有很多条微博消息,每条消息有几个计数。这时候获取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循环多次查询一条消息,多次count查询一次查询一个计数。每个count都是一个count语句,那怎么优化呢??3。外部计数架构设计计数是一种通用要求。有没有可能这个计数需求不是通过关注服务、粉丝服务、微博服务来提供相应的服务,而是在通用的系统中实现?功能呢(否则可扩展性极差)?这样就需要实现一个通用的计数服务。通过分析,上述微博的业务可以抽象为两类:Countingofuser(uid)维度:用户的关注数、粉丝数、发表微博数countingofmicroblogmessage(msg_id)维度:消息转发数、评论数、likecounting可以抽象出两个表,存储这两个维度的计数: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?》)。当业务发生变化时,向MQ发送异步消息,通知计数系统计数发生变化。没错:如上图:用户发布了一条新的微博。msg-service向MQ发送消息。计数服务从MQ接收消息。counting-service修改uid,发布微博消息计数。总结如下:counting-service用于保存计数MQ同步计数和改变多条消息的多次计数,一个batchIN查询对外完成计数。本质是数据冗余。在架构设计上,数据冗余必然导致数据一致性问题,需要一种机制来保证计数系统中的数据与业务系统中的数据一致。常见的方法包括:对于一致性要求高的业务,必须要有定期的检查和修复机制,比如关注计数、关注计数、微博消息计数等对一致性要求比较低的业务,即使出现数据不一致,业务是可以接受的,比如微博浏览量,微博转发量等。4.统计外部缓存优化统计量非常大一定程度上解决了统计访问量的性能问题,但是还有没有优化空间?像关注人数统计、粉丝统计、微博消息统计,变化频率很低,查询频率很高。这种业务读多读少的场景,非常适合使用缓存进行查询优化,减少数据库查询次数,减轻数据库压力。但是缓存是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(其实写多读少、一致性要求不高的counts也可以先保存在cache中,然后周期性的flush到数据库中,减少读和读)对数据库的写压力)使用id:type作为缓存的key,使用count作为缓存的value,多次读取缓存,查询多个uid的count。多缓存交互仍有优化空间。有什么办法可以进一步优化吗?当当当!不要陷入这样的思维模式,谁说value只能是一个count,一个value不能存多个count?Cachekv结构key为uid,value可以同时存放多个count。对于uid=123的用户,followcount,followercount,微博消息数的缓存可以设计如下:这样可以一次查询多个用户,多个count:memcache::get($uid1,$uid2,$uid3,...);然后分析得到的值,得到followcount,followercount,Weibocount。如果计数值可以提前预知一个范围,你甚至可以用一个整数的不同位来存储多个计数,使用整数AND或NOT计算来提高效率。这种“countingexternalcachebatchoptimization”方案可以归纳为:以id为key,使用相同id的多个counts的串接作为value一次可以进行多个ids的多个counting查询六、counting的可扩展性优化考虑为了提高效率,在架构设计中需要考虑可扩展性。如果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)在数据量大的情况下,频繁的改变结构显然是不可取的的数据库架构。有没有更好的扩展方式?当当当!别陷入那种思维定势,谁说只能通过扩展列来扩展属性,通过扩展行来扩展属性,这不是《架构师之路》系列文章的第一篇(请参考《啥,又要为表增加一列属性?》《这才是真正的表扩展方案》《100亿数据1万属性数据架构设计》详解),可以这样设计表结构:t_user_count(uid,count_key,count_value)如果需要增加一个新的要统计XX_count,只需要增加一行,不改变表结构:7.总结小数。当数据量大,并发量大时,架构实践思路如下:外部计数:通过“计数法”升级为“计数外部法”。对于读数更多的计数而写少,甚至写多但一致性要求不高,需要缓存优化,降低数据库压力,缓存kv设计优化可以通过[key:type]->[count]来确定,优化为[key]->[c1]:c2:c3],也就是:optimizedto:databasescalabilityoptimization,可以从列扩展优化到行扩展,也就是:optimizedto:countingsystemarchitecture先说到这里,希望大家Gained。【这篇文章是专栏作者《58神剑》原稿,转载请联系原作者】点此阅读更多该作者好文