概览
为什么需要多维度排序
常见的feed流架构下,我们需要很多的排序流,比如 雪球的评论流,需要按赞和时间进行排序(有赞的评论,先按赞排序。没有赞的评论按时间倒排)。
需求:按赞和评论排序。QPS > 5000,需要缓存。
常见设计方案:
- feed流里面存储按时间排序feed流。定时或者异步计算时间因子。但是这个时候会面临一个问题,你的feed流永远计算不是实时的。你的热贴流永远计算不及时。所以方案一不可取。
- 采用bit方式, 统一或若干小domain(比如点赞数), 再在排序后面接bit控制。核心在于如何计算score。
版本
redis版本:4.0
关于feed流:需要了解常见feed流架构。了解sort set。
关于sort set
见:https://redis.readthedocs.io/en/2.4/sorted_set.html
1 | # 添加单个元素 |
Zrange 返回有序集key
中,所有score
值介于min
和max
之间(包括等于min
或max
)的成员。有序集成员按score
值递增(从小到大)次序排列。
具有相同score
值的成员按字典序(lexicographical order)来排列(该属性是有序集提供的,不需要额外的计算)。
可选的LIMIT
参数指定返回结果的数量及区间(就像SQL中的SELECT LIMIT offset, count
),注意当offset
很大时,定位offset
的操作可能需要遍历整个有序集,此过程最坏复杂度为O(N)时间。
score 聚合
在这里,我们先简单定义各个因子的权重:点赞数(like_count) > 回复数(reply_count) > 按时间排序(time)。
为了尽量减少我们score值占用的bit位。
为了描述清楚。这里用一个例子:当前评论,点赞数为 5 , 回复数为 8 ,时间戳为 12312312
默认 score为0。
- 因子权重最低的放在小数位。其实时间戳并不连续,需要发号器(snowflake算法)使时间戳因子连续。现在的 score = 0.12312312 。
- 第二维度为回复数。这里需要第一个计算手段:计算出当前数据库里面,回复数最多的那个评论的数目。假设当前最大回复数为n,这里取10000。在这里做最大数据保护,扩展100 倍,即,我们任务,未来会有一个评论的回复数会到100w(注意:只是保护设计,生产环境需要详细评估,基于你的用户体量来分析,考虑互联网的2、8分布)。此时的score = 8.12312312。
- 第三个维度为点赞数。上一步的最大回复数用在这里。即第三个维度的score = 100w * 5 + 8 + 0.12312312
即 : score = like_count * ( max(reply_count) * 100) + reply_count + 0.time 。
推导过程
当用户有点赞时,即like_count > 0:
用户的 score = like_count * ( max(reply_count) * 100) + reply_count + 0.time > 0 * ( max(reply_count) * 100) + reply_count + 0.time >= reply_count + 0.time 。
即:当用户的评论有赞时,永远会按赞排序。
回复同理,当like_count = 0、 reply_count > 0 时:
score = like_count * ( max(reply_count) * 100) + reply_count + 0.time > 0 * ( max(reply_count) * 100) + 0* reply_count + 0.time >= 0.time 。
即:当用户的评论没有赞时,但有评论时、永远会按时间排序。
验证:当我们有三个评论
- A (点赞 3, 评论 5,时间 100 ) 。 score = 3000005.100
- B (点赞 4, 评论 5,时间 100 )。 score = 4000005.100
- C (点赞 3, 评论 4,时间 99 )。 score = 3000004.99
1 | redis> ZRANGE add 3000005.100 A |