PHP性能優化探索

如題,在開發過程中總會遇到的一些性能優化上的疑惑點,這裏我整理一下以便於後面自己的複習吧。

排序問題

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


未完,待續…

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章