如题,在开发过程中总会遇到的一些性能优化上的疑惑点,这里我整理一下以便于后面自己的复习吧。
排序问题
场景如下:
有时候,根据业务场景的不同,总会遇到一些比较容易出分歧的思路。比如我今天遇到了一个“直播话题”相关的需求,后台接口返回的是近期正在使用的直播话题,同时后台可以通过对其进行上移下移实现APP中固定顺序的输出。
老代码中是这样做的,用redis的一个sortedset类型的key记录着所有直播话题,当前时间戳作为话题(关联数组被json_encode后的member)的值。然后每次拿出所有的member,遍历每一个member(json_decode后有starttime和endtime字段)来过滤出当前有效的话题详情,最后通过PHP的ksort方法实现整体的排序。
看到这里,我就有点怀疑这段代码的性能了,火星每天开播的主播不在少数,而且这个接口在不同的地方都有可能被调用,可想而知,QPS也是一个不小的考验。那么有没有什么更快的实现呢?比如从设计key的时候,话题ID作为sortedset的topicid和score,而每一个topicid作为一个hash的member,对应话题详情(json_encode后的值)作为这个member的值。遍历的时候只需要找出endtime大于当前时间的topicid,然后再获取对应的话题详情。
具体哪一种我也不是很清楚,毕竟业务逻辑在那摆着呢,下面针对这个问题,来做一个试验好了。
老代码
<?php
$starttime = microtime(true);
$redis = new Redis();
$redis->connect("ip", 6379);
$redis->auth("auth");
$key = "questions:sort:old";
//$redis->del($key);
/*
//模拟存储源数据
for($index=0; $index< 1000; $index++) {
$topicid = time() + rand(-500, 500);
$details = array("starttime"=>$topicid, "endtime"=>intval($topicid), "desc"=>"直播话题详情:{$topicid}");
$redis->zadd($key, $topicid, json_encode($details));
}
*/
echo time()."\n";
// 根据老代码逻辑,拿到结果
$topics = $redis->zrevrange($key, 0, -1, true);
$result = array();
foreach($topics as $member=>$score) {
$member = json_decode($member, true);
if($member['endtime'] > 1525962300) { // 固定下时间戳
$result[$score] = $member;
}
}
ksort($result);
var_dump("共获取:".count($result)."个直播话题!\n");
echo "共计耗时:".(microtime(true) - $starttime)."毫秒\n";
执行结果如下:
- member数量为1000的时候
root@aliyun:/var/www/html/questions/sort# php old.php
1525963267
string(32) "共获取:329个直播话题!
"
共计耗时:0.021611928939819毫秒
- member数量为100000的时候:
1525963486
string(32) "共获取:489个直播话题!
"
共计耗时:6.1188609600067毫秒
新方法
<?php
$starttime = microtime(true);
$redis = new Redis();
$redis->connect("ip", 6379);
$redis->auth("auth");
$sortkey = "questions:sort:new:sort";// sortedset
$storekey = "questions:sort:new:store";//hash
//$redis->multi()->del($sortkey)->del($storekey)->exec();
/*
//模拟存储源数据
for($index=0; $index< 1000; $index++) {
$topicid = time() + rand(-500, 500);
$details = array("starttime"=>$topicid, "endtime"=>intval($topicid), "desc"=>"直播话题详情:{$topicid}");
$redis->multi()->zadd($sortkey, $topicid, $topicid)->hset($storekey, $topicid, json_encode($details))->exec();
}
*/
echo time()."\n";
// 根据老代码逻辑,拿到结果
$topicids = $redis->zrevrange($sortkey, 0, -1, true);
$result = array();
foreach($topicids as $topicid) {
if($topicid > 1525962703) { // 固定下时间戳
$result[$topicid] = json_decode($redis->hget($storekey, $topicid));
}
}
//ksort($result);
var_dump("共获取:".count($result)."个直播话题!\n");
$endtime = microtime(true);
echo "[{$starttime}], [{$endtime}]\n";
echo "共计耗时:".($endtime-$starttime)."毫秒\n";
执行结果如下:
- member 为1000的时候:
1525963345
string(32) "共获取:292个直播话题!
"
[1525963345.6308], [1525963346.2325]
共计耗时:0.60166215896606毫秒
- member为100000的时候:
1525963574
string(32) "共获取:484个直播话题!
"
[1525963574.1738], [1525963577.0668]
共计耗时:2.8929860591888毫秒
实验分析
从上面的数据可以发现这样的一个现象:
- 当member也就是话题数量较少的时候,选择老代码的方式速度更快。
- 当member数量较多的时候,选择新代码的方式速度会更快。
其实,仅仅有上面的测试样例,是不充分的。除了对速度的测试,我们还要考虑到redis服务器的性能,redis的QPS以及单次请求的数据压力(比如,返回值大小超过XX的时候,Redis就不能正常工作了,具体的数组也和配置,也和服务器本身的硬件性能有关,这里不做过多考究)。Redis本身对于member的长度等都是需要考虑的。但更重要的是,根据业务需求来选择合适的方案,这里直播话题通常来说根本不会达到万的级别,所以老代码的方式更为妥当,但是这不是说所有的场景都适合用老代码的方式构建,具体的场景,具体的业务需求,都是需要首先考虑在内的,功能做不出来,何谈优化呢。
未完,待续…