一次成功(失败)的Redis重构

导言

很久没写博客了,主要觉得工作上值得写博客的地方不多,个人对于写博客的看法是:凡是网上能找到的东西更适合记忆,比如说 shell 命令、某些语言的官方库或者某项开源技术的文档等。博客更适合的是总结和复盘自己的经验,更适合原创用来深化记忆。下面开始复盘这次重构。

复盘

起因

​ 这次重构起因是一个后台管理项目添加功能的需求,先介绍一下项目的大致情况:

  • 表 A :数据量K级,主要保存一些单条数据内容,核心表,日增长大概几十条左右

  • 表 B :数据量 100K 级,和表A是多对一关联,日增长千条左右。

  • 表 C :数据量 100K 级,和表A也是多对一关联,日增长同表B,表C和表B关联字段不同

  • 表 D :整个数据库的大 BOSS ,慢查询的根本原因所在,数据1000K级而且增长迅速,保存的是表 A 和表 B 的关联状态也就是保存的表 A 与表 B 关联状态的各种值。基本上所有条件查询都是从表D而来

分析环境

当我接手这套项目的时候页面显示速度已经到了惊人的 8S 左右,运营使用的时候进行搜索和打开页面要花很长时间,看了下代码,这套项目用的 Yii2 php 框架,按理说应该用 ActiveRecord 进行数据库操作的,但是当时写这套代码的大兄弟图省事直接用的SQL字符串+占位符+拼接的形式, SQL 大多数都是select A join B的形式,因为涉及到了条件查询,在某些查询中嵌套到了惊人的5层join,直接导致了查询时间过长。

解决思路

首先在解决数据库查询的之前,先发现了一个导致显示时间翻倍的祸首: Yii2 的分页,众所周知,数据进行分页显示的时候要统计数据总数来算出页数和总数 来计算 SQL 查询的 offset 和 limit 值,如果要在单次请求的情况下显示分页的话,会在原有查询的基础上进行一次 count 操作,也就是原本的时间*2 ,接需求的时候考虑到紧急程度,先将分页换成前端异步请求显示,算是优化了打开的速度,把页面打开速度控制在了5S左右。
暂时解决了页面打开速度之后,考虑的是如何优化了,页面逻辑其实很简单,就是几个求并集的操作,以核心表 A 的某个字段为主键查询其他几个表,在看到业务代码的时候我就考虑到可以直接用 redis 的 set 来储存数据集,用 set 的 api 来进行求交集操作。使用redis重构了之后发现速度降到了 200ms 左右.
在解决了条件查询之后,遇到的另外一个问题就是排序,众所周知, redis 自带排序的 zset 排序效果非常差,而且无法和set求交集, 所以靠 zset 解决排序是基本不可能的,在跟运营那边负责人沟通了之后了解到表 A 的数据处于 2K 以内,于是写了个根据表 A 的主键从 zset 取值并进行排序的快排算法,算是部分解决了页面显示过慢的问题。把首页打开时间降到了1S以内。

总结

如果要我归纳下这次redis优化的主要总结有啥,其实还是传统型数据库和新型 Nosql 数据库之间的冲突,mysql在涉及到排序、条件查询且关联数据不多 IO不多的情况下都是最优解,但是一旦涉及到求交集的时候,传统数据库的缺点就很明显了:涉及到的 join 操作过多很容易导致慢查询。此外,当初设计表的时候也有很多不足的地方,比如说像D表这种需要经常修改状态的大中型表,居然没有加 created 和 updated 字段,直接导致无法根据时间戳的时间来更新 redis 内的数据,再比如我现在接到的需求,需要根据 A 和 B 两张表的信息变动查询数据,但是在设计数据库的时候居然没有考虑日志记录这回事,直接导致前期所有变动数据无处可查,只能额外加个日志表储存。