说明:本次实践基于Redis版本3.2.11。
关于SortedSet
首先,我们都知道Redis的SortedSet是可以根据score进行排序的,以手机应用商店的热门榜单排序为例,根据下载量倒序排列,其简单用法如下:
127.0.0.1:6379> zadd TopApp 12000000 wechat
(integer) 1
127.0.0.1:6379> zadd TopApp 8000000 taobao 10000000 alipay
(integer) 2
127.0.0.1:6379> ZREVRANGE TopApp 0 -1
1) "wechat"
2) "alipay"
3) "taobao"
对SortedSet做一下简单总结:
- 默认升序排列,即通过命令 ZRANGE 实现;如果要按照降序排列,需要通过命令 ZREVRANGE 实现;
- 当score即得分一样时,按照 字典顺序 对member进行排序,字典排序用的是二进制,它比较的是字符串的字节数组,所以实际上是比较ASCII码。
简单用法介绍完后,接下来给出几种方案,介绍 如何利用Redis实现多维度排序 。同样的,还是以手机应用商店的热门榜单排序为例:首先按照APP的下载量倒序排序,如果下载量一样,则按照最后更新时间倒序排列。
方案1
介绍的第一个方案,并不需要依赖SortedSet,它的实现非常简单,但是需要产品做简单的妥协,即不能实时更新榜单。其实现方案是:定时每隔1分钟(可以由产品确定时间间隔)通过SQL(select * from tb_apps order by download_count desc, updated_time desc limit 300)
或者其他方式计算热门榜单,然后把TOP300用List结构保存到缓存中。
说明:根据应用商店的用户行为分析,真实用户很少会预览10页以后的数据,即使有这种用户,我们也可以忽略掉。所以只需要将总计10页,即10x30=300个APP信息用List结构保存即可。分页取数据时,通过lrange命令即可轻松实现。
这种方案虽然简单,但是非常有用。即使不能做到实时,但是并没有影响用户体验。在项目初期需要快速发布,是一个比较推荐的做法。
方案2
方案2就是本文重点介绍的利用SortedSet实现多维度排序。
介绍方案之前,我们再看一下SortedSet排序因子score, 它是一个双精度64位的浮点型数字字符串。+inf和-inf都是有效值 ,能包括的整数范围是-(2^53) 到 +(2^53),或者说是-9007199254740992 到 9007199254740992。
那么,我们如何实现多维度排序呢?答案是 构造一个特殊的score 。以本文案例为例,排序影响因子是下载量和更新时间,那么我们可以构造一个这样特殊的浮点类型的score: 整数部分就是下载量,小数部分就是最后更新时间戳 。
talk is cheap,show me the code。假设有5个app的下载量和最后更新时间分别如下(说明:更新时间只精确到秒):
wechat-下载量:12000000,最后更新时间:1564022201;其score为:12000000.1564022201
qq-下载量:12000000,最后更新时间:1564022222;其score为:12000000.1564022222
tiktok-下载量:9808900,最后更新时间:1563552267;其score为:9808900.1563552267
taobao-下载量:11006600,最后更新时间:1564345601;其score为:11006600.1564345601
alipay-下载量:11006600,最后更新时间:1564345600;其score为:11006600.1564345600
接下来,我们通过如下命令将这5个APP用SortedSet数据类型保存到Redis中:
zadd TopApp 12000000.1564022201 wechat 12000000.1564022222 qq 9808900.1563552267 tiktok 11006600.1564345601 taobao 11006600.1564345600 alipay
保存后,我们看一下排序结果是否符合我们的预期:
127.0.0.1:6379> zrevrange TopApp 0 -1
1) "qq"
2) "wechat"
3) "taobao"
4) "alipay"
5) "tiktok"
写在最后
是不是很完美?
还不完美,这种讨巧的方式只能实现二维排序。如果有三维排序,四维排序呢?这里笔者提供一种实现参考,即 自定义得分权重计算公式 ,这个公式包含所有影响排序的因子,例如:downloadCount*1000+updatedTime
。这种实现无论排序维度多少都搞得定,但是需要注意的是,在具体实现时一定注意不要让score溢出。
总结
以上所述是小编给大家介绍的Redis高级玩法之利用SortedSet实现多维度排序的方法,希望对大家有所帮助,如果大家有任何疑问请给我留言,如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!
$arr = [
['1','张三',98,'http://www.img.com/3.jpg'],
['2','李四',79,'http://www.img.com/4.jpg'],
['3','王五',48,'http://www.img.com/5.jpg'],
['4','赵六',85,'http://www.img.com/6.jpg'],
['5','钱八',19,'http://www.img.com/8.jpg'],
['6','孙吉',38,'http://www.img.com/9.jpg'],
['7','张飞',46,'http://www.img.com/f.jpg'],
['8','关羽',65,'http://www.img.com/g.jpg'],
['9','德玛',73,'http://www.img.com/dm.jpg'],
['10','盖伦',73,'http://www.img.com/gl.jpg']
];
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->del('uid');
foreach($arr as $k=>$v){
$redis->lpush('uid',$v[0]);
$redis->set('name_'.$v[0], $v[1]);
$redis->set('score_'.$v[0], $v[2]);
$redis->set('user_img_'.$v[0], $v[3]);
}
//外部根据score排序
$sort = [
'BY'=>'score_*',
'SORT'=>'DESC' ,
//'LIMIT'=>array(0,3),
'GET'=>array('name_*','score_*','user_img_*')
];
echo '<pre>';
$res = array_chunk($redis->sort('uid',$sort), 3);
foreach($res as $k=>$v){
$res[$k]['sort'] = $k+1;
}
print_r($res);
foreach($res as $k=>$v){
$tr.="<tr>
<td>{$v['sort']}</td>
<td>{$v[0]}</td>
<td>{$v[2]}</td>
<td>{$v[1]}</td>
</tr>";
}
echo '<h1>排行榜</h1>';
$html = "<table>
<tr>
<th>排名</th>
<th>用户名</th>
<th>头像</th>
<th>分数 </th>>
</tr>
{$tr}
</table>";
echo $html;
header("Content-type: text/html; charset=utf-8");
//zset
$arr = [ //产品名 下载量 更新时间 产品图标
['wx',986,time(),'http://www.img.cn/wx.jpg'],
['qq',986,time()-1,'http://www.img.cn/qq.jpg'],
['taobao',425,time()-100,'http://www.img.cn/taobao.jpg'],
['sougou',476,time()-10,'http://www.img.cn/sougou.jpg']
];
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$topName = 'topApp';
$redis->del($topName);//先清空
//把下载次数和更新时间连接 进行排序
foreach($arr as $k=> $v){
$redis->zadd($topName,$v[1].$v[2],$v[0].'_'.$v[3]);
}
echo '<pre>';
//print_r( $redis->zscan($topName, 0, 'match','wx_*', 1) ) ;
print_r($redis->zrevrange($topName,0,-1));