日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

连接池和协程池为何能提升并发能力?

發布時間:2023/11/28 生活经验 55 豆豆
生活随笔 收集整理的這篇文章主要介紹了 连接池和协程池为何能提升并发能力? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

你有沒有發現,“內存池”和“進程池”都帶有“池”字?其實,這兩種技術都屬于“池化技術”。它通常是由系統預先分配一批資源并循環利用,達到資源“池化”的目的,以解決高并發下,由于大量分配資源,導致性能開銷過大的問題。

一句話,池化技術的核心是“預分配”和“循環使用”。這就像食堂在用餐高峰期安排餐具一樣。食堂通常會在餐廳提前準備一批餐具,并在用餐過程中安排人專門清洗餐具,然后循環使用。食堂采用這種方法,節省了開支,原來只有 50 套的餐具就能保障 100 人有序就餐。池化技術也是類似的作用。

那么,除了前面提到的內存池和進程池外,都還有哪些池化技術呢?常用的池化技術還有連接池和協程池。它們解決什么具體問題呢?

連接池的作用
連接池是指預先分配一批連接,并將它們放入一個緩沖區中循環使用,形成池化效應。以秒殺為例,秒殺接口服務并發非常高,如果不用連接池,會導致什么后果?

在介紹 KV 存儲的時候,我提到過秒殺系統使用 Redis 緩存活動信息。假如秒殺接口服務與 Redis 只有一個連接,平均每次請求 Redis 耗時 10ms,一秒鐘能請求多少次呢?沒錯,一個連接一秒鐘最多只能處理 100 次請求。

在秒殺系統中,除了活動信息外,秒殺庫存信息也緩存在 Redis 中,而且需要支持 1 萬以上并發能力?;顒有畔⒓由蠋齑执嫘畔?#xff0c;分攤到 50 個秒殺接口節點上,平均每個節點向 Redis 發起的請求可能超過 300 QPS,遠超過單個連接的處理能力。

這就會出現第一個問題:單連接無法承載高并發。

可能你會說,既然復用單個連接無法承載高并發,那就每次請求都新建連接嘛!想法很好,但現實很殘酷。建立連接的時候,TCP 需要經歷三次握手,假如網絡延遲是 5 ms,三次握手就耗費 15ms,這比一次請求來回的時間都長了。其次,如果每次請求都建立連接,還需要考慮關閉連接,以免連接數過多壓垮 Redis。而關閉連接的過程涉及 TCP 四次揮手,這又是一筆時間開銷。

所以,如果不用連接池就會出現第二個問題:每次請求建立、關閉連接會導致請求延遲增加,還有可能把 Redis 壓垮。

另外,如果高并發下頻繁地建立、關閉連接,會導致操作系統耗費過多 CPU 用于分配、回收系統資源。

那我們該如何設計連接池來解決這三個問題呢?

通常,連接池有幾個參數:最小連接數、空閑連接數、最大連接數。為何這么設計呢?請看下圖:


最小連接數通常用于控制當前連接數的最小值,如果連接數小于最小值,遇到突發流量容易導致性能問題。

空閑連接數就是用于控制連接池中空閑連接的數量,如果超過這個值,意味著浪費資源,需要關閉多余連接;如果低于這個值,則可能無法應對突發流量,需要分配新的空閑連接。

有關空閑連接的分配,可以通過定時器來控制。設置時,要盡量保障秒殺服務向 Redis 發起請求的時候,有足夠的空閑連接,這樣可以減少建立連接的時間和資源開銷。通常是由獨立的線程定時檢查空閑連接是否小于某個值,比如每隔 1 秒鐘檢查空閑連接數是否小于 2,是的話就新建一批空閑連接。

最大連接數通常用于控制系統中連接數不超過最大值,以免大量連接將 Redis 壓垮。

秒殺服務在啟動時,將按照配置文件中的參數初始化好連接數池,比如設置初始連接數為最小連接數。當秒殺服務需要發起一個 Redis 請求時,會先嘗試從連接池中獲取連接,如果獲取不到,則會建立一個新的連接。

當連接數超過最大值時, 請求就會阻塞,等待其他請求歸還連接。在請求完 Redis 后,秒殺服務需要將連接放回到連接池中。如果空閑連接超過參數指定的數,秒殺服務會直接關閉該連接。

另外,為了確保 Redis 集群中的多個實例負載均衡,連接池中的連接也需要做負載均衡。

如何從連接池中獲取連接,用完后又如何將連接放回到連接池中呢?

通??梢圆捎醚h隊列來保存空閑連接。使用的時候,可以從隊列頭部取出連接,用完后將空閑連接放到隊列尾部。在 Go 語言中,還有另外一種方法,那就是利用帶緩沖區的 channel 來充當隊列。這個實現起來非常簡單,在代碼實戰環節我會詳細給你介紹。

