[Design] 後端程序高並發與異步
既然涉及到高並發這個概念,就少不了先談這麽幾個概念,並發數、多進程、多線程、協程、負載均衡。
操作系統上講的並發是操作系統上有幾個程序在同時執行,單核CPU在微觀上是由CPU調度執行,非同時執行,多核CPU在微觀上才是真正的並行。
互聯網產品的並發通常是指並發連接數,用戶同時訪問數量,哪些因素能影響到並發能力,既有編程模型,也有服務器負載能力。
PHP 依賴多進程解決並發數,是最原始和耗費資源的一種方式。
以 8G 內存服務器為例,一個PHP進程占用20M內存,最多也就開幾百個進程,假如任務比較耗時且並發很高,那麽新請求很快就得不到響應了。
對於並發訪問 PHP 提供 curl_multi_* 系列函數,純接口的並發調用就不需要自己寫多進程、多線程程序了。
Java是多線程模型,每個進程可以創建多個線程,線程使用進程的上下文,每個線程只需要小部分運行資源,所以比進程輕量許多。
進程和線程都是依賴操作系統的系統調用支持的,所以這部分調度開銷是難以避免的。
協程是程序級的調度模型,最輕量,好比框架級實現像操作系統一樣能自由對程序中斷、調度,比如借助於 C 的 setjmp 系列函數就能做到中斷。
不管編程模型多先進,單機總會達到並發上限,要想突破限制就必須引入負載均衡,通過橫向擴展解決問題。
上面所提到的編程模型優劣,本質上還都是語言層面的,短期內並不能真正解決問題,下面就延伸出從應用層面的考慮:
為什麽高性能都離不開異步,比如 Swoole ? 目的就是提升響應時間,提高Qps。
在我們通常的業務開發過程中,邏輯代碼一般是同步阻塞模式,一方面它容易理解,另一方面也方便進行一些測試。
這些優勢再加上大部分業務場景對並發並沒有較高的要求,所以是可以接受的。
但是對於一個大的網站,以及對響應速度和並發要求高的場景,這時候就需要做些優化了,盡量把阻塞操作給異步化。
通過消息隊列來做異步的場景:
加速響應:比如註冊後的提醒郵件,註冊操作成功後將發消息交給隊列,直接返回信息給用戶,寫入隊列的速度非常快,然後由訂閱的異步任務處理郵件發送。
應用解耦:比如用戶發帖之後要給他的粉絲推送帖子,這時候實時性要求並不高,可以將新帖的消息寫入隊列,然後由隊列處理程序操作。
流量削峰:比如秒殺活動,我們不需要實時處理一些購買邏輯,只要將用戶請求寫入消息隊列,長度達到限制就提示用戶已結束,後續程序再對隊列內容處理。
日誌收集:日誌一般都需要進行寫磁盤操作,大訪問量會對 I/O 造成壓力,降低程序性能;此時可以將日誌寫入消息隊列,由處理程序訂閱該隊列進行消費。
廣播聊天:用戶通過訂閱頻道來獲得最新發布的消息。
實例演示
<?php /** * Pub.php * * @author farwish */ // 1.連接並選擇數據庫 $redis = new \Redis(); $bool = $redis->connect(‘127.0.0.1‘, 6379, 2.5, null, 100); if (! $bool) { die(‘連接失敗‘); } $bool = $redis->select(0); // 模擬將一個任務放置隊列中, 並發布 //$phone = 13199999999; //$redis->lpush(‘sms-signin‘, $phone); //$redis->publish(‘sms-signin‘, $phone); // // 2.模擬生成批量數據 for ($i = 0; $i < 10; $i++) { $phone = mt_rand(10000000000, 99999999999); echo $phone . PHP_EOL; // 模擬每個任務耗時100ms, 使用 publish 代替方案,在 subscribe 中處理 //echo $phone . PHP_EOL; //usleep(100000); $redis->publish(‘signin-sms‘, $phone); }
如果不將消息寫入隊列,而是每次都自己執行,響應時間很長,用戶體驗不好。
通過訂閱程序異步處理任務,用戶無感知,並且體驗會很好。
<?php /** * Sub.php * * @author farwish */ // 1.連接並選擇數據庫 $redis = new \Redis(); $bool= $redis->connect(‘127.0.0.1‘, 6379, 2.5, null, 100); if (! $bool) { die(‘連接失敗‘); }
// * 阻止 redis read timeout $redis->setOption(Redis::OPT_READ_TIMEOUT, -1); // 2.訂閱耗時任務並處理 // 如果這個訂閱的任務比較重要,將對可用性有要求,日誌收集等可以采用。 $redis->subscribe([‘signin-sms‘, ‘signin-mail‘, ‘crawler-task-1‘], function($redis, $chan, $msg) { switch ($chan) { case ‘signin-sms‘: // 耗時1s, 發送並記錄數據庫 sleep(1); echo "{$msg} 發註冊短信\n"; break; case ‘signin-mail‘: break; case ‘crawler-task-1‘: // 其他耗時任務,通過 $msg 傳遞參數來執行 break; } });
其它常見的消息隊列產品有 RabbitMQ、ZeroMQ、ActiveMQ、Kafka ..
Link:https://www.cnblogs.com/farwish/p/9513100.html
[Design] 後端程序高並發與異步