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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

数据库性能优化—数据库连接池

發(fā)布時間:2024/4/15 数据库 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 数据库性能优化—数据库连接池 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章出自:阿里巴巴十億級并發(fā)系統(tǒng)設計(2021版)

鏈接:https://pan.baidu.com/s/1lbqQhDWjdZe1CBU-6U4jhA?提取碼:8888?

目錄

接下來,讓我們正式進入課程。

那么為什么頻繁創(chuàng)建連接會造成響應時間慢呢?來看一個實際的測試。

用連接池預先建立數據庫連接

用線程池預先創(chuàng)建線程

課程小結


在前面幾節(jié)課程中,我從宏觀的角度帶你了解了高并發(fā)系統(tǒng)設計的基礎知識,你已經知曉 了,我們系統(tǒng)設計的目的是為了獲得更好的性能、更高的可用性,以及更強的系統(tǒng)擴展能 力。

那么從這一講開始,我們正式進入演進篇,我會再從局部出發(fā),帶你逐一了解完成這些目標 會使用到的一些方法,這些方法會針對性地解決高并發(fā)系統(tǒng)設計中出現的問題。比如,在 15 講中我會提及布隆過濾器,這個組件就是為了解決存在大量緩存穿透的情況下,如何盡 量提升緩存命中率的問題。

當然,單純地講解理論,講解方案會比較枯燥,所以我將用一個虛擬的系統(tǒng)作為貫穿整個課程的主線,說明當這個系統(tǒng)到達某一個階段時,我們會遇到什么問題,然后要采用什么樣的方案應對,應對的過程中又涉及哪些技術點。通過這樣的講述方式,力求以案例引出問題,

能夠讓你了解遇到不同問題時,解決思路是怎樣的,當然,在這個過程中,我希望你能多加 思考,然后將學到的知識活學活用到實際的項目中。

接下來,讓我們正式進入課程。

來想象這樣一個場景,一天,公司 CEO 把你叫到會議室,告訴你公司看到了一個新的商業(yè) 機會,希望你能帶領一名兄弟,迅速研發(fā)出一套面向某個垂直領域的電商系統(tǒng)。在人手緊張,時間不足的情況下,為了能夠完成任務,你毫不猶豫地采用了最簡單的架構: 前端一臺 Web 服務器運行業(yè)務代碼,后端一臺數據庫服務器存儲業(yè)務數據。這個架構圖是我們每個人最熟悉的,最簡單的架構原型,很多系統(tǒng)在一開始都是長這樣的, 只是隨著業(yè)務復雜度的提高,架構做了疊加,然后看起來就越來越復雜了。

再說回我們的垂直電商系統(tǒng),系統(tǒng)一開始上線之后,雖然用戶量不大,但運行平穩(wěn),你很有 成就感,不過 CEO 覺得用戶量太少了,所以緊急調動運營同學做了一次全網的流量推廣。這一推廣很快帶來了一大波流量,但這時,系統(tǒng)的訪問速度開始變慢。

分析程序的日志之后,你發(fā)現系統(tǒng)慢的原因出現在和數據庫的交互上因為你們數據庫的調用方式是先獲取數據庫的連接,然后依靠這條連接從數據庫中查詢數據,最后關閉連接釋放數據庫資源。這種調用方式下,每次執(zhí)行 SQL 都需要重新建立連接,所以你懷疑,是不是頻繁地建立數據庫連接耗費時間長導致了訪問慢的問題

那么為什么頻繁創(chuàng)建連接會造成響應時間慢呢?來看一個實際的測試。

我用"tcpdump -i bond0 -nn -tttt port 4490"命令抓取了線上 MySQL 建立連接的網絡包 來做分析,從抓包結果來看,整個 MySQL 的連接過程可以分為兩部分:

第一部分是前三個數據包。第一個數據包是客戶端向服務端發(fā)送的一個“SYN”包,第二個 包是服務端回給客戶端的“ACK”包以及一個“SYN”包,第三個包是客戶端回給服務端 的“ACK”包,熟悉 TCP 協議的同學可以看出這是一個 TCP 的三次握手過程。

