背景
由于用户不断增加,业务功能不断积累,redis的使用也变的越多,单机扩展内存已不是最优解,选用了升级redis集群,采用的( Proxy +Redis Cluster)的架构方案。
架构优势
a. 相比于使用代理,减少了一层网络传输的消耗,效率较高;
b. 不依赖于第三方中间件,实现方法和代码自己掌控,可随时调整。
Proxy:
a. 提供一套 HTTP Restful 接口,隔离底层存储。对客户端完全透明,跨语言调用。
b. 升级维护较为容易,维护 Redis Cluster,只需要平滑升级 Proxy。
c. 层次化存储,底层存储做冷热异构存储。
d. 权限控制,Proxy 可以通过秘钥控制白名单,把一些不合法的请求都过滤掉。并且也可以控制用户请求的超大 Value 进行控制和过滤。
e. 安全性,可以屏蔽掉一些危险命令,比如 Keys、Save、Flush All 等。
f. 容量控制,根据不同用户容量申请进行容量限制。
g. 资源逻辑隔离,根据不同用户的 Key 加上前缀,来进行资源隔离。
h. 监控埋点,对于不同的接口进行埋点监控等信息。
架构缺点
- 客户端的不成熟,影响应用的稳定性,提高开发难度。
- MultiOp 和 Pipeline 支持有限。
- 连接维护,Smart 客户端对连接到集群中每个结点 Socket 的维护。
Proxy:
- 代理层多了一次转发,性能有所损耗
- 进行扩容/缩容时候对运维要求较高,而且难以做到平滑的扩缩容
官方地址
兼容改动
1.keys 命令不再允许使用,可改用基于游标的迭代器来完成 scan,最后我会列举使用示例。
2.exists 命令也不再允许使用,可通过ttl判断时间来替代,时间复杂度为O(1),或者根据hash,set等类型长度来替代。
3.multi事务中,需要增加watch。这也暗藏着一个bug。
主要修改的是这三个命令。
scan示例
class pro
{
public function output($str = '') {
echo $str . PHP_EOL;
return true;
}
public function formatList($data) {
$data = explode(PHP_EOL, $data);
$list = [];
$cursor = 0;
foreach ($data as $item) {
if (empty($item)) continue;
if (!$cursor) {
$cursor = $item; //没有值,第一个是游标值
} else {
$list[] = $item;
}
}
return [$cursor, $list];
}
public function main() {
$config = config::get('redis_api'); //获取配置
$host = $config[0]['host'];
$pwd = $config[0]['pwd'];
$port = $config[0]['port'];
sleep(2);
$redis = new Redis(); //实例化redis
$cursor = 0; //游标值,为0表示结束
$size = 100;
try {
do {
//命令行方式执行redis scan
$shell = "redis-cli -h $host -a $pwd -p $port scan $cursor match " . $this->redisKey . "* count $size";
$result = shell_exec($shell);
//格式化游标和内容
list($cursor, $list) = $this->formatList($result);
foreach ($list as $key) {
//进行对结果的操作,如下删除key
$redis->del($key);
}
} while ($cursor != 0);
} catch (Exception $e) {
$this->output('error:' . $e->getMessage());
}
}
}
$pro = new pro();
$pro->main();
redis事务中的坑
$redis = new redis();
$key = 'test_zp';
//升级集群增加的watch
$redis->watch($key);
//实例化事务
$multi = $redis->multi(Redis::PIPELINE);
//批量执行插入操作
foreach ($list as $item) {
$redis->ZADD($key, time(), $item . random_int(100, 500));
}
$multi->exec();
// 重点,因为集群升级所加的watch必须用unwatch配合使用
// unwatch必须加
$redis->unwatch($key);
Redis::PIPELINE是为了管道方式执行为了避免多次链接,一次请求完成多个操作。
由于使用了管道,导致了必须使用unwatch,不然会导致exec后面的redis操作失败。