協程池的作用
協程池,簡單來說就是由多個協程實現的池化技術。

如果你了解過 Linux 內核,應該知道 Linux 內核中是以進程為單元來調度資源的,線程也是輕量級進程??梢哉f,進程、線程都是由內核來創建并調度。而協程是由應用程序創建出來的任務執行單元,比如 Go 語言中的協程“goroutine”。協程本身是運行在線程上,由應用程序自己調度,它是比線程更輕量的執行單元。

相比進程、線程,協程有什么優點呢?那得從高并發下進程、線程的性能說起。

當應用程序需要創建一個進程或者線程時,它需要先調用系統函數向內核提交申請。接著它會從用戶態切換到內核態,由內核創建進程和線程,然后再從內核態切換到用戶態。

在用戶態與內核態來回切換的過程中,操作系統需要保存大量的上下文信息。具體來說,從用戶態切換到內核態時,內核需要將 CPU 各寄存器中的數據寫入到棧內存;當程序從內核態恢復到用戶態時,需要內核將前面保存的寄存器數據從棧內存加載到 CPU 中。

內核創建進程和線程的時候,需要分配 PCB (Processing Control Block,進程控制塊),其作用是用于保存進程和線程的運行狀態。也就是說,進程和線程在內核中是占用內存空間的。并且,在分配 PCB 的過程也會耗費額外的 CPU 資源。

特別是在父進程創建子進程的時候,子進程會繼承父進程的內存狀態。當子進程修改某塊內存的數據時,會觸發寫時拷貝,系統會為子進程分配新的內存空間,這意味著運行時子進程有額外的 CPU 開銷。另外,每個線程也有自己的??臻g,比如 Linux 下默認??臻g大小是 8MB,也就是說即使線程程什么事情都不做,也會白白浪費 8MB 內存。

還有,前面提到了創建進程和線程時會發生狀態切換,主要是因為內核中的任務是以內核線程的方式來運行的。而且內核線程是可以動態創建的,可以超過 CPU 線程數。但是,一個 CPU 線程在同一時刻只能運行一個內核線程,這就涉及內核線程間上下文切換的問題了。

要知道,上下文切換是很耗費 CPU 資源的。首先,上下文切換時 CPU 需要耗費更多的時間保存或者加載數據,這會導致 CPU 有效使用率降低。其次,上下文切換后,需要加載新的數據,可能導致 CPU 中的 L1、L2 緩存中數據失效,性能打折扣。

在高并發場景下,每創建、銷毀一個線程,帶來的 CPU 和內存開銷都不小。為了解決這些問題,協程誕生了。

在 Go 語言中,一個協程初始內存空間是 2KB,相比線程和進程來說要小很多。協程的創建和銷毀完全是在用戶態執行的,不涉及用戶態和內核態的切換。另外,協程完全由應用程序在用戶態下調用,不涉及內核態的上下文切換。協程切換時由于不需要處理線程狀態,需要保存的上下文也很少,速度很快。

既然協程的創建、切換、銷毀性能已經很高了,為何還要做協程池呢?

前面提到了,每個協程初始內存大小為 2K。假如每個請求都創建一個協程,當秒殺服務單機并發達到 10 萬的時候,會帶來近 200MB 的內存分配和占用,可能會帶來 GC 回收內存的問題。

另外,雖然協程創建很快,但還是要耗費時間的,比如需要分配內存,需要初始化協程狀態等。在秒殺這種高并發場景下,每個請求哪怕是增加 1 毫秒延遲,也會給服務帶來不小的 CPU 開銷。

那我們如何設計協程池呢?

在 Go 語言中,協程池的實現方法有兩種:搶占式和調度式。

搶占式協程池中,所有任務存放到一個共享的 channel 中,由多個協程同時去消費 channel 中的任務,誰先拿到誰先執行。它的好處是下發任務的邏輯可以實現的很簡單,拿到任務直接放到共享 channel 里即可。缺點是多個協程同時消費一個 channel 會涉及鎖的爭奪,當協程執行比較耗時的任務時,單個 channel 也容易帶來容量問題。

調度式協程池中,每個協程都有自己的 channel,每個協程只消費自己的 channel。當下發任務的時候,可以采用負載均衡算法選擇合適的協程來執行任務。比如選擇排隊中任務最少的協程,或者簡單輪詢。

原文:
https://kaiwu.lagou.com/course/courseInfo.htm?courseId=547#/detail/pc?id=5304

總結

以上是生活随笔為你收集整理的连接池和协程池为何能提升并发能力?的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。