线上Redis流量优化经历全过程,一点点的小改动,流量下降500%

简要说明:#

1、阿里云 Redis 服务。
2、32G 内存,100M 带宽。
3、Redis 的 Get 请求频率大约 5000 次 /s。
4、Redis 的 Key 总量约 2000 万。

原因:#

高峰期已过,阿里云钉钉群每天至少有一次流量报警,有时更多。

处理方法:#

1、阿里云离线全量 Key 分析数据。#

1)第一步分析大 Key,大部分是集合,找代码没有发现取全量。
2)第二步按 KEY 占用大小倒序,找到部分频率高,且 KEY 值较大键值优化。主要是减小本身 key 值的大小。(优化后没有效果,有些时候可以解决问题)

2、通过 monitor 实时命令分析。#

1)第一步把 Redis 在 5 秒内执行的 GET 打印到文件(时间再长就有点多),进行筛选,没有找到原因,执行命令如下。

redis-cli  -h 主机名 -p 6379 -a 密码 monitor |grep "GET" > /tmp/temp-1.log

线上Redis流量优化经历全过程,一次小小的改动,流量下降500%

2)第二步把导出数据放在 Excel 分析,抽取数据中某一秒的所有数据(要取中间, 不要取最前面和最后面的),肉眼寻找可能有问题的 KEY 进行优化。(问题仍未解决)
3)第三步在分析第二步时,发现有些 Key 已过期,Get 不到数据。
4)第四步写一个直接分析 monitor 日志脚本,并打印所有 Key 大小。

 /**
     * Execute the console command.
     *
     * @return int
     */
    public function handle()
    {
        $file = "/tmp/monitor-2.log";
        $totalLen = $this->fileHandel($file);
        echo "完成 总大小:"  . $totalLen . "\r\n";
        return 0;
    }

    /**
     * 根据文件获取
     */
    public function fileHandel($fileURL)
    {
        $handle = fopen($fileURL, "r");
        $totalLen  = 0;
        if ($handle) {
            while (($line = fgets($handle)) !== false) {
                // 处理每一行
                preg_match('/\[([0-9]+)[\s\S]+GET[\s\S]+"([^"]+)"/i', $line, $arr);
                if (count($arr) != 3) {
                    echo "错误";
                } else {
                    $len = $this->getLen($arr[2], $arr[1]);
                    $totalLen += $len;
                    echo $len . "  " . $arr[2] . "    " . $arr[1] . "\r\n";
                }
            }
            fclose($handle);
        } else {
            echo "无法打开文件";
        }

        return $totalLen;
    }

    private function getLen($key, $dbSelect)
    {
        //去掉前缀
        $key1 = (string)str_replace("redis端缀", "", $key);

        //设置库名
        Redis::select($dbSelect);

        //查询大小
        $len = Redis::strlen($key1);
        return $len;
    }

线上Redis流量优化经历全过程,一次小小的改动,流量下降500%

5)第五步根据 Key 大小再筛选,然后再看看 1 秒内执行的次数,最终发现了问题。
线上Redis流量优化经历全过程,一次小小的改动,流量下降500%
6)最后优化代码

        // 新代码
        $key = \getRedisKey('qSubjectList') . implode("", $ids);
        $fields = [  'id', 'name' ];
        $list =  Cache::remember($key, TTL::TTL_DAY_RAND(), function () use ($fields, $ids) {
            return self::query()->where('status', self::STATUS_ON)
            ->select($fields)
            ->whereIn("id", $ids)
            ->get()->toArray();
        }) ?? [];
        return collect($list)->whereIn('id', $ids);

        // 旧代码,需要优化
        // $list = self::getCacheList();
        // return collect($list)->whereIn('id', $ids);

7)更改代码后,实际效果如图:
线上Redis流量优化经历全过程,一次小小的改动,流量下降500%

总结:#

1、最难的问题是怎么找到这个 Key,为了找这个 Key 花费了不少时间。
2、最主要问题还是代码质量,所以大家写代码时一定按需取字段,且记不可以把所有数据存在一个 Key,而是按唯一性存储(后台有时候方便,可以使用)。

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 11

这样写每次 $ids 不一样都会查询数据库啊

我一般做法 key 是 getRedisKey('qSubjectList') . $id
1、通过 pipeline 循环获取命中缓冲的
2、db 查询 未命中缓冲的,然后写入缓冲
3、最后合并数据

1周前 评论
huangYX (楼主) 1周前
huangYX (楼主) 1周前
雷雷 (作者) 1周前

排查:访问频繁且值较大的 key(流量最大)
措施:原来 qSubjectList 包含了所有 id 的数据,查询时是取出来再筛选 ids 比如 [1,2,3] 的数据,现在是将 ids [1,2,3] 单独存储到一个键值。
理解的对吗?

1周前 评论
huangYX (楼主) 1周前
php_yt (作者) 1周前

感觉写得稀里糊涂的,没看明白?有大能帮忙解释下吗?

1天前 评论
huangYX (楼主) 1天前
php_yt 1天前
老年人 (作者) 19小时前