如題,在開發過程中總會遇到的一些性能優化上的疑惑點,這裏我整理一下以便於後面自己的複習吧。
排序問題
場景如下:
有時候,根據業務場景的不同,總會遇到一些比較容易出分歧的思路。比如我今天遇到了一個“直播話題”相關的需求,後臺接口返回的是近期正在使用的直播話題,同時後臺可以通過對其進行上移下移實現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的長度等都是需要考慮的。但更重要的是,根據業務需求來選擇合適的方案,這裏直播話題通常來說根本不會達到萬的級別,所以老代碼的方式更爲妥當,但是這不是說所有的場景都適合用老代碼的方式構建,具體的場景,具體的業務需求,都是需要首先考慮在內的,功能做不出來,何談優化呢。
未完,待續…