- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章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 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
//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中删掉下面这段:
1
2
3
4
|
if
(
$DB
->autoinit == TRUE)
{
$DB
->initialize();
}
|
接下来就是最核心的地方。根据查询语句实现读写分离。 DB_driver.php中的simple_query方法可以理解为最后执行SQL语句的方法,我们可以在这里进行数据库链接的判断.
重写DB_driver.php 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
//增加属性,表示当前组
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方法,可以考虑下是否可以在该方法中关闭?这里认为是不行的.
关闭数据库链接 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
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即可.
模型调整 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
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配置 。
1
2
3
4
5
|
$_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中的分配方式.
希望本文所述对大家基于CodeIgniter框架的PHP程序设计有所帮助.
最后此篇关于CodeIgniter读写分离实现方法详解的文章就讲到这里了,如果你想了解更多关于CodeIgniter读写分离实现方法详解的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我在尝试使用 jpa2.0 将包含持久实体和分离实体(新创建的实体)的实体列表更新到我的数据库中时遇到错误。 我的实体包含在合并数据时出现错误(在标题中提到)的内部实体: Class supercla
我在分层 Pane 中有一组面板。我需要一个分隔符来将 sideBar 与 topBar 和 tabbedPanel 分开。我留了一个 10 像素的缓冲区来放置它。不幸的是,可能由于它是 JLayer
在我从数据库中读取的代码中,我还使用自定义适配器打印出每一行,该行中有一个 texttview、2 个按钮和一个 edittext。这一切都很好,但是当按下按钮时,edittext 会递增或递减,有没
我有一个由 Hibernate 4.3.4 管理的实体,它有一个其他实体的一对多集合。 在我的处理过程中,我必须分离父实体(将分离级联到子列表)。但是,当我向列表中添加一个尚未持久化的新项目并执行分离
我想追加一行,该行应该是表格的最后一行。在我的代码中,它似乎在第一次动态添加行时起作用。但是当添加其他行时它不会成为最后一行。 我总是希望“subtot”行成为最后一行,但是当我追加其他行时,它不
我试图用它们之间的空格分隔这 2 个 div(请参见图 1)。问题是当我添加边距或填充时会发生这种情况(请参见图 2)。 这是我的代码,请注意我没有使用 Bootstrap: .row { mar
我的服务器包含一些 ServerActor。该 actor 接收 RegisterClient 消息并将 ActorRefs 添加到已注册客户端列表中。 我还有多个客户端,每个客户端都包含 Clien
假设我有一个需要两个参数的函数,并且参数的顺序会影响结果。 是否可以将第一个参数传递给 partial 或 comp 函数,然后将另一个参数传递给它,如下所示: (defn bar [arg1 arg
如何搜索和分离多个后代键。 例子: (def d {:foo 123 :bar { :baz 456 :bam { :w
我正在尝试为 Slick 表创建一个类型安全的动态 DSL,但不确定如何实现这一点。 用户可以通过以 form/json 格式发送过滤器来将过滤器发布到服务器,我需要使用所有这些来构建一个 Slick
我是新来的,我发现看到充满大量函数和变量初始化以及 UI 的组件时眼睛很痛。是否可以将它们分开? 而不是默认设置,如下所示。如何将业务逻辑分离到另一个文件中? function MyComponent
我试图通过将参数粘贴在一起来使用分离。这应该是一件容易的工作,但不适合我。当我想到使用 eval(parse()) 时,我知道是时候寻求帮助了 通常,如果我加载一个包,我可以按如下方式分离它: det
(dissoc :a m)允许我解除给定的键。但是,有没有办法使用谓词函数来分离 pred 为真的任何键? (dissoc-with-pred pred? m) 所以给了一张 map - {:a 2
我编写了一个使用 devtools 来包含内部数据的包: devtools::use_data(.data, internal = T, overwrite = T) 我最近更改了该数据并重建了包。我
所以我有一个脚本,我想在我的服务器上运行它而不会打扰我。所以我想我会在 tmux 窗口中运行服务器,然后 detach这样我就可以简单地 attach如果我想查看进度(此脚本需要数天才能运行)。 但是
ThreeJS中动画数据和模型数据是否可以解耦? 这样就可以交换模型并保留动画?我认为这可能非常强大 我知道如何在 ThreeJS 中做到这一点的方法是将每个动画一个接一个地打包在一个模型中,这似乎是
我有一堆(Ruby)脚本存储在服务器上。到目前为止,我的团队通过打开一个启动脚本名称列表的访问器应用程序来使用它们,然后他们在工作文件夹中的文件上选择要在该实例中运行的脚本。脚本直接从服务器运行,因此
我想知道 javascript 如何包含在 jsp 中 - 我们是否可以在 .js 文件中放置 jsp 能够识别的任何代码,而不仅仅是 javascript 代码? 我有一些常见的 JavaScrip
您是否可以在 Dockerfile 中指定一个选项,默认使用它构建的容器以分离方式运行。 这将导致与 -d 相同的结果: docker run -d 这样 docker run 默认情况下会分离运
我正在为现有的 Java 程序开发 Java 插件。现有程序使用特定版本的 eclipse.uml2.*,我的插件也是如此。不幸的是,我的插件需要更新版本。 为了运行该插件,我需要将其导出到 Jar
我是一名优秀的程序员,十分优秀!