背景

由于用户不断增加,业务功能不断积累,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. 监控埋点,对于不同的接口进行埋点监控等信息。

架构缺点

  1. 客户端的不成熟,影响应用的稳定性,提高开发难度。
  2. MultiOp 和 Pipeline 支持有限。
  3. 连接维护,Smart 客户端对连接到集群中每个结点 Socket 的维护。

Proxy:

  1. 代理层多了一次转发,性能有所损耗
  2. 进行扩容/缩容时候对运维要求较高,而且难以做到平滑的扩缩容

官方地址

redis集群兼容说明

兼容改动

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操作失败。