第二部分是 MySQL 服務端校驗客戶端密碼的過程。其中第一個包是服務端發(fā)給客戶端要 求認證的報文,第二和第三個包是客戶端將加密后的密碼發(fā)送給服務端的包,最后兩個包是 服務端回給客戶端認證 OK 的報文。從圖中,你可以看到整個連接過程大概消耗了 4ms(969012-964904)。

那么單條 SQL 執(zhí)行時間是多少呢?我們統(tǒng)計了一段時間的 SQL 執(zhí)行時間,發(fā)現 SQL 的平均執(zhí)行時間大概是 1ms,也就是說相比于 SQL 的執(zhí)行,MySQL 建立連接的過程是比較耗時的。這在請求量小的時候其實影響不大,因為無論是建立連接還是執(zhí)行 SQL,耗時都是毫秒級別的。可是請求量上來之后,如果按照原來的方式建立一次連接只執(zhí)行一條 SQL 的 話,1s 只能執(zhí)行 200 次數據庫的查詢,而數據庫建立連接的時間占了其中 4/5。

那這時你要怎么做呢?一番谷歌搜索之后,你發(fā)現解決方案也很簡單,只要使用連接池將數據庫連接預先建立好, 這樣在使用的時候就不需要頻繁地創(chuàng)建連接了。調整之后,你發(fā)現 1s 就可以執(zhí)行 1000 次 的數據庫查詢,查詢性能大大的提升了。

用連接池預先建立數據庫連接

雖然短時間解決了問題,不過你還是想徹底搞明白解決問題的核心原理,于是又開始補課。 其實,在開發(fā)過程中我們會用到很多的連接池,像是數據庫連接池、HTTP 連接池、Redis 連接池等等。而連接池的管理是連接池設計的核心,我就以數據庫連接池為例,來說明一下連接池管理的關鍵點。

數據庫連接池有兩個最重要的配置:最小連接數和最大連接數,它們控制著從連接池中獲取 連接的流程:

  • 如果當前連接數小于最小連接數,則創(chuàng)建新的連接處理數據庫請求;
  • 如果連接池中有空閑連接則復用空閑連接;
  • 如果空閑池中沒有連接并且當前連接數小于最大連接數,則創(chuàng)建新的連接處理請求;
  • 如果當前連接數已經大于等于最大連接數,則按照配置中設定的時間(C3P0 的連接池配 置是 checkoutTimeout)等待舊的連接可用;
  • 如果等待超過了這個設定時間則向用戶拋出錯誤。
  • 這個流程你不用死記,非常簡單。你可以停下來想想如果你是連接池的設計者你會怎么設 計,有哪些關鍵點,這個設計思路在我們以后的架構設計中經常會用到。

    為了方便你理解性記憶這個流程,我來舉個例子。 假設你在機場里經營著一家按摩椅的小店,店里一共擺著 10 臺按摩椅(類比最大連接 數),為了節(jié)省成本(按摩椅費電),你平時會保持店里開著 4 臺按摩椅(最小連接 數),其他 6 臺都關著。有顧客來的時候,如果平時保持啟動的 4 臺按摩椅有空著的,你直接請他去空著的那臺就 好了。但如果顧客來的時候,4 臺按摩椅都不空著,那你就會新啟動一臺,直到你的 10 臺 按摩椅都被用完。那 10 臺按摩椅都被用完之后怎么辦呢?你會告訴用戶,稍等一會兒,我承諾你 5 分鐘(等 待時間)之內必定能空出來,然后第 11 位用戶就開始等著。這時,會有兩個結果:如果 5 分鐘之內有空出來的,那顧客直接去空出來的那臺按摩椅就可以了,但如果用戶等了 5 分 鐘都沒空出來,那你就得賠禮道歉,讓用戶去其他店再看看。

    對于數據庫連接池,根據我的經驗,一般在線上我建議最小連接數控制在 10 左右,最大連 接數控制在 20~30 左右即可。在這里,你需要注意池子中連接的維護問題,也就是我提到的按摩椅。有的按摩椅雖然開 著,但有的時候會有故障,一般情況下,“按摩椅故障”的原因可能有以下幾種:

  • 數據庫的域名對應的 IP 發(fā)生了變更,池子的連接還是使用舊的 IP,當舊的 IP 下的數據 庫服務關閉后,再使用這個連接查詢就會發(fā)生錯誤;
  • MySQL 有個參數是“wait_timeout”,控制著當數據庫連接閑置多長時間后,數據庫會 主動的關閉這條連接。這個機制對于數據庫使用方是無感知的,所以當我們使用這個被關閉 的連接時就會發(fā)生錯誤。
  • 那么,作為按摩椅店老板,你怎么保證你啟動著的按摩椅一定是可用的呢?

  • 啟動一個線程來定期檢測連接池中的連接是否可用,比如使用連接發(fā)送“select 1”的命 令給數據庫看是否會拋出異常,如果拋出異常則將這個連接從連接池中移除,并且嘗試關 閉。目前 C3P0 連接池可以采用這種方式來檢測連接是否可用,也是我比較推薦的方式。
  • 在獲取到連接之后,先校驗連接是否可用,如果可用才會執(zhí)行 SQL 語句。比如 DBCP 連 接池的 testOnBorrow 配置項,就是控制是否開啟這個驗證。這種方式在獲取連接時會引 入多余的開銷,在線上系統(tǒng)中還是盡量不要開啟,在測試服務上可以使用。
  • 至此,你徹底搞清楚了連接池的工作原理。可是,當你剛想松一口氣的時候,CEO 又提出 了一個新的需求。你分析了一下這個需求,發(fā)現在一個非常重要的接口中,你需要訪問 3 次數據庫。根據經驗判斷,你覺得這里未來肯定會成為系統(tǒng)瓶頸。
  • 進一步想,你覺得可以創(chuàng)建多個線程來并行處理與數據庫之間的交互,這樣速度就能快了。 不過,因為有了上次數據庫的教訓,你想到在高并發(fā)階段,頻繁創(chuàng)建線程的開銷也會很大, 于是順著之前的思路繼續(xù)想,猜測到了線程池。

    用線程池預先創(chuàng)建線程

    果不其然,JDK 1.5 中引入的 ThreadPoolExecutor 就是一種線程池的實現,它有兩個重要 的參數:coreThreadCount 和 maxThreadCount,這兩個參數控制著線程池的執(zhí)行過程。它的執(zhí)行原理類似上面我們說的按摩椅店的模式,我這里再給你描述下,以加深你的記憶:

  • 如果線程池中的線程數少于 coreThreadCount 時,處理新的任務時會創(chuàng)建新的線程;
  • 如果線程數大于 coreThreadCount 則把任務丟到一個隊列里面,由當前空閑的線程執(zhí) 行;
  • 當隊列中的任務堆積滿了的時候,則繼續(xù)創(chuàng)建線程,直到達到 maxThreadCount;
  • 當線程數達到 maxTheadCount 時還有新的任務提交,那么我們就不得不將它們丟棄 了。
  • 這個任務處理流程看似簡單,實際上有很多坑,你在使用的時候一定要注意:

    首先,?JDK 實現的這個線程池優(yōu)先把任務放入隊列暫存起來,而不是創(chuàng)建更多的線程,它 比較適用于執(zhí)行 CPU 密集型的任務,也就是需要執(zhí)行大量 CPU 運算的任務。這是為什么呢?因為執(zhí)行 CPU 密集型的任務時 CPU 比較繁忙,因此只需要創(chuàng)建和 CPU 核數相當的線程就好了,多了反而會造成線程上下文切換,降低任務執(zhí)行效率。所以當當前線程數超過核心線程數時,線程池不會增加線程,而是放在隊列里等待核心線程空閑下來。但是,我們平時開發(fā)的 Web 系統(tǒng)通常都有大量的 IO 操作,比方說查詢數據庫、查詢緩存 等等。任務在執(zhí)行 IO 操作的時候 CPU 就空閑了下來,這時如果增加執(zhí)行任務的線程數而不是把任務暫存在隊列中,就可以在單位時間內執(zhí)行更多的任務,大大提高了任務執(zhí)行的吞吐量。所以你看 Tomcat 使用的線程池就不是 JDK 原生的線程池,而是做了一些改造當線程數超過 coreThreadCount 之后會優(yōu)先創(chuàng)建線程,直到線程數到maxThreadCount,這樣就比較適合于 Web 系統(tǒng)大量 IO 操作的場景了,你在實際運用過 程中也可以參考借鑒。

    其次,線程池中使用的隊列的堆積量也是我們需要監(jiān)控的重要指標,對于實時性要求比較高的任務來說,這個指標尤為關鍵。我在實際項目中就曾經遇到過任務被丟給線程池之后,長時間都沒有被執(zhí)行的詭異問題。最 初,我認為這是代碼的 Bug 導致的,后來經過排查發(fā)現,是因為線程池的 coreThreadCount 和 maxThreadCount 設置的比較小,導致任務在線程池里面大量的堆積,在調大了這兩個參數之后問題就解決了。跳出這個坑之后,我就把重要線程池的隊列任務堆積量,作為一個重要的監(jiān)控指標放到了系統(tǒng)監(jiān)控大屏上

    最后,如果你使用線程池請一定記住不要使用無界隊列(即沒有設置固定大小的隊列)。也 許你會覺得使用了無界隊列后,任務就永遠不會被丟棄,只要任務對實時性要求不高,反正早晚有消費完的一天。但是,大量的任務堆積會占用大量的內存空間,一旦內存空間被占滿就會頻繁地觸發(fā) Full GC,造成服務不可用,我之前排查過的一次 GC 引起的宕機,起因就是系統(tǒng)中的一個線程池使用了無界隊列。

    理解了線程池的關鍵要點,你在系統(tǒng)里加上了這個特性,至此,系統(tǒng)穩(wěn)定,你圓滿完成了公 司給你的研發(fā)任務。

    這時,你回顧一下這兩種技術,會發(fā)現它們都有一個共同點:它們所管理的對象,無論是連接還是線程,它們的創(chuàng)建過程都比較耗時,也比較消耗系統(tǒng)資源。所以,我們把它們放在一 個池子里統(tǒng)一管理起來,以達到提升性能和資源復用的目的。

    這是一種常見的軟件設計思想,叫做池化技術,它的核心思想是空間換時間,期望使用預先創(chuàng)建好的對象來減少頻繁創(chuàng)建對象的性能開銷,同時還可以對對象進行統(tǒng)一的管理,降低了對象的使用的成本,總之是好處多多。

    不過,池化技術也存在一些缺陷,比方說存儲池子中的對象肯定需要消耗多余的內存,如果對象沒有被頻繁使用,就會造成內存上的浪費。再比方說,池子中的對象需要在系統(tǒng)啟動的時候就預先創(chuàng)建完成,這在一定程度上增加了系統(tǒng)啟動時間

    可這些缺陷相比池化技術的優(yōu)勢來說就比較微不足道了,只要我們確認要使用的對象在創(chuàng)建 時確實比較耗時或者消耗資源,并且這些對象也確實會被頻繁地創(chuàng)建和銷毀,我們就可以使 用池化技術來優(yōu)化。

    課程小結

    本節(jié)課,我模擬了研發(fā)垂直電商系統(tǒng)最原始的場景,在遇到數據庫查詢性能下降的問題時, 我們使用數據庫連接池解決了頻繁創(chuàng)建連接帶來的性能問題,后面又使用線程池提升了并行查詢數據庫的性能。

    其實,連接池和線程池你并不陌生,不過你可能對它們的原理和使用方式上還存在困惑或者 誤區(qū),我在面試時,就發(fā)現有很多的同學對線程池的基本使用方式都不了解。借用這節(jié)課,我想再次強調的重點是:

    池子的最大值和最小值的設置很重要,初期可以依據經驗來設置,后面還是需要根據實際 運行情況做調整

    池子中的對象需要在使用之前預先初始化完成,這叫做池子的預熱,比方說使用線程池時 就需要預先初始化所有的核心線程。如果池子未經過預熱可能會導致系統(tǒng)重啟后產生比較多的慢請求

    池化技術核心是一種空間換時間優(yōu)化方法的實踐,所以要關注空間占用情況,避免出現空間過度使用出現內存泄露或者頻繁垃圾回收等問題

    超強干貨來襲 云風專訪:近40年碼齡,通宵達旦的技術人生

    總結

    以上是生活随笔為你收集整理的数据库性能优化—数据库连接池的全部內容,希望文章能夠幫你解決所遇到的問題。

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