建設(shè)一個網(wǎng)站的步驟有哪些網(wǎng)絡(luò)推廣公司怎么找客戶
目錄
1 池化技術(shù)
2 什么是數(shù)據(jù)庫連接池
3 為什么使用數(shù)據(jù)庫連接池
3.1 不使用連接池
3.2 使用連接池
3.3 長連接和連接池的區(qū)別
4 數(shù)據(jù)庫連接池運行機(jī)制
?5 連接池和線程池的關(guān)系
6 線程池設(shè)計要點
6.1 連接池設(shè)計邏輯
構(gòu)造函數(shù)?
初始化
請求獲取連接?
?歸還連接?
析構(gòu)連接池?
連接
6.2 mysql連接重連機(jī)制
問題
C/C++Linux服務(wù)器開發(fā)/后臺架構(gòu)師【零聲教育】-學(xué)習(xí)視頻教程-騰訊課堂
1 池化技術(shù)
????????池化技術(shù)能夠減少資源對象的創(chuàng)建次數(shù),提高程序的響應(yīng)性能,特別是在高并發(fā)下這種提高更加明顯。
????????使用池化技術(shù)緩存的資源對象有如下共同特點:
1. 對象創(chuàng)建時間長;
2. 對象創(chuàng)建需要大量資源;
3. 對象創(chuàng)建后可被重復(fù)使用
像常見的線程池、內(nèi)存池、連接池、對象池都具有以上的共同特點。
2 什么是數(shù)據(jù)庫連接池
????????定義:數(shù)據(jù)庫連接池(Connection pooling)是程序啟動時建立足夠的數(shù)據(jù)庫連接,并將這些連接組成一個連接池,由程序動態(tài)地對池中的連接進(jìn)行申請,使用,釋放。
????????大白話:創(chuàng)建數(shù)據(jù)庫連接是一個很耗時的操作,也容易對數(shù)據(jù)庫造成安全隱患。所以,在程序初始化的時候,集中創(chuàng)建多個數(shù)據(jù)庫連接,并把他們集中管理,供程序使用,可以保證較快的數(shù)據(jù)庫讀寫速度,還更加安全可靠。
????????這里講的數(shù)據(jù)庫,不單只是指Mysql,也同樣適用于Redis。
3 為什么使用數(shù)據(jù)庫連接池
1. 資源復(fù)用
????????由于數(shù)據(jù)庫連接得到復(fù)用,避免了頻繁的創(chuàng)建、釋放連接引起的性能開銷,在減少系統(tǒng)消耗的基礎(chǔ)上,另一方面也增進(jìn)了系統(tǒng)運行環(huán)境的平穩(wěn)性(減少內(nèi)存碎片以及數(shù)據(jù)庫臨時進(jìn)程/線程的數(shù)量)。
2. 更快的系統(tǒng)響應(yīng)速度
????????數(shù)據(jù)庫連接池在初始化過程中,往往已經(jīng)創(chuàng)建了若干數(shù)據(jù)庫連接置于池中備用。此時連接的初始化工作均已完成。對于業(yè)務(wù)請求處理而言,直接利用現(xiàn)有可用連接,避免了從數(shù)據(jù)庫連接初始化和釋放過程的開銷,從而縮減了系統(tǒng)整體響應(yīng)時間。
3. 統(tǒng)一的連接管理,避免數(shù)據(jù)庫連接泄露
????????在較為完備的數(shù)據(jù)庫連接池實現(xiàn)中,可根據(jù)預(yù)先的連接占用超時設(shè)定,強(qiáng)制收回被占用連接。從而避免了常規(guī)數(shù)據(jù)庫連接操作中可能出現(xiàn)的資源泄露。
3.1 不使用連接池
1. TCP建立連接的三次握手(客戶端與MySQL服務(wù)器的連接基于TCP協(xié)議)
2. MySQL認(rèn)證的三次握手
3. 真正的SQL執(zhí)行
4. MySQL的關(guān)閉
5. TCP的四次握手關(guān)閉
????????可以看到,為了執(zhí)行一條SQL,需要進(jìn)行TCP三次握手,Mysql認(rèn)證、Mysql關(guān)閉、TCP四次揮手等其他操作,執(zhí)行SQL操作在所有的操作占比非常低。
優(yōu)點:實現(xiàn)簡單 省了連接池的設(shè)計。
缺點:每一次發(fā)起SQL操作都經(jīng)歷TCP建立連接、數(shù)據(jù)庫用戶身份驗證、數(shù)據(jù)庫用戶登出、TCP斷開連接。
- 網(wǎng)絡(luò)IO較多
- 帶寬利用率低
- QPS較低
- 應(yīng)用頻繁低創(chuàng)建連接和關(guān)閉連接,導(dǎo)致臨時對象較多,帶來更多的內(nèi)存碎片
- 在關(guān)閉連接后,會出現(xiàn)大量TIME_WAIT 的TCP狀態(tài)(在2個MSL之后關(guān)閉)
3.2 使用連接池
????????在連接池初始化的時候,會按照需求建立N個連接,但是之后的訪問,都會從池里面取,復(fù)用初始化時創(chuàng)建的連接,直接執(zhí)行sql語句。
優(yōu)點:
1. 降低了網(wǎng)絡(luò)開銷
2. 連接復(fù)用,有效減少連接數(shù)。
3. 提升性能,避免頻繁的新建連接。新建連接的開銷比較大
4. 沒有TIME_WAIT狀態(tài)的問題
缺點:
- 設(shè)計較為復(fù)雜
3.3 長連接和連接池的區(qū)別
- 長連接是一些驅(qū)動、驅(qū)動框架、ORM工具的特性,由驅(qū)動來保持連接句柄的打開,以便后續(xù)的數(shù)據(jù)庫操作可以重用連接,從而減少數(shù)據(jù)庫的連接開銷。
- 而連接池是應(yīng)用服務(wù)器的組件,它可以通過參數(shù)來配置連接數(shù)、連接檢測、連接的生命周期等
- 連接池內(nèi)的連接,其實就是長連接。
4 數(shù)據(jù)庫連接池運行機(jī)制
1. 從連接池獲取或創(chuàng)建可用連接;
2. 使用完畢之后,把連接返回給連接池;
3. 在系統(tǒng)關(guān)閉前,斷開所有連接并釋放連接占用的系統(tǒng)資源;
?5 連接池和線程池的關(guān)系
連接池和線程池的區(qū)別
????????線程池:主動調(diào)用任務(wù)。當(dāng)任務(wù)隊列不為空的時候從隊列取任務(wù)取執(zhí)行。比如去銀行辦理業(yè)務(wù),窗口柜員是線程,多個窗口組成了線程池,柜員從排號隊列叫號執(zhí)行。
????????連接池:被動被任務(wù)使用。當(dāng)某任務(wù)需要操作數(shù)據(jù)庫時,只要從連接池中取出一個連接對象,當(dāng)任務(wù)使用完該連接對象后,將該連接對象放回到連接池中。如果連接池中沒有連接對象可以用,那么該任務(wù)就必須等待。比如去銀行用筆填單,筆是連接對象,我們要用筆的時候去取,用完了還回去。
連接池和線程池設(shè)置數(shù)量的關(guān)系
- 一般線程池線程數(shù)量和連接池連接對象數(shù)量一致;
- 一般線程執(zhí)行任務(wù)完畢的時候歸還連接對象;
6 線程池設(shè)計要點
使用連接池需要預(yù)先建立數(shù)據(jù)庫連接。
線程池設(shè)計思路:
1. 連接到數(shù)據(jù)庫,涉及到數(shù)據(jù)庫ip、端口、用戶名、密碼、數(shù)據(jù)庫名字等;
????????a.連接的操作,每個連接對象都是獨立的連接通道,它們是獨立的
????????b.配置最小連接數(shù)和最大連接數(shù)
2. 需要一個隊列管理他的連接,比如使用list;
3. 獲取連接對象:
4. 歸還連接對象;
5. 連接池的名字
6.1 連接池設(shè)計邏輯
構(gòu)造函數(shù)?
string m_pool_name; ? ? ? ? ? ? // 連接池名稱string m_db_server_ip; ? ? ? // 數(shù)據(jù)庫ipuint16_t m_db_server_port; ? // 數(shù)據(jù)庫端口string m_username; ? ? ? ? ? // 用戶名string m_password; ? ? ? ? ? // 用戶密碼string m_db_name; ? ? ? ? ? ?// db名稱int m_db_cur_conn_cnt; ? ? ? // 當(dāng)前啟用的連接數(shù)量int m_db_max_conn_cnt; ? ? ? // 最大連接數(shù)量CDBPool::CDBPool(const char *pool_name, const char *db_server_ip, uint16_t db_server_port,const char *username, const char *password, const char *db_name, int max_conn_cnt)
{m_pool_name = pool_name;m_db_server_ip = db_server_ip;m_db_server_port = db_server_port;m_username = username;m_password = password;m_db_name = db_name;m_db_max_conn_cnt = max_conn_cnt; ? ?//m_db_cur_conn_cnt = MIN_DB_CONN_CNT; // 最小連接數(shù)量
}
初始化
CDBConn::CDBConn(CDBPool *pPool)
{m_pDBPool = pPool;m_mysql = NULL;
}CDBConn::~CDBConn()
{if (m_mysql){mysql_close(m_mysql);}
}int CDBConn::Init()
{m_mysql = mysql_init(NULL); // mysql_標(biāo)準(zhǔn)的mysql c client對應(yīng)的apiif (!m_mysql){LOG_ERROR << "mysql_init failed";return 1;
}my_bool reconnect = true;mysql_options(m_mysql, MYSQL_OPT_RECONNECT, &reconnect); ? // 配合mysql_ping實現(xiàn)自動重連
mysql_options(m_mysql, MYSQL_SET_CHARSET_NAME, "utf8mb4"); // utf8mb4和utf8區(qū)別// ip 端口 用戶名 密碼 數(shù)據(jù)庫名if (!mysql_real_connect(m_mysql, m_pDBPool->GetDBServerIP(), m_pDBPool->GetUsername(), m_pDBPool->GetPasswrod(),m_pDBPool->GetDBName(), m_pDBPool->GetDBServerPort(), NULL, 0)){LOG_ERROR << "mysql_real_connect failed: " << mysql_error(m_mysql);return 2;}return 0;
}int CDBPool::Init()
{// 創(chuàng)建固定最小的連接數(shù)量for (int i = 0; i < m_db_cur_conn_cnt; i++){CDBConn *pDBConn = new CDBConn(this);int ret = pDBConn->Init();if (ret){delete pDBConn;return ret;}m_free_list.push_back(pDBConn);}// log_info("db pool: %s, size: %d\n", m_pool_name.c_str(), (int)m_free_list.size());return 0;
}
請求獲取連接?
/**TODO: 增加保護(hù)機(jī)制,把分配的連接加入另一個隊列,這樣獲取連接時,如果沒有空閑連接,*TODO: 檢查已經(jīng)分配的連接多久沒有返回,如果超過一定時間,則自動收回連接,放在用戶忘了調(diào)用釋放連接的接口* timeout_ms默認(rèn)為 0死等* timeout_ms >0 則為等待的時間*/
CDBConn *CDBPool::GetDBConn(const int timeout_ms)
{std::unique_lock<std::mutex> lock(m_mutex);if (m_abort_request){LOG_WARN << "have aboort";return NULL;}if (m_free_list.empty()) // 2 當(dāng)沒有連接可以用時{// 第一步先檢測 當(dāng)前連接數(shù)量是否達(dá)到最大的連接數(shù)量if (m_db_cur_conn_cnt >= m_db_max_conn_cnt) // 等待的邏輯{// 如果已經(jīng)到達(dá)了,看看是否需要超時等待if (timeout_ms <= 0) // 死等,直到有連接可以用 或者 連接池要退出{m_cond_var.wait(lock, [this] {// 當(dāng)前連接數(shù)量小于最大連接數(shù)量 或者請求釋放連接池時退出return (!m_free_list.empty()) | m_abort_request;});}else{// return如果返回 false,繼續(xù)wait(或者超時), ?如果返回true退出wait// 1.m_free_list不為空// 2.超時退出// 3. m_abort_request被置為true,要釋放整個連接池m_cond_var.wait_for(lock, std::chrono::milliseconds(timeout_ms), [this] {return (!m_free_list.empty()) | m_abort_request; });// 帶超時功能時還要判斷是否為空if (m_free_list.empty()) // 如果連接池還是沒有空閑則退出{return NULL;}}if (m_abort_request){LOG_WARN << "have abort";return NULL;}}else // 還沒有到最大連接則創(chuàng)建連接{CDBConn *pDBConn = new CDBConn(this); //新建連接int ret = pDBConn->Init();if (ret){LOG_ERROR << "Init DBConnecton failed";delete pDBConn;return NULL;}else{m_free_list.push_back(pDBConn);m_db_cur_conn_cnt++;// log_info("new db connection: %s, conn_cnt: %d\n", m_pool_name.c_str(), m_db_cur_conn_cnt);}}}CDBConn *pConn = m_free_list.front(); // 獲取連接m_free_list.pop_front(); ? ? ? ? ? ? ?// STL 吐出連接,從空閑隊列刪除return pConn;
}
?歸還連接?
void CDBPool::RelDBConn(CDBConn *pConn)
{std::lock_guard<std::mutex> lock(m_mutex);list<CDBConn *>::iterator it = m_free_list.begin();for (; it != m_free_list.end(); it++) // 避免重復(fù)歸還{if (*it == pConn){break;}
}if (it == m_free_list.end()){// m_used_list.remove(pConn);m_free_list.push_back(pConn);m_cond_var.notify_one(); // 通知取隊列}else{LOG_WARN << "RelDBConn failed";// 不再次回收連接}
}
析構(gòu)連接池?
// 釋放連接池
CDBPool::~CDBPool()
{std::lock_guard<std::mutex> lock(m_mutex);m_abort_request = true;m_cond_var.notify_all(); // 通知所有在等待的for (list<CDBConn *>::iterator it = m_free_list.begin(); it != m_free_list.end(); it++){CDBConn *pConn = *it;delete pConn;}m_free_list.clear();
}
連接
給每個池分配不同的名稱,那么就可以用多個連接池實現(xiàn)不同的業(yè)務(wù)。池名
6.2 mysql連接重連機(jī)制
1. 設(shè)置啟用(當(dāng)發(fā)現(xiàn)連接斷開時的)自動重連
my_bool reconnect = true;mysql_options(m_mysql, MYSQL_OPT_RECONNECT, &reconnect); // 配合mysql_ping實現(xiàn)自動重連
2. 檢測連接是否正常
int STDCALL mysql_ping(MYSQL *mysql);
描述:
????????檢查與服務(wù)端的連接是否正常。連接斷開時,如果自動重新連接功能未被禁用,則嘗試重新連接服務(wù)器。該函數(shù)可被客戶端用來檢測閑置許久以后,與服務(wù)端的連接是否關(guān)閉,如有需要,則重新連接。
返回值:
????????連接正常,返回0;如有錯誤發(fā)生,則返回非0值。返回非0值并不意味著服務(wù)器本身關(guān)閉掉,也有可能是網(wǎng)絡(luò)原因?qū)е戮W(wǎng)絡(luò)不通。
問題
4個連接池對象和4個線程使用4個連接池做同樣的事情嗎?還是區(qū)分每個線程做不同的事情。
答:連接池只是提供了連接對象,提供了一條連接通道,至于調(diào)用者要拿這個連接對象做什么業(yè)務(wù)是用調(diào)用者取決定的。出于業(yè)務(wù)解耦合的場景,也可以設(shè)置不同的線程池和不同的連接池應(yīng)對不同的業(yè)務(wù),比如即時通訊寫入聊天記錄和讀取聊天記錄采用不同的線程池和對象池。