php是用phpredis扩展实现和redis的连接的,但是phpredis只是提供了简单的命令处理,没有对redis的主从架构做处理,因此,考虑自己写一个。
处理的思路很简单,在phpredis外面再套一层,根据传入的命令判断是进行读操作还是写操作,然后用call_user_func_array方法,将命令传递给phpredis做处理
一、安装phpredis扩展
二、配置
//redis主从配置 $GLOBALS['config']['redis_master_slave'] = [ 'host' => '127.0.0.1,127.0.0.1,127.0.0.1,127.0.01', // redis主机,默认第一个为master 'port' => '6379,6380,6381,6382', // redis端口 'password' => '', // 密码 'select' => '0,0,0,0', // 操作库 'expire' => 3600, //有效期 'timeout' => 0, // 连接超时时间(单位:毫秒) 'prefix' => 'PHPREDIS_SESSION:', // key前缀 ];
三、主从处理的类
/**
* Class RedisMs:redis主从复制
*/
class RedisMs {
/**
* 对象实例数组
* @var array
*/
private static $_instance = [];
/**
* hander
* @var null|redis
*/
protected $handler = null;
/**
* 配置
* @var array
*/
protected static $config = [
'host' => '127.0.0.1,127.0.0.1', // redis主机
'port' => '6379,6379', // redis端口
'password' => '', // 密码
'select' => '0,0', // 操作库
'timeout' => 0, // 连接超时时间(单位:毫秒)
];
/**
* debug模式
* @var bool
*/
public $debug = false;
/*
* 单例模式
*/
private function __construct($host, $port, $auth, $select, $timeout, $debug = false) {
if(!$this->handler) {
$this->handler = new redis();
}
$this->handler->connect($host, $port, $timeout);
if('' != $auth) {
$this->handler->auth($auth);
}
if(0 != $select) {
$this->handler->select($select);
}
$this->debug = $debug;
}
public static function getInstance($config = [], $debug = false) {
// 检测php环境
if (!extension_loaded('redis')) {
throw new Exception('not support:redis');
}
$config = array_merge(self::$config, $config);
$hosts = explode(",", $config['host']);
$ports = explode(",", $config['port']);
$pwds = explode(",", $config['password']);
$selects = explode(",", $config['select']);
if(count($hosts) < 2 || $ports < 2) {
throw new Exception('config error: at least 2 host and 2 port needed');
}
foreach($hosts as $key => $host) {
$port = !empty($ports[$key]) ? $ports[$key] : 6379;
$pwd = !empty($pwds[$key]) ? $pwds[$key] : '';
$select = intval($selects[$key]);
$timeout = $config['timeout'];
if(!self::$_instance[$key] instanceof self) {
self::$_instance[$key] = new self($host, $port, $pwd, $select, $timeout, $debug);
}
}
return self::$_instance[0];
}
/**
* 获取主服务器
* @return mixed
*/
public function master() {
if($this->debug) {
echo 'i am master
';
}
return self::$_instance[0];
}
/**
* 获取从服务器
*/
public function slaves() {
$slaves = [];
for($i = 1; $i < count(self::$_instance); $i++) {
$slaves[] = self::$_instance[$i];
}
return $slaves;
}
/**
* 随机生成一台从服务器
*/
public function oneSlave() {
$slaves = $this->slaves();
$count=count($slaves);
$i= mt_rand(0,$count - 1);
if($this->debug) {
echo 'i am slave '.$i.'
';
}
return self::$_instance[$i];
}
/**
* 执行命令
*/
public function runCommand($command, $params) {
try{
$redis = $this->getByCommand($command);
return call_user_func_array([$redis, $command], $params);
} catch (Exception $e) {
throw new Exception($e->getMessage(), $e->getCode());
}
}
/**
* 根据command命令来获取服务器
*/
protected function getByCommand($command) {
//TODO::这里需要完善,只列出了部分读命令
$read_command = ['get', 'hGet', 'lRange'];
$write_command = ['set', 'hSet'];
if(in_array($command, $read_command)) { //读命令,随机返回一台读服务器
return $this->oneSlave()->handler;
} elseif(in_array($command, $write_command)) {
return $this->master()->handler;
} else {
throw new Exception('不支持该命令:'.$command);
}
}
private function __clone() {
}
}
注意:getByCommand方法还需要完善,里面只列出了部分的读命令和写命令,将命令完善就可以了。
四、调用
/**
* redis主从操作
*/
header("Content-type: text/html; charset=utf-8");
error_reporting(E_ERROR);
define('DIR', dirname(__FILE__));
//引入配置文件
require_once DIR.'/config.php';
//引入库文件
require_once DIR.'/lib/RedisMs.php';
$handler = RedisMs::getInstance($GLOBALS['config']['redis_master_slave'], true);
//测试set命令
try{
echo '测试set命令
';
$handler->runCommand('set', ['name', 'maque']);
var_dump($handler->runCommand('get', ['name']));
echo '测试hset命令
';
$handler->runCommand('hSet', ['user_info', 'name', 'maque']);
$handler->runCommand('hGet', ['user_info', 'name']);
} catch (Exception $e) {
var_dump($e->getMessage());
}