为什么要读写分离?
一般的系统都是读多写少,利用读写分离,可以提升mysql的效率
读写分离后,从库可以水平扩展
下面我们开始代码之旅吧
配置先改造:
$config = [ 'host'=> '127.0.0.1', //数据库ip'port'=> 3306, //数据库端口'user'=> 'root', //数据库用户名'password'=> '123456', //数据库密码'database'=> 'test', //默认数据库名'timeout'=> 0.5, //数据库连接超时时间'charset'=> 'utf8mb4', //默认字符集'strict_type'=> true, //ture,会自动表数字转为int类型'pool_size'=> '3', //连接池大小'pool_get_timeout'=> 0.5, //当在此时间内未获得到一个连接,会立即返回。(表示所以的连接都已在使用中)];
改成:
$config = [ 'pool_size'=> '3', //连接池大小'pool_get_timeout'=> 0.5, //当在此时间内未获得到一个连接,会立即返回。(表示所以的连接都已在使用中)'master'=> [ 'host'=> '127.0.0.1', //数据库ip'port'=> 3306, //数据库端口'user'=> 'root', //数据库用户名'password'=> '123456', //数据库密码'database'=> 'test', //默认数据库名'timeout'=> 0.5, //数据库连接超时时间'charset'=> 'utf8mb4', //默认字符集'strict_type'=> true, //ture,会自动表数字转为int类型], 'slave'=> [
[ 'host'=> '127.0.0.1', //从数据库1ip'port'=> 3306, //从数据库1端口'user'=> 'root', //从数据库1用户名'password'=> '123456', //从数据库1密码'database'=> 'test', //默认数据库名'timeout'=> 0.5, //数据库连接超时时间'charset'=> 'utf8mb4', //默认字符集'strict_type'=> true, //ture,会自动表数字转为int类型],
[ 'host'=> '127.0.0.1', //从数据库2ip'port'=> 3306, //从数据库2端口'user'=> 'root', //从数据库2用户名'password'=> '123456', //数据库密码'database'=> 'test', //默认数据库名'timeout'=> 0.5, //数据库连接超时时间'charset'=> 'utf8mb4', //默认字符集'strict_type'=> true, //ture,会自动表数字转为int类型]
],
];
上面模拟的一主两从,由于我本机没有搭建mysql的主从,所以这里主从配置一样
由于我们要做到对上层业务无感知,所以我们只需要改动mydb.php这一层就行了
{ //创建主数据连接$master = newMySQL();
$res = $master->connect($config['master']); if($res === false) { //连接失败,抛弃常throw newRuntimeException($master->connect_error, $master->errno);
} else{ //存入master资源$this->master= $master;
} //创建从数据库连接foreach($config['slave'] as$conf) {
$slave = newMySQL();
$res = $slave->connect($conf); if($res === false) { //连接失败,抛弃常throw newRuntimeException($slave->connect_error, $slave->errno);
} else{ //存入slave资源$this->slave[] = $slave;
}
}
$this->config= $config; return$res;
} /***@param$type*@param$index*@returnMySQL*@desc单个数据库重连*/public functionreconnect($type, $index)
{ //通过type判断是主还是从if('master'== $type) { //创建主数据连接$master = newMySQL();
$res = $master->connect($this->config['master']); if($res === false) { //连接失败,抛弃常throw newRuntimeException($master->connect_error, $master->errno);
} else{ //更新主库连接$this->master= $master;
} return$this->master;
} //创建从数据连接$slave = newMySQL();
$res = $slave->connect($this->config['slave'][$index]); if($res === false) { //连接失败,抛弃常throw newRuntimeException($slave->connect_error, $slave->errno);
} else{ //更新对应的重库连接$this->slave[$index] = $slave;
} return$slave;
} /***@param$name*@param$arguments*@returnmixed*@desc利用__call,实现操作mysql,并能做断线重连等相关检测*/public function__call($name, $arguments)
{
$sql = $arguments[0];
$res = $this->chooseDb($sql); print_r($res);
$db = $res['db'];
$result = call_user_func_array([$db, $name], $arguments); if(false=== $result) { if(!$db->connected) { //断线重连echo"mysql reconnect". PHP_EOL;
$db = $this->reconnect($res['type'], $res['index']);
$result = call_user_func_array([$db, $name], $arguments); return$this->parseResult($result, $db);
} if(!empty($db->errno)) { //有错误码,则抛出弃常throw newRuntimeException($db->error, $db->errno);
}
} return$this->parseResult($result, $db);
} /***@param$result*@param$db MySQL*@returnarray*@desc格式化返回结果:查询:返回结果集,插入:返回新增id, 更新删除等操作:返回影响行数*/public functionparseResult($result, $db)
{ if($result === true) { return[ 'affected_rows'=> $db->affected_rows, 'insert_id'=> $db->insert_id,
];
} return$result;
} /***@param$sql*@desc根据sql语句,选择主还是从* @ 判断有select 则选择从库, insert, update, delete等选择主库*@returnarray*/protected functionchooseDb($sql)
{ //查询语句,随机选择一个从库if('select'== strtolower(substr($sql, 0, 6))) { if(1== count($this->slave)) {
$index = 0;
} else{
$index = array_rand($this->slave);
} return[ 'type'=> 'slave', 'index'=> $index, 'db'=> $this->slave[$index],
];
} return[ 'type'=> 'master', 'index'=> 0, 'db'=> $this->master];
}
}
connect的时候,我们自动把主,从所有的连接都建立好
执行sql的时候,我们跟据sql语句是否有select关键词,来判断选择主库还是从库
重连操作,只重连有问题的连接,不重连所有的连接
一个支持读写分离的mysql数据库接连池就OK了,是不是很简单的,大家完全可以在此基础上封装自己的CRUD等相关操作
我们在http server里增加 Insert的逻辑:
if($request->server['path_info'] == '/add') {
go(function() use($request, $response) { //从池子中获取一个实例try{
$pool = MysqlPool::getInstance(); echo"当前可用连接数:". $pool->getLength() . PHP_EOL;
$mysql = $pool->get(); echo"当前可用连接数:". $pool->getLength() . PHP_EOL;
defer(function() use($mysql) { //协程执行完成,归还$mysql到连接池MysqlPool::getInstance()->put($mysql); echo"当前可用连接数:". MysqlPool::getInstance()->getLength() . PHP_EOL;
});
$ct = time();
$title = $request->get['title'];
$result = $mysql->query("insert into test values(NULL, '{$title}', '{$ct}')");
$response->end(json_encode($result));
} catch(\Exception $e) {
$response->end($e->getMessage());
}
}); return;
}
访问:http://127.0.0.1:9501/add?title=测试
再访问: http://127.0.0.1:9501/list, 看看是否有最新的
抛个问题:
主从分离后,如果写入之后马上读,大概率会读不到?为什么?有什么方案解决?