最近一直在學(xué)習(xí)研究swoole,發(fā)現(xiàn)這個框架真是PHP的神作,swoole + php7 性能可以說是能與java媲美了,但是JAVA在處理高并發(fā)時還是比PHP更有優(yōu)勢,最近一直在想PHP是不是能像JAVA一樣實(shí)現(xiàn)數(shù)據(jù)庫連接池。
一般的fpm PHP應(yīng)用程序都是使用的數(shù)據(jù)庫短連接,每個php-fpm的請求都會新建一個mysql數(shù)據(jù)庫連接,那么在并發(fā)數(shù)很高的情況mysql的連接數(shù)很快就會用完,從而導(dǎo)致導(dǎo)致MySQL服務(wù)器崩潰。即使使用了mysql pconnet 但是又會導(dǎo)致很多無用的sleep連接。如何才能有效的避免這個問題 讓PHP也能開發(fā)像java一樣的高并發(fā)應(yīng)用呢。
連接池是可以有效降低MySQL-Server負(fù)載的。原理是 連接池使用一個共享資源的模式,如并發(fā)100個請求,實(shí)際上并不是每個請求的所有時間都在執(zhí)行SQL查詢。這樣100個請求,共享20個MySQL連接就可以滿足需求了。當(dāng)一個請求操作完數(shù)據(jù)庫后,開始進(jìn)入模板渲染等其它邏輯流程,這時就會釋放數(shù)據(jù)庫連接給其他的請求使用。
連接池僅在超大型應(yīng)用中才有價(jià)值。普通的應(yīng)用采用MySQL長連接方案,每個php-fpm創(chuàng)建一個MySQL連接,每臺機(jī)器開啟100個php-fpm進(jìn)程。如果有10臺機(jī)器,每臺機(jī)器并發(fā)的請求為100。實(shí)際上只需要創(chuàng)建1000個MySQL連接就能滿足需求,數(shù)據(jù)庫的壓力并不大。即使有100臺機(jī)器,硬件配置好的存儲服務(wù)器依然可以承受。
達(dá)到數(shù)百或者數(shù)千臺應(yīng)用服務(wù)器時,MySQL服務(wù)器就需要維持十萬級的連接。這時數(shù)據(jù)庫的壓力就會非常大了。連接池技術(shù)就可以派上用場了,可以大大降低數(shù)據(jù)庫連接數(shù)。
下面是我基于swoole擴(kuò)展實(shí)現(xiàn)的一個mysql數(shù)據(jù)庫連接池類,其基本原理是使用mysql長連接,使用空閑隊(duì)列分配數(shù)據(jù)庫連接,當(dāng)沒有空閑資源時生成新的數(shù)據(jù)庫連接,設(shè)置一個最大連接數(shù),和一個最小連接數(shù),當(dāng)超過設(shè)置的最大連接數(shù),不予申請新的數(shù)據(jù)庫連接,當(dāng)sleep的連接數(shù)超過最小連接數(shù)時,釋放多余的mysql連接。并且程序中也實(shí)現(xiàn)了mysql長連接的斷線重連,下面給出連接池程序源碼歡迎同學(xué)們吐槽試用,報(bào)bug。
<?php
namespace frame\base;
use frame\log\Log;
class MysqlPool
{
const MAX_CONN = 100;
const TIME_OUT = 1800;
const MIN_CONN = 10; //最小連接
const RECOVERY_TIME_INTERVAL = 30000; //定時回收毫秒
public static $working_pool; //工作連接池
public static $free_queue; //空閑連接資源隊(duì)列
public static $close_queue; //已關(guān)閉連接資源隊(duì)列
public static $config;
public static $timer_start = false;
/**
* [init 連接池初始化 支持多個數(shù)據(jù)庫連接池]
* @param $connkey 連接關(guān)鍵字,可以實(shí)現(xiàn)多個不同的數(shù)據(jù)庫連接
* @param $argv [description] 配置參數(shù)
*/
public static function init($connkey, $argv)
{
if (empty(self::$config[$connkey]['is_init'])) {
Log::debug(__METHOD__ . " init start ");
self::$config[$connkey]['max'] = $argv['max'];
self::$config[$connkey]['min'] = $argv['min'];
self::$config[$connkey]['is_init'] = true;
self::$working_pool[$connkey] = array();
self::$free_queue[$connkey] = new \SplQueue();
self::$close_queue[$connkey] = new \SplQueue();
}
}
/**
* 開啟定時任務(wù)
* @param $connkey
* @param $argv
*/
public static function start($connkey, $argv)
{
if (!self::$timer_start) {
Log::debug(__METHOD__ . " schedule ");
//定時更新過期資源
//self::schedule($connkey, $argv);
//定時回收數(shù)據(jù)庫連接資源
self::recovery($connkey, $argv);
self::$timer_start = true;
}
}
/**
* 獲取連接資源
* @param $connkey
* @param $argv
* @return array
*/
public static function getResource($connkey, $argv)
{
if(empty($argv['max'])) $argv['max'] = self::MAX_CONN;
if(empty($argv['min'])) $argv['min'] = self::MIN_CONN;
if(empty($argv['timeout'])) $argv['timeout'] = self::TIME_OUT;
self::init($connkey, $argv);
self::start($connkey, $argv);
if (!self::$free_queue[$connkey]->isEmpty()) {
//現(xiàn)有資源可處于空閑狀態(tài)
$key = self::$free_queue[$connkey]->dequeue();
Log::debug(__METHOD__ . " free queue key == $key ", __CLASS__);
return array(
'r' => 0,
'key' => $key,
'data' => self::update($connkey, $key, $argv),
);
} elseif (count(self::$working_pool[$connkey]) < self::$config[$connkey]['max']) {
Log::debug(__METHOD__ . " below max, current count:" . count(self::$working_pool[$connkey]), __CLASS__);
if(self::$close_queue[$connkey]->isEmpty()) {
$key = count(self::$working_pool[$connkey]);
}
else {
$key = self::$close_queue[$connkey]->dequeue();
}
//當(dāng)前池可以再添加資源用于分配
$resource = self::product($connkey, $argv);
//product失敗
if(!$resource) {
Log::info('product resource error:' . $connkey . $key);
return array('r' => 1);
}
self::$working_pool[$connkey][$key] = $resource;
return array(
'r' => 0,
'key' => $key,
'data' => self::$working_pool[$connkey][$key]['obj'],
);
} else {
Log::error(__METHOD__ . " no resource can apply ", __CLASS__);
return array('r' => 1);
}
}
/**
* [freeResource 釋放資源]
* @param $connkey
* @param $key
*/
public static function freeResource($connkey, $key)
{
Log::debug(__METHOD__ . " key == $key", __CLASS__);
self::$free_queue[$connkey]->enqueue($key);
self::$working_pool[$connkey][$key]['status'] = 0;
}
/**
* [schedule 定時調(diào)度 釋放過期資源]
* @param $connkey
* @param $argv
*/
public static function schedule($connkey, $argv)
{
Log::debug(__METHOD__ . ' schedule start:' . $argv['timeout'] . 's');
swoole_timer_tick($argv['timeout'] * 1000, function() use($argv) {
Log::debug('schedule timer tick start');
foreach (self::$working_pool as $connkey => $pool_data) {
foreach ($pool_data as $key => $data) {
//當(dāng)前連接已超時
if($data['status'] != 0 && $data['lifetime'] < microtime(true)) {
//釋放資源
self::freeResource($connkey, $key);
}
}
}
});
}
/**
* 定時回收多余空閑連接資源
* @param string $connkey
* @param array $argv
*/
public static function recovery($connkey, $argv)
{
Log::debug(__METHOD__ . ' recovery start:' . self::RECOVERY_TIME_INTERVAL);
swoole_timer_tick(self::RECOVERY_TIME_INTERVAL, function() use($argv) {
Log::debug('recovery timer tick start');
foreach (self::$free_queue as $connkey => $queue) {
if($queue->isEmpty() || $queue->count() <= $argv['min'])
continue;
//空閑資源超過最小連接,關(guān)閉多余的數(shù)據(jù)庫連接
for($i = $argv['min']; $i < $queue->count();) {
$key = $queue->dequeue();
//關(guān)閉數(shù)據(jù)庫連接
self::$working_pool[$connkey][$key]['obj']->close();
self::$close_queue[$connkey]->enqueue($key);
unset(self::$working_pool[$connkey][$key]);
Log::debug(__METHOD__ . ' key' . $key . ' queue count:' . $queue->count() . ' connect number:' . count(self::$working_pool[$connkey]));
}
}
});
}
/**
* [product 生產(chǎn)資源]
* @param $connkey
* @param $argv
* @return mixed
*/
private static function product($connkey, $argv)
{
//防止并發(fā)出現(xiàn)已超過連接數(shù)
if(count(self::$working_pool[$connkey]) >= self::$config[$connkey]['max'])
return false;
$resource = $argv['db']->connect($argv['config']);
if(!$resource) return false;
return array(
'obj' => $resource, //實(shí)例
'lifetime' => microtime(true) + ((float) $argv['timeout']), //生命期
'status' => 1, //狀態(tài) 1 在用 0 空閑
);
}
/**
* [update 更新資源]
* @param string $connkey
* @param string $key
* @param array $argv
* @return array
*/
private static function update($connkey, $key, $argv)
{
self::$working_pool[$connkey][$key]['status'] = 1;
self::$working_pool[$connkey][$key]['lifetime'] = microtime(true) + ((float) $argv['timeout']);
return self::$working_pool[$connkey][$key]['obj'];
}
/**
* 更新數(shù)據(jù)庫連接
* @param string $connkey
* @param string $key
* @param array $argv
* @return array
*/
public static function updateConnect($connkey, $key, $argv)
{
//更新資源
$argv['db']->close();
$resource = $argv['db']->connect($argv['config']);
self::$working_pool[$connkey][$key]['obj'] = $resource;
self::$working_pool[$connkey][$key]['lifetime'] = microtime(true) + ((float) $argv['timeout']);
Log::info('更新working pool key:' . $connkey . $key);
return $resource;
}
}
更完整的框架源碼請查閱我的github項(xiàng)目地址 https://github.com/jack15083/sample-swoole