程式類型: C++連接配接池抽象類
适用平台: Linux
适用資料庫: Mysql(親測可用), Redis(親測可用), Oracle(親測可用)
适合讀者: C/C++初學者學習交流使用
使用注意: 此類屬于C++抽象類, 必須被繼承後使用
簡單介紹: 這段時間開發伺服器涉及到redis和mysql資料庫連接配接池, 在網上翻閱資料基本上都是針對性的,比如針對mysql設計的mysql連接配接池, 針對redis設計的redis連接配接池, 自己想了想, 都是連接配接池, 為啥不能寫一個公用的方法呢 , 寫一個抽象類, 供其他資料庫連接配接池繼承, 這樣也就符合了C++的多态思想(這邊純屬裝個<B>, 高手請繞道)
思路介紹:
- 首先有連接配接池,必須先創造一個由若幹個連接配接組成的連接配接池來管理, 并指定最大和最小連接配接數
- 連接配接池創造好了,當然要提供給外部擷取和返還的方法
- 在外部調用過多時, 不夠的需要創造新的, 這時候就會出現調用完成反還後面用不到, 連接配接池内部出現太多的空閑連接配接白消耗資源,這時候就需要管理者來管理這些備援的連接配接
- 創造管理者子線程,用來定期監控空閑線程和定期清理那些未知原因導緻的連接配接與資料庫斷開的連接配接
功能介紹:
1. 需要子類重寫的受保護的純虛函數(protected)
//建立一個連接配接:純虛函數, 子類必須要實作
virtual void * createLink() = 0;
//銷毀指定的連接配接:純虛函數
virtual void v_destroyLink(void *link) = 0;
//檢測連接配接是否可以用:通過一個不影響資料庫的請求檢測,比如Mysql的mysql_ping(MYSQL* con)
virtual bool isUsed(void *link) = 0;
[注意] 獲得連接配接和反還連接配接用的都是void*, 此處是因為目前類為抽象類, 不是具體的資料庫連接配接池, 所有的連接配接對象類型都未知, 調用如下:
//比如這是Redis的連接配接池僞代碼
//執行個體繼承連接配接池的Redis連接配接池對象
RedisPool redis;
//将獲得的redis連接配接強轉成Redis對象
Redis *red = (Redis*)redis.getLink();
2. 介紹公共成員函數
//構造函數
LinkPool();
//獲得錯誤碼
LinkPool::ErrOpt errNo();
//初始化連接配接池
//(minLinkNum:連接配接池最小連接配接數, maxLinkNum:連接配接池最大連接配接數,checkFreeLinkSecs:檢查空閑連結秒數)
void createLinkPool(int minLinkNum, int maxLinkNum, int checkFreeLinkSecs = 10);
//銷毀一個連接配接
void destroyLink(void *link);
//獲得一個連接配接
void * getLink();
//返還一個連接配接到連接配接池
void backLink(void *link);
//銷毀連接配接池:析構時自動調用
void destroyLinkPool();
//虛析構:目的是為了析構子類時,析構父類
virtual ~LinkPool();
3. 介紹私有成員變量
//線程鎖:用來多線程操作的安全,使用靜态友善調用
static pthread_rwlock_t *g_lock;
//連接配接池清單:目前連接配接池可用連接配接的連結清單
list<void*> m_linkList;
//忙碌的連接配接清單:已被取出的連接配接連結清單
list<void*> m_busyLinkList;
//最大連接配接數:連接配接池容納的最大連接配接數
int m_maxLinkNum = 0;
//最小連接配接數:連接配接池容納的最小連接配接數
int m_minLinkNum = 0;
//檢查連接配接秒數:檢查是否存在空閑連接配接的頻率
int m_checkFreeLinkSecs = 0;
//存管理線程tid:管理者子線程id,用來銷毀線程池時進行回收
pthread_t m_adjust_tid;
//忙碌連接配接數:目前忙碌的連接配接數(已被取出的)
int m_busyLinkNum = 0;
//空閑連結數:目前空閑的連接配接數(未被取出的)
int m_freeLinkNum = 0;
//錯誤碼:出錯時進行指派(枚舉)
LinkPool::ErrOpt m_errno;
//是否銷毀:是否銷毀,管理者線程執行無限循環的判斷條件, 銷毀則自動結束管理者線程
bool m_isDestory = false;
4. 錯誤類型(enum)
enum ErrOpt {
NoErr = 0, //沒有錯誤
ErrBusying = -100, //達到最大連接配接數(繁忙)
ErrConnectFailed, //連接配接失敗
};
5. 私有成員函數
//建立多個連結
bool createLinks(int n);
//銷毀一個連接配接:是不是似曾相識, 這是内部使用的,内部實作不一樣的
void priDestroyLink(void *link = nullptr);
//判斷空閑連接配接的管理者線程回調:管理者子線程的線程回調函數,格式不能變動
static void *adjustFreeLinck(void *linkPool);
所有的頭檔案都在這了,是不是很簡單呢, 當然有些漏掉的功能, 我寫的隻是最基本的功能, 大家可以去自動擴充, 具體的實作我就不一一介紹了, 下面是具體的代碼部分大家到這兒複制去用:
LinkPool.h
/**
*@name 連接配接池基類
*@brief 提供給連結池繼承的基類
*@see 是一個抽象類
*@author 打工的小兵
*@date 2019-03-23
*/
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <list>
#include <string.h>
#include <string>
#include <pthread.h>
using namespace std;
#define LINK_POOL_DEL_LINKS 10 //空閑連接配接一次性删除的最大個數
#define LINK_POOL_CHECK_KEEP_ALIVE_SECS 3 //檢測保持連接配接的秒數(一定秒數檢測一次是否斷開連接配接)
class LinkPool
{
public:
enum ErrOpt {
NoErr = 0, //沒有錯誤
ErrBusying = -100, //達到最大連接配接數(繁忙)
ErrConnectFailed, //連接配接失敗
};
private:
static pthread_rwlock_t *g_lock; //線程鎖
list<void*> m_linkList; //連接配接池清單
list<void*> m_busyLinkList; //忙碌的連接配接清單(此處為了嚴謹性, 防止意外終止子程序導緻的忙碌連接配接無法被回收)
int m_maxLinkNum = 0; //最大連接配接數
int m_minLinkNum = 0; //最小連接配接數
int m_checkFreeLinkSecs = 0; //檢查連接配接秒數
pthread_t m_adjust_tid; //存管理線程tid
bool m_isDestory = false; //是否銷毀
int m_busyLinkNum = 0; //忙碌連接配接數
int m_freeLinkNum = 0; //空閑連結數
LinkPool::ErrOpt m_errno; //錯誤碼
public:
/**
*@name 構造函數
*@brief 構造函數
*@param 函數參數
*@see 注意事項
*@return 傳回值
*@date 2019-03-23
*/
LinkPool();
/**
*@name 獲得錯誤碼
*@brief 獲得錯誤碼
*@param 函數參數
*@see 注意事項
*@return LinkPool::ErrOpt枚舉:0表示無錯誤
*@date 2019-03-23
*/
LinkPool::ErrOpt errNo();
/**
*@name 建立連結池
*@brief 初始化連接配接池
*@param minLinkNum:連接配接池最小連接配接數, maxLinkNum:連接配接池最大連接配接數,checkFreeLinkSecs:檢查空閑連結秒數
*@see 構造後必須調用
*@return void
*@date 2019-03-23
*/
void createLinkPool(int minLinkNum, int maxLinkNum, int checkFreeLinkSecs = 10);
/**
*@name 銷毀連結
*@brief 銷毀一個連接配接
*@param link[null]:從連接配接池中空閑的連結中銷毀一個(内部使用), link[!null]:銷毀指定的連接配接, isNeedLock:是否需要加鎖(true:是)
*@return void
*@date 2019-03-23
*/
void destroyLink(void *link);
/**
*@name 獲得一個連接配接
*@brief 獲得一個連接配接
*@param 函數參數
*@see 注意事項
*@return void*:連接配接指針
*@date 2019-03-23
*/
void * getLink();
/**
*@name 返還一個連接配接到連接配接池
*@brief 返還一個連接配接到連接配接池
*@param link:連接配接指針
*@see 注意事項
*@return void
*@date 2019-03-23
*/
void backLink(void *link);
/**
*@name 銷毀連接配接池
*@brief 銷毀連接配接池
*@see 析構函數自動調用
*@return void
*@date 2019-03-23
*/
void destroyLinkPool();
virtual ~LinkPool();
protected:
/**
*@name 建立連接配接
*@brief 建立一個連接配接
*@param 函數參數
*@see 純虛函數
*@return void*[null]:建立失敗, void*[!null]:建立成功
*@date 2019-03-23
*/
virtual void * createLink() = 0;
/**
*@name 銷毀一個連接配接
*@brief 銷毀指定的連結
*@param link:連結指針
*@see 子類純虛函數,直接銷毀給定對象,無需加鎖
*@return void
*@date 2019-03-23
*/
virtual void v_destroyLink(void *link) = 0;
/**
*@name 檢查連接配接是否可用
*@brief 檢測連接配接是否可以用
*@param link:連接配接指針
*@see 注意事項
*@return bool[true]:連接配接正常, bool[false]:連接配接不正常
*@date 2019-03-23
*/
virtual bool isUsed(void *link) = 0;
private:
/**
*@name 建立多個連結
*@brief 建立多個連接配接
*@param n:連接配接數
*@see 需要外部加鎖保護
*@return bool[true]:建立成功, bool[false]:建立失敗,設定errno
*@date 2019-03-23
*/
bool createLinks(int n);
/**
*@name 銷毀一個連接配接
*@brief 銷毀一個連接配接
*@param link:指定連接配接,預設值,則随機銷毀一個
*@see 當參數link非空時, 預設不從m_linkList中取出(提供給管理者使用), 如需取出,從外部取出
*@return void
*@date 2019-03-24
*/
void priDestroyLink(void *link = nullptr);
/**
*@name 判斷空閑連接配接
*@brief 判斷空閑連接配接的管理者線程回調
*@param linkPool:回調參數,目前連接配接池
*@see 回調函數,建立線程調用
*@return void
*@date 2019-03-23
*/
static void *adjustFreeLinck(void *linkPool);
};
LinkPool.cpp
#include "LinkPool.h"
#include <unistd.h>
#include <iostream>
pthread_rwlock_t *LinkPool::g_lock = nullptr;
LinkPool::LinkPool()
{
g_lock = new pthread_rwlock_t;
pthread_rwlock_init(g_lock, nullptr);
m_errno = NoErr;
}
/*獲得錯誤碼*/
LinkPool::ErrOpt LinkPool::errNo()
{
pthread_rwlock_rdlock(g_lock);
LinkPool::ErrOpt err = m_errno;
pthread_rwlock_unlock(g_lock);
return err;
}
/*初始化連接配接池*/
void LinkPool::createLinkPool(int minLinkNum, int maxLinkNum, int checkFreeLinkSecs)
{
m_checkFreeLinkSecs = checkFreeLinkSecs;
m_minLinkNum = minLinkNum;
m_maxLinkNum = maxLinkNum;
//建立連接配接
pthread_rwlock_wrlock(g_lock);
createLinks(minLinkNum);
pthread_rwlock_unlock(g_lock);
//加入管理者線程
pthread_create(&m_adjust_tid, NULL, adjustFreeLinck, (void*)this);
}
/*銷毀一個連接配接*/
void LinkPool::priDestroyLink(void * link)
{
void *links = link;
if (!link) {
links = m_linkList.front();
m_linkList.pop_front();
}
v_destroyLink(links);
cout << "銷毀的連接配接是:[" << m_linkList.front() << "]" << endl;
m_freeLinkNum--;
}
/*銷毀一個連接配接*/
void LinkPool::destroyLink(void * link)
{
if (link) {
//外部調用,必須加鎖
pthread_rwlock_wrlock(g_lock);
v_destroyLink(link);
//忙碌連接配接數-1:因為外部調用的是已經獲得連接配接
m_busyLinkList.remove(link);
m_busyLinkNum--;
pthread_rwlock_unlock(g_lock);
}
}
/*獲得一個連接配接*/
void * LinkPool::getLink()
{
pthread_rwlock_wrlock(g_lock);
if (m_linkList.empty() && !createLinks(1)) {
pthread_rwlock_unlock(g_lock);
return nullptr;
}
auto link = m_linkList.front();
m_linkList.pop_front();
m_busyLinkList.push_back(link);
--m_freeLinkNum;
++m_busyLinkNum;
cout << "獲得一個連接配接:[" << link << "], 目前空閑連接配接數:" << m_freeLinkNum << " 目前忙碌連接配接數:" << m_busyLinkNum << endl;
pthread_rwlock_unlock(g_lock);
return link;
}
/*返還一個連接配接到連接配接池*/
void LinkPool::backLink(void * link)
{
pthread_rwlock_wrlock(g_lock);
m_linkList.push_back(link);
m_busyLinkList.remove(link);
++m_freeLinkNum;
--m_busyLinkNum;
cout << "反還一個連接配接:[" << link << "], 目前空閑連接配接數:" << m_freeLinkNum << " 目前忙碌連接配接數:" << m_busyLinkNum << endl;
pthread_rwlock_unlock(g_lock);
}
/*銷毀連接配接池*/
void LinkPool::destroyLinkPool()
{
pthread_rwlock_wrlock(g_lock);
m_isDestory = true;
while (m_linkList.size() > 0) {
priDestroyLink();
}
pthread_rwlock_unlock(g_lock);
//銷毀忙碌未返還的連接配接
while (m_busyLinkList.size() > 0) {
destroyLink(m_busyLinkList.front());
}
//回收管理者線程
pthread_join(m_adjust_tid, NULL);
//銷毀鎖
pthread_rwlock_destroy(g_lock);
delete g_lock;
}
LinkPool::~LinkPool()
{
destroyLinkPool();
}
/*建立多個連接配接*/
bool LinkPool::createLinks(int n)
{
int i;
if (m_freeLinkNum + m_busyLinkNum + 1 > m_maxLinkNum) {
cout << "達到最大連結!" << endl;
m_errno = ErrBusying;
return false;
}
for (i = 0; i < n; ++i) {
auto link = createLink();
if (link) {
m_linkList.push_back(link);
++m_freeLinkNum;
cout << "成功創造一個新連接配接:[" << link << "] 目前空閑連接配接數為:" << m_freeLinkNum << endl;
}
else {
cout << "建立失敗!" << endl;
m_errno = ErrConnectFailed;
}
}
if (NoErr == m_errno) {
return true;
}
return false;
}
/*管理者執行函數:判斷空閑*/
void * LinkPool::adjustFreeLinck(void * linkPool)
{
LinkPool *pool = (LinkPool*)linkPool;
int i;
int checkLinkSecs = 0;
while (!pool->m_isDestory) {
sleep(pool->m_checkFreeLinkSecs);
cout << "管理者開始檢查!.." << endl;
pthread_rwlock_rdlock(g_lock);
int busyNum = pool->m_busyLinkNum;
int freeNum = pool->m_freeLinkNum;
pthread_rwlock_unlock(g_lock);
//此處計算需要銷毀的連結數, 不能銷毀之後剩餘的總連接配接不足最小連接配接數
int destroyNum = freeNum - pool->m_minLinkNum + busyNum;
if (destroyNum >= LINK_POOL_DEL_LINKS) {
destroyNum = LINK_POOL_DEL_LINKS;
}
if (busyNum * 2 < freeNum && freeNum > pool->m_minLinkNum) {
pthread_rwlock_wrlock(g_lock);
cout << "管理者開始銷空閑連接配接, 銷毀連接配接數為:" << destroyNum << endl;
for (i = 0; i < destroyNum; ++i) {
pool->priDestroyLink();
}
pthread_rwlock_unlock(g_lock);
}
//檢查連接配接池連接配接數是否小于最小連接配接數
if (busyNum + freeNum < pool->m_minLinkNum) {
pthread_rwlock_wrlock(g_lock);
pool->createLinks(pool->m_minLinkNum - busyNum - freeNum);
pthread_rwlock_unlock(g_lock);
}
//檢查連接配接是否可用
checkLinkSecs += pool->m_checkFreeLinkSecs;
if (checkLinkSecs >= LINK_POOL_CHECK_KEEP_ALIVE_SECS) {
checkLinkSecs = 0;
pthread_rwlock_wrlock(g_lock);
//周遊删除法
for (list<void*>::iterator t = pool->m_linkList.begin(); t != pool->m_linkList.end();) {
if (!pool->isUsed(*t)) {
pool->priDestroyLink(*t);
pool->m_linkList.erase(t++);
}
else {
t++;
}
}
pthread_rwlock_unlock(g_lock);
}
}
return nullptr;
}
大家自行去除不需要的測試的内容, 這隻是一個基類, 需要對應資料庫的派生類, 關注本文的後續更新!
大家發現BUG的請留言, 這樣好改程序式, 作為分享出來的回報吧