继续回答星水友的问题,30WQPS的点数业务如何设计?可以看出该业务的特点是:高吞吐量;可以接受某些数据不一致;画外音:数的有点不准,没什么大问题。我们先用最简单的思路,只考虑点赞数,我们能做什么?有几点是最容易想到的:一定不要用数据库去抗实时读写流量;redis天然支持固化,可以使用高可用的redis集群来固化存储;也可以用MySQL做固化存储,redis做缓存,读写操作全部缓存,异步线程周期性刷新DB;设置一层计数服务,将计数与业务逻辑解耦;此时MySQL的核心数据结构为:t_count(msg_id,praise_count)这时候redis的KV设计并不难:key:msg_idvalue:praise_count看起来很容易搞定:服务可以横向扩展;当数据量增加时,可以水平扩展数据库;当读写量增加时,缓存也可以横向扩展;计数系统的难点在于业务扩展性和效率的问题。以微博为例:用户的微博首页有多个消息列表,是一个扩展;同一条消息msg_id不仅统计点赞数,还统计阅读量、转发量、评论量,也是一种扩展;如果用最简单的方式实现,获取多条消息的多次计数伪代码如下://(1)获取首页所有消息msg_idlist=getHomePageMsg(uid);//(2)对于首页所有的消息,需要Pullmultiplecountsfor(msg_idinlist){//(3.1)获取阅读计数getReadCount(msg_id);//(3.2)获取转发计数getForwordCount(msg_id);//(3.3)获取评论数getCommentCount(msg_id);//(3.4)获取好评数getPraiseCount(msg_id);}由于同一个msg_id多了几个业务数,rediskey需要携带业务flag,升级为:msg_id:readmsg_id:forwordmsg_id:commentmsg_id:praise用于用一个msg_id区分四种不同的业务计数,redis不能支持key的模糊操作,必须访问reids四次。假设首页有100条消息,这个方案总结如下:for循环每条消息,100条消息100次;每条消息调用RPCgetcount接口4次;每次服务被调用,获取reids,组装key得到count;画外音:这个方案的可扩展性和效率都很低。那么如何优化呢?首先看数据库层面的元数据扩展。常见的扩展方式是增加列,记录更多的业务数。如上图所示,一列点赞数扩展为四列阅读、转发、评论和点赞数。增加列的业务计数扩展方式的缺点是,每次要扩展业务计数,总是需要修改表结构和增加列,非常烦人。有没有什么办法可以在不改变表结构的情况下进行扩容呢?行扩展是一种更具可扩展性的方式。表结构固化为:t_count(msg_id,count_key,count_value)当要扩充业务计数时,只需要增加一行,表结构不做修改。画外音:很多配置商家会使用这个方案来方便额外的配置。增加行的业务计数扩展方式的缺点是表数据行数会增加,但这不是主要矛盾。数据库的水平扩展可以轻松解决大数据量的问题。接下来我们看看redis批量获取counts的优化方案。原方案通过组合key来区分同一msg_id的不同业务计数。它可以升级为存储具有相同值的多个计数。如上图所示,将相同msg_id的4个计数存储在一个value中,从而避免了多次redis访问。画外音:按价值扩张不是很聪明吗?总结一下计数业务,在数据量大,并发量大的时候需要考虑的一些技术点:使用缓存来抵抗读写;面向服务,统计系统与业务系统解耦;水平分片扩展了吞吐量、数据量和读写量;考虑可扩展性,数据库层面常见的优化包括:列扩展和行扩展;考虑批量操作,缓存层面常见的优化包括:一个值存储多个业务计数;计数系统优化先说到这里,希望大家有所收获。【本文为专栏作者《58神剑》原创稿件,转载请联系原作者】点此阅读更多该作者好文