Home  >  Article  >  Backend Development  >  How CodeIgniter implements read-write separation

How CodeIgniter implements read-write separation

不言
不言Original
2018-06-19 16:59:101292browse

This article mainly introduces the implementation method of CodeIgniter read-write separation, and analyzes the related configuration and function implementation skills of CodeIgniter read-write separation in detail in the form of examples. Friends in need can refer to the examples of this article

Describes the implementation method of CodeIgniter reading and writing separation. Share it with everyone for your reference, the details are as follows:

The current server is only master-slave, and read-write separation is not configured. The function of read-write separation can only be implemented by the program. Here we mainly talk about how Codeigniter implements it. Read and write are separated, and the following two points need to be met:

1. Read and write separation should be transparent to development.

There are solutions on the Internet to achieve read-write separation by manually loading multiple DBs. Such separation is too closely related to the business, increases the difficulty of development and is not conducive to maintenance. What we need to do is to default to read-write. library, writing writes to the main library, and the separation of reading and writing is transparent to developers

2. Simple configuration.

Retain the existing configuration method and configure read-write separation by adding an array, without affecting the original usage method.

Ideas

#1. The simplest idea to achieve separation of reading and writing is to determine whether to insert into the main library or read from the slave library based on the query statement where the query is finally executed. , so this function needs to be found.

2. The database should be connected only once, and the link should be reusable for the next operation. That is to say, all read operations are available even after re-database, and there is no need to connect again. The same is true for the main database. So we can put the link in the CI super object.

3. The master-slave judgment is based on the final executed SQL statement, so the automatic link autoinit parameter in the database configuration does not need to be set to true. If it is connected by default and there is no need to operate the library It's a waste of resources.

4. You can use $this->db in the model to directly operate the query without any other adjustments.

5. Do not directly modify the files under the system

Realize read and write separation

CI's DB class is fixed to read files under the system. We can achieve this with appropriate rewriting. The first is Loader.php, in which the database method is used to load database objects. It refers to the system/database/DB.php file. We determine whether there is a custom DB.php file and import it if it exists.

Rewrite 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 */

Then we create database/DB.php under application/core, This file has only one DB method, which is used to read the configuration file and perform initialization work. There are also two places that need to be rewritten:

Rewrite 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 */

The entire DB.php The adjustment is basically the introduction of files. The introduction of group name is to facilitate subsequent judgment. If it is not introduced, it can be configured through the host and database names. If you want to force autoint to be turned off, you can delete the following paragraph in DB.php:

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

The next step is the core part. Separation of reading and writing is achieved based on query statements.
The simple_query method in DB_driver.php can be understood as the final method of executing the SQL statement. We can judge the database link here.

Rewrite 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 */

The separation of reading and writing is basically realized here, but it is difficult to do things From beginning to end, the linked database object needs to be closed, and the connection can be closed after execution in the public controller.

DB_driver.php also has a close method. Can you consider whether it can be closed in this method? I think this is not possible here.

Close the database link

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 */

Use in the model, in order to make $this available in each model ->db, and do not connect to the database multiple times, here the link is also placed in the CI super object. This can be done even if there is no separation of reading and writing. It can easily connect multiple DBs. If you want to use other libraries for a specific model, you only need to pass in the group name in the constructor.

Model adjustment

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 */

The final database configuration method only needs to configure an array on the original basis. Can. Whether to use dual masters or one master and multiple slaves depends on the configuration here. At first I thought of adding key names directly to the original configuration, but the corresponding relationship between master and slave is still not so clear. The definition here determines the implementation of load_db_proxy_setting.

database.php configuration

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

The initial database link was not placed in the CI super object. It was found that When loading multiple models, the link will be opened every time, so you must test after completing the read-write separation. You can check whether the database link is opened and closed to see whether it is executed as expected (the method corresponds to application/core/database/drivers/mysql/mysql_driver. db_connect and _close in php). The two most important points in the entire adjustment process are the simple_query method and closing the database connection in the constructor. The adjustment in the model is to make it easier to link multiple libraries. It is also adjusted in this way when the separation of reading and writing is not implemented. Commonly used methods are separated into a file and MY_Model inherits them.

There are many middlewares that implement MYSQL reading and writing separation. When these are not used, reading and writing separation can be achieved through program control. Of course, this only implements the separation of reading and writing, and you can force the use of the main library. If you want a better allocation method, you can think about the allocation method in load_db_proxy_setting.

The above is the entire content of this article. I hope it will be helpful to everyone’s study. For more related content, please pay attention to the PHP Chinese website!

Related recommendations:

Analysis of commonly used operation classes in CI framework

About infinite classification and recursion of CI framework accomplish

The above is the detailed content of How CodeIgniter implements read-write separation. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn