字节跳动系统设计面试实时排行榜

题目

现在有个游戏,有亿级别的用户会来玩,玩游戏后会获得一个分数,当游戏结束的时候返回给用户自己所在的排名和排名前百分比,如何设计这样一个系统。

思路

功能其实并不算复杂,有很多种方式可以实现,所以这就要明确整个系统需要支持的用户量和容错与扩展等等,如果这个就几百的并发。

方案1 mysql

直接搞一个mysql的表即可解决。user_score (id, user_id, score, create_time),游戏结束就insert一条记录,然后select count出分数比自己高的,然后再count全表,即可算出排名和百分比。但是一般mysql实例也就上千的并发,题目明确说明过亿的用户来玩,这个并发上限是肯定无法支撑的。

方案2 redis zset

redis的zset是一个有序集合,比较适合拿来做排行榜的功能。其数据结构有三个 key, member, score。内部通过skiplist和ziplist进行排序。
字节跳动系统设计面试实时排行榜_第1张图片
跳跃表可以有序的遍历,维持了一种score顺序。
zset理论上能存放232的人数的排序,算下来远远大于亿级,所以放在一个跳跃表是可以的。单实例一般可以达到10w/s。
主要操作逻辑:

  1. 用户游戏完成写入redis zset分数, ZADD score_list score user_id写入zset
  2. 执行ZCOUNT score_list 0 score ,获取到排名后面的用户的数量x,ZCARD score_list 获得全部用户的数量y。
  3. 展示给用户击败了x个用户,排名前 (y - x) / y 的用户。
    因为ZADD命令不存在写入,存在就更新,user_id的score值,所以也不用做并发控制。

字节跳动系统设计面试实时排行榜_第2张图片

这里可能存在的瓶颈,就是单机的读写问题,首先单机的数量太大会导致性能降低,内存可能也不够,并且单机查询10w/s可能扛不住
一种扩容的方式就是挂redis从节点一起来负载整个读,master只提供写请求,这样理论上读的请求可以横向扩容,但是也存在一个情况就是主从延迟,可能导致数据不够准确,可以配置主从同步策略同步刷slave,但是这样会影响写入的性能,因此在业务允许的情况下最好异步刷slave。
字节跳动系统设计面试实时排行榜_第3张图片
这里解决了单节点的问题,高可用可引入哨兵节点进行主恢复,接下来单机内存无法存放所有用户的分数信息怎么办呢,而且如果单节点数据量太大,查询范围的耗时也会增加,因此考虑分片扩容,为了保证数据的均匀分布,假设用户id是一个随机值,可以取摸得到分片。字节跳动系统设计面试实时排行榜_第4张图片
这里按用户id去写入到不同的分片,每次用户来了都会查询所有节点,最后再进行聚合计算,一次用户提交,写入1次,查询2 * n次,一共操作1 + 2 * n次,因为每个分片是一个主从集群,所以查询压力并不是瓶颈,但是值得注意的是客户端的查询操作最好用多线程去查询,整个redis集群的rt取决于最慢的节点的分片。字节跳动系统设计面试实时排行榜_第5张图片
之后跟涛哥讨论了一下我的设计思路,他认为每次请求都来查询全部分片不太合理,而且如果分片过多,可能会导致部分分片网络抖动容错性可能较差(虽然客户端可以重试)他给出一个新的分片方案,按分数段去分片,但是这样会造成一定的数据倾斜问题(绝大部分人分数可能比较集中),好处就是查询排名前多少这个值就可以只查询高分数段的分片和当前分片的前面的数量。只需要1 + k + n,(k<=n),最好的情况2 + n次查询即可。

总结

专家或架构师面试考察复杂系统的架构设计比重越来越大,其题目往往看似简单,但是涉及了高性能缓存中间件redis的数据结构,负载均衡与高性能扩容,容错性,系统瓶颈分析,读写分离等等思路。没有全能的方案,只有在已经掌握比较全面的软件系统架构知识和充分了解业务场景逐渐演化才能给出一个合理的方案。

你可能感兴趣的:(经验总结,架构设计)