1. 程式人生 > >[Design] 後端程序高並發與異步

[Design] 後端程序高並發與異步

依賴 sel 多核 bit red curl 協程 實例 lee

既然涉及到高並發這個概念,就少不了先談這麽幾個概念,並發數、多進程、多線程、協程、負載均衡。

操作系統上講的並發是操作系統上有幾個程序在同時執行,單核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] 後端程序高並發與異步