内容产品中比较常见的就是各种计数,比如点赞数、评论数、粉丝数等,如下图:
由于近期需要实现计数的功能,因此针对如何计数实现作了下思考,并总结成本文。
简单粗暴,解决问题的最快方式。比较常见的使用场景有交易系统中的订单计数,以用户为维度按状态进行分组计数,更新不是很频繁,从性能上考虑,可以在上层做缓存。
然而最快并不一定意味着最好,在单用户记录数比较多的时候,利用数据库去做聚合统计成本还是相当高的,在列表场景下就更不适合了。
类似如下的表结构:
+----------+--------+-------------+
| biz_type | biz_id | biz_counter |
+----------+--------+-------------+
| 1 | 8094 | 3 |
| 2 | 8095 | 9 |
| 3 | 8099 | 7 |
+----------+--------+-------------+
通过业务类型、业务id的唯一性来关联计数。业务数据的更新,同步更新db中的计数记录。同样从性能上考虑,可以在上层做一层缓存,计数更新,同步清除缓存。
相比方案一,会减少很多db的慢查询,也适合列表式的场景,但考虑业务发展到一定规模后,尤其是内容社交型的产品,数据频繁更新,tps瞬间峰值高,导致db的开销比较大,缓存命中率也不高,故此方案也只适用于中小型产品。
注意,标题是内存存储,不是内存缓存。内存存储是不设过期时间的。如果业务发展迅速,导致内存容量不够,像db扩容一样,内存也要进行扩容。
虽然是内存存储,但redis是支持数据落地的,因此不必担心数据丢失。访问计数不存在,上层业务可以认为计数值就是0。这一点就避免了大部分没有计数的数据(比如无赞无评论的问题)占用不必要的内存。另外redis设计本身支持原子性的增减操作,如命令HINCRBY、INCRBY,因此也不必担心数据被改错的问题。
只要保证redis的高可用,此方案相比方案一、二在性能上是最优的。然而好的东西总要付出代价,毕竟内存是昂贵的。在成本可控的情况下,要保证服务可用性,只能在软件设计上进行突破了。
方案三的性能最优,但受内存成本限制,因此需要在成本与用户体验上做出一个权衡。基本思想是:
基于以上思想,来看下面的计数服务架构图:
说明:
需要额外注意的是,计数写入要进行判断:
以上都无法根本避免,在线程高并发的情况下,从取完初始化的数据到执行完HMSET期间数据丢失的可能。不过在缓存已经过期的情况下,业务上基本很少有高并发了
欢迎有更好的方案提出来与大家共享。
网易云大礼包:https://www.163yun.com/gift
本文来自网易实践者社区,经作者刘魏威授权发布