用 MySQL 的一张表实现一个 KV
2017-06-06
我司使用 redis 的场景硬性要求是线上热数据,因为 redis 机器内存就那么大,提高内存利用率也只能放热数据了,不过具体怎么定义热数据,这个就具体情况具体讨论了。
但是回过头来看,我们很大一部分使用 redis 的场景的目的不是为了使用它作为一个高效的内存数据库的特性,而是它自带的 kv 形式的数据结构,也就是平时业务中会遇到的大量中间数据的需要存储为一个 list、set 这种的需求,而 mysql 作为 source of truth
,是完全可以在一张表的基础上实现一个伪 redis 的。这里讲一下实现。
整理了下大部分的需求,发现绝大多数的使用场景为 list 和 set,这俩都是序列,里面含有多个 item,所以可以使用 key-item 作为 mysql 的一条记录,这样同时也方便后期对其他数据结构的扩展,对于有序可以增加一个标记位置的字段。
表设计
CREATE TABLE `misc_storage` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` int(11) unsigned NOT NULL,
`value` text COLLATE utf8mb4_unicode_ci,
`position` int(11) NOT NULL,
`lock_version` int(11) unsigned NOT NULL DEFAULT '0',
`create_time` datetime NOT NULL,
`_created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`_updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
- 使用 mysql 自增 ID 作为主键;
- name 作为 kv 里的 key(这里为了规避 mysql 关键字而使用了 name),name 的类型是 int(11),这样的目的是为了提高索引性能;
- value 表示一个原子的值,也就是字符串和数字,这里不关心具体业务值类型统一存储为字符串;
- position 表示在序列里的顺序,用于 list 这种有排序需求的数据结构;
- lock_version 当时设计是为了实现 redis 里面原子自增的操作,不过后面业务没有实际用到,所以只增加了字段,没有写具体的实现;
- create_time 没有业务用途,只是用于排错回溯
- 对 name 增加索引
在实现操作 API 时,函数签名和返回值要和 redis-py 里对应的方法一致,方便调用者。
表上的读写方法
- 创建记录
- 根据 name 获取 value 值
- 根据 name 获取按 position 排序的表记录
- 获取 name 对应的记录数
- 更新 position
- 根据 name 分页
- 查找 name 和对应的 value 是否存在
- 删除 name 对应的所有记录
- 删除一条记录
后面根据具体使用可以做一些优化,比如给 value 加上前缀索引索引之类的(SISMEMBER 要按 name 和 value 查询,数据量大之后效率很低)进一步优化性能