首頁 >後端開發 >php教程 >CodeIgniter實作讀寫分離的方法

CodeIgniter實作讀寫分離的方法

不言
不言原創
2018-06-19 16:59:101312瀏覽

這篇文章主要介紹了CodeIgniter讀寫分離實現方法,結合實例形式較為詳細的分析了CodeIgniter讀寫分離的相關配置與功能實現技巧,需要的朋友可以參考下

本文實例講述了CodeIgniter讀寫分離實作方法。分享給大家供大家參考,具體如下:

目前伺服器只做了主從,未配置讀寫分離,讀寫分離的功能就只有交給程式來實現,這裡主要談談Codeigniter怎麼實現讀寫分離,並且需要滿足以下兩點:

1、讀寫分離對開發應該透明。

網路上有方案透過手動load多個DB來實現讀寫分離,這樣的分離跟業務關聯太緊,增加了開發難度也不利於維護,我們要做的是預設讀重庫,寫則寫主庫,讀寫分離對開發者透明

2、配置簡單。

保留現有的配置方式,透過增加一個陣列來配置讀寫分離,不影響原有使用方式。

思路

1、要實現讀寫分離最簡單的思路就是在最終執行查詢的地方根據查詢語句判斷是插入主庫還是讀取從庫,所以需要找出該函數。

2、應該只連接一次資料庫,下次操作該連結應可重複使用。也就是連一次重庫後所有的讀取操作都可用,不需再次連接,主庫同理。所以我們可以將連結放在CI超級物件中。

3、主從的判斷是根據最終執行的SQL語句來判斷的,所以資料庫設定中的自動連結autoinit參數就不用設定為true了,如果預設連接了而又不需要操作該函式庫就浪費資源了。

4、模型中可以使用$this->db來直接操作查詢,不需要其他調整。

5、不直接修改system下的文件

實作讀寫分離

#CI的DB類別固定為讀取system下的文件,我們可以透過適當的重寫來實現。首先是Loader.php,其中的database方法用來載入資料庫對象,固定引用了system/database/DB.php文件,我們判斷下是否存在自訂DB.php文件,存在則引入。

重寫Loader.php

public function database($params = '', $return = FALSE, $active_record = NULL)
{
  $CI =& get_instance();
  if (class_exists('CI_DB') AND $return == FALSE AND $active_record == NULL AND isset($CI->db) AND is_object($CI->db)) {
    return FALSE;
  }
  if(file_exists(APPPATH.'core/database/DB.php')) {
    require_once(APPPATH.'core/database/DB.php');
  } else {
    require_once(BASEPATH.'database/DB.php');
  }
  if ($return === TRUE) {
    return DB($params, $active_record);
  }
  $CI->db = '';
  $CI->db =& DB($params, $active_record);
}
/* End of file MY_Loader.php */
/* Location: ./application/core/MY_Loader.php */

接著我們在application/core下建立database/DB.php,該檔案只有一個DB方法,用來讀取設定檔並進行初始化工作。同樣有兩處地方需要重寫:

重寫DB.php

#
//DB_driver.php为所有驱动方式的父类,最终执行查询的方法在该文件中
//第一处修改为判断自定义的DB_driver.php是否存在,存在则引入
if(file_exists(APPPATH.'core/database/DB_driver.php')) {
  require_once(APPPATH.'core/database/DB_driver.php');
} else {
  require_once(BASEPATH.'database/DB_driver.php');
}
//第二处 $params['dbdriver'].'_driver.php' 该文件可不调整,实际未修改该文件,为了方便调试也加了
//mysql驱动对应system/database/drivers/mysql/mysql_driver.php,mysql的最后执行方法在这里,
//包括数据库打开和关闭、查询等,可以该文件增加相应日志查看读写分离是否有效
if(file_exists(APPPATH.'core/database/drivers/'.$params['dbdriver'].'/'.$params['dbdriver'].'_driver.php')) {
  require_once(APPPATH.'core/database/drivers/'.$params['dbdriver'].'/'.$params['dbdriver'].'_driver.php');
} else {
  require_once(BASEPATH.'database/drivers/'.$params['dbdriver'].'/'.$params['dbdriver'].'_driver.php');
}
//将当前group name赋值给param,方便判断
$params['group_name'] = $active_group;
 
/* End of file DB.php */
/* Location: ./application/core/database/DB.php */

##整個DB.php調整的基本上也是檔案的引入,group name的引入是為了方便後面的判斷, 不引入則可以透過主機、資料庫名稱這些來設定。如果想要強制關閉autoint,可以在DB.php中刪除下面這段:

if ($DB->autoinit == TRUE)
{
  $DB->initialize();
}

接下來就是最核心的地方。根據查詢語句實現讀寫分離。

DB_driver.php中的simple_query方法可以理解為最後執行SQL語句的方法,我們可以在這裡進行資料庫連結的判斷。

重寫DB_driver.php

//增加属性,表示当前组
var $active_group;
//增加属性,使用强制使用主库
var $db_force_master;
//该方法为执行查询的必经之地,我们可以在这里根据类型判断使用哪个链接。
function simple_query($sql)
{
  //load_db_proxy_setting方法这里写在helper中,也可以直接写在该类中,写在helper中则需要在自动加载中加载该helper
  //该方法的作用是根据当前链接group name 和sql读写类型,以及是否强制使用主库判断使用哪个链接。使用主库 OR 重库?
  //主重库的负载均衡,单点故障都可以在这里考虑。也就是根据3个参数返回一个可用的配置数组。
  $proxy_setting = load_db_proxy_setting($this->group_name, $this->is_write_type($sql), $this->db_force_master);
  if(is_array($proxy_setting) && ! empty($proxy_setting)) {
    $proxy_setting_key = key($proxy_setting);
    $this->group_name = $proxy_setting_key;
    //将当前配置重新赋值给类的属性,如果database.php配置的是DSN字符串,则需要在load_db_proxy_setting中做处理
    foreach($proxy_setting[$proxy_setting_key] as $key => $val) {
      $this->$key = $val;
    }
    //定义链接ID为conn_前缀
    $proxy_conn_id = 'conn_'.$proxy_setting_key;
    $CI = & get_instance();
    //赋值给CI超级对象或者直接从CI超级对象中读取
    if(isset($CI->$proxy_conn_id) && is_resource($CI->$proxy_conn_id)) {
      $this->conn_id = $CI->$proxy_conn_id;
    } else {
      $this->conn_id = false;
      $this->initialize();
      $CI->$proxy_conn_id = $this->conn_id;
    }
    //强制只一次有效,下次查询失效,防止一直强制主库
    $this->reset_force_master();
  }
  if ( ! $this->conn_id)
  {
    $this->initialize();
  }
  return $this->_execute($sql);
}
//某些情况会强制使用主库,先执行该方法即可
public function force_master()
{
  $this->db_force_master = TRUE;
}
public function reset_force_master()
{
  $this->db_force_master = FALSE;
}
/* End of file DB_driver.php */
/* Location: ./application/core/database/DB_driver.php */

到這裡讀寫分離即基本實作了,但做事情得善始善終,連結的資料庫物件需要關閉,可以在公用控制器中執行完畢後關掉連線。

DB_driver.php中也有close方法,可以考慮下是否可以在該方法中關閉?這裡認為是不行的。

關閉資料庫連結

class MY_Controller extends CI_Controller 
{
  public function __construct() 
  {
    parent::__construct();
    $this->load->service('common/helper_service', NULL, 'helper');
    //下面这段为关闭CI超级对象中的数据库对象和数据库链接,db的对象Codeigniter.php中会关闭
    register_shutdown_function(function(){
      foreach(get_object_vars($this) as $key => $val) {
        if(substr($key, 0, 3) == 'db_' && is_object($this->{$key}) && method_exists($this->{$key}, 'close')) {
          $this->{$key}->close();
        }
        if(substr($key, 0, 5) == 'conn_' && is_resource($this->{$key})) {
          $this->db->_close($val);
          unset($this->{$key});
        }
      }
    });
  }
}
/* End of file MY_Controller.php */
/* Location: ./application/core/MY_Controller.php */

模型中的使用,為了讓每個model中都可以使用$this ->db,以及不多次連接資料庫,這裡也是將連結放在CI超級物件中。這裡就算不讀寫分離也可以這麼處理,可以很方便的連接多個DB,具體的model要使用其他函式庫只需要在建構函式中傳入group name即可。

模型調整

public function __construct($group_name = '')
{
  parent::__construct();
  $this->initDb($group_name);
}
private function initDb($group_name = '')
{
  $db_conn_name = $this->getDbName($group_name);
  $CI = & get_instance();
  if(isset($CI->{$db_conn_name}) && is_object($CI->{$db_conn_name})) {
    $this->db = $CI->{$db_conn_name};
  } else {
    $CI->{$db_conn_name} = $this->db = $this->load->database($group_name, TRUE);
  }
}
private function getDbName($group_name = '')
{
  if($group_name == '') {
    $db_conn_name = 'db';
  } else {
    $db_conn_name = 'db_'.$group_name;
  }
  return $db_conn_name;
}
/* End of file MY_Model.php */
/* Location: ./application/core/MY_Model.php */

#最後的資料庫配置方式,只需要在原有的基礎上配置一個陣列即可。是使用雙主還是一主多從就看這裡的配置方式。一開始想到直接在原始配置上加鍵名來處理,但主與從的對應關係還是沒有這樣子明了,這裡的定義方式決定了load_db_proxy_setting的實作方式。

database.php配置

$_master_slave_relation = array(
  'default_master' => array('default_slave1', 'default_slave2', 'default_slave3'),
);
/* End of file database.php */
/* Location: ./application/config/database.php */

最開始的資料庫連結並未放到CI超級物件中,發現load多個模型時每次都會打開鏈接,所以完成讀寫分離之後一定要測試,可以在數據庫鏈接打開和關閉的地方查看是否按預期執行(方法對應application/core/database/drivers/mysql/mysql_driver. php中的db_connect和_close)。整個調整過程最重要的兩點是simple_query方法以及建構函式中關閉資料庫連結。模型中的調整是為了更方便的連結多個函式庫,未實現讀寫分離時也是這麼調整的,常用的方法獨立成一個文件,MY_Model去繼承。

實作MYSQL讀寫分離的中間件挺多,在沒有用到這些時可以透過程式上的控制來實現讀寫分離。當然這裡只是實作了讀寫分離,可以強制使用主函式庫。如果想要更好的分配方式,可以好好想想load_db_proxy_setting中的分配方式。

以上就是本文的全部內容,希望對大家的學習有所幫助,更多相關內容請關注PHP中文網!

相關推薦:

CI框架中常用的操作類別分析

#關於CI框架無限分類和遞歸的實作

以上是CodeIgniter實作讀寫分離的方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn