<?php
/**
* Redis 操作,支持 Master/Slave 的负载集群
*
* @author V哥
*/
class
RedisCluster{
// 是否使用 M/S 的读写集群方案
private
$_iSUSECluster
= false;
// Slave 句柄标记
private
$_sn
= 0;
// 服务器连接句柄
private
$_linkHandle
=
array
(
'master'
=>null,
// 只支持一台 Master
'slave'
=>
array
(),
// 可以有多台 Slave
);
/**
* 构造函数
*
* @param boolean $isUseCluster 是否采用 M/S 方案
*/
public
function
__construct(
$isUseCluster
=false){
$this
->_isUseCluster =
$isUseCluster
;
}
/**
* 连接服务器,注意:这里使用长连接,提高效率,但不会自动关闭
*
* @param array $config Redis服务器配置
* @param boolean $isMaster 当前添加的服务器是否为 Master 服务器
* @return boolean
*/
public
function
connect(
$config
=
array
(
'host'
=>
'127.0.0.1'
,
'port'
=>6379),
$isMaster
=true){
// default port
if
(!isset(
$config
[
'port'
])){
$config
[
'port'
] = 6379;
}
// 设置 Master 连接
if
(
$isMaster
){
$this
->_linkHandle[
'master'
] =
new
Redis();
$ret
=
$this
->_linkHandle[
'master'
]->pconnect(
$config
[
'host'
],
$config
[
'port'
]);
}
else
{
// 多个 Slave 连接
$this
->_linkHandle[
'slave'
][
$this
->_sn] =
new
Redis();
$ret
=
$this
->_linkHandle[
'slave'
][
$this
->_sn]->pconnect(
$config
[
'host'
],
$config
[
'port'
]);
++
$this
->_sn;
}
return
$ret
;
}
/**
* 关闭连接
*
* @param int $flag 关闭选择 0:关闭 Master 1:关闭 Slave 2:关闭所有
* @return boolean
*/
public
function
close(
$flag
=2){
switch
(
$flag
){
// 关闭 Master
case
0:
$this
->getRedis()->close();
break
;
// 关闭 Slave
case
1:
for
(
$i
=0;
$i
<
$this
->_sn; ++
$i
){
$this
->_linkHandle[
'slave'
][
$i
]->close();
}
break
;
// 关闭所有
case
1:
$this
->getRedis()->close();
for
(
$i
=0;
$i
<
$this
->_sn; ++
$i
){
$this
->_linkHandle[
'slave'
][
$i
]->close();
}
break
;
}
return
true;
}
/**
* 得到 Redis 原始对象可以有更多的操作
*
* @param boolean $isMaster 返回服务器的类型 true:返回Master false:返回Slave
* @param boolean $slaveOne 返回的Slave选择 true:负载均衡随机返回一个Slave选择 false:返回所有的Slave选择
* @return redis object
*/
public
function
getRedis(
$isMaster
=true,
$slaveOne
=true){
// 只返回 Master
if
(
$isMaster
){
return
$this
->_linkHandle[
'master'
];
}
else
{
return
$slaveOne
?
$this
->_getSlaveRedis() :
$this
->_linkHandle[
'slave'
];
}
}
/**
* 写缓存
*
* @param string $key 组存KEY
* @param string $value 缓存值
* @param int $expire 过期时间, 0:表示无过期时间
*/
public
function
set(
$key
,
$value
,
$expire
=0){
// 永不超时
if
(
$expire
== 0){
$ret
=
$this
->getRedis()->set(
$key
,
$value
);
}
else
{
$ret
=
$this
->getRedis()->setex(
$key
,
$expire
,
$value
);
}
return
$ret
;
}
/**
* 读缓存
*
* @param string $key 缓存KEY,支持一次取多个 $key = array('key1','key2')
* @return string || boolean 失败返回 false, 成功返回字符串
*/
public
function
get(
$key
){
// 是否一次取多个值
$func
=
is_array
(
$key
) ?
'mGet'
:
'get'
;
// 没有使用M/S
if
(!
$this
->_isUseCluster){
return
$this
->getRedis()->{
$func
}(
$key
);
}
// 使用了 M/S
return
$this
->_getSlaveRedis()->{
$func
}(
$key
);
}
/**
* 条件形式设置缓存,如果 key 不存时就设置,存在时设置失败
*
* @param string $key 缓存KEY
* @param string $value 缓存值
* @return boolean
*/
public
function
setnx(
$key
,
$value
){
return
$this
->getRedis()->setnx(
$key
,
$value
);
}
/**
* 删除缓存
*
* @param string || array $key 缓存KEY,支持单个健:"key1" 或多个健:array('key1','key2')
* @return int 删除的健的数量
*/
public
function
remove(
$key
){
// $key => "key1" || array('key1','key2')
return
$this
->getRedis()->
delete
(
$key
);
}
/**
* 值加加操作,类似 ++$i ,如果 key 不存在时自动设置为 0 后进行加加操作
*
* @param string $key 缓存KEY
* @param int $default 操作时的默认值
* @return int 操作后的值
*/
public
function
incr(
$key
,
$default
=1){
if
(
$default
== 1){
return
$this
->getRedis()->incr(
$key
);
}
else
{
return
$this
->getRedis()->incrBy(
$key
,
$default
);
}
}
/**
* 值减减操作,类似 --$i ,如果 key 不存在时自动设置为 0 后进行减减操作
*
* @param string $key 缓存KEY
* @param int $default 操作时的默认值
* @return int 操作后的值
*/
public
function
decr(
$key
,
$default
=1){
if
(
$default
== 1){
return
$this
->getRedis()->decr(
$key
);
}
else
{
return
$this
->getRedis()->decrBy(
$key
,
$default
);
}
}
/**
* 添空当前数据库
*
* @return boolean
*/
public
function
clear(){
return
$this
->getRedis()->flushDB();
}
/* =================== 以下私有方法 =================== */
/**
* 随机 HASH 得到 Redis Slave 服务器句柄
*
* @return redis object
*/
private
function
_getSlaveRedis(){
// 就一台 Slave 机直接返回
if
(
$this
->_sn <= 1){
return
$this
->_linkHandle[
'slave'
][0];
}
// 随机 Hash 得到 Slave 的句柄
$hash
=
$this
->_hashId(mt_rand(),
$this
->_sn);
return
$this
->_linkHandle[
'slave'
][
$hash
];
}
/**
* 根据ID得到 hash 后 0~m-1 之间的值
*
* @param string $id
* @param int $m
* @return int
*/
private
function
_hashId(
$id
,
$m
=10)
{
//把字符串K转换为 0~m-1 之间的一个值作为对应记录的散列地址
$k
= md5(
$id
);
$l
=
strlen
(
$k
);
$b
= bin2hex(
$k
);
$h
= 0;
for
(
$i
=0;
$i
<
$l
;
$i
++)
{
//相加模式HASH
$h
+=
substr
(
$b
,
$i
*2,2);
}
$hash
= (
$h
*1)%
$m
;
return
$hash
;
}
}
// End Class
// ================= TEST DEMO =================
// 只有一台 Redis 的应用
$redis
=
new
RedisCluster();
$redis
->connect(
array
(
'host'
=>
'127.0.0.1'
,
'port'
=>6379));
$redis
->set(
'id'
,35);
var_dump(
$redis
->get(
'id'
));
// 有一台 Master 和 多台Slave 的集群应用
$redis
=
new
RedisCluster(true);
$redis
->connect(
array
(
'host'
=>
'127.0.0.1'
,
'port'
=>6379), true);
// master
$redis
->connect(
array
(
'host'
=>
'127.0.0.1'
,
'port'
=>63791), false);
// slave 1
$redis
->connect(
array
(
'host'
=>
'127.0.0.1'
,
'port'
=>63792), false);
// slave 2
$redis
->set(
'id'
,100);
for
(
$i
=1;
$i
<=100; ++
$i
){
var_dump(
$redis
->get(
'id'
)).PHP_EOL;
}
// phpRedis 扩展的更多高级操作
$redis
=
new
RedisCluster();
$redis
->connect(
array
(
'host'
=>
'127.0.0.1'
,
'port'
=>6379));
$ret
=
$redis
->getRedis()->ping();
// phpRedis 原始API
var_dump(
$ret
);