2021-04-30

分布式数据库的过期机制(TTL)实现原理

Views: 5186 | Add Comments

像 MySQL 这样的传统关系数据库, 没有提供数据过期自动删除功能(TTL), 因为从常规理解, MySQL 是一个持久化数据库, 不是缓存系统, 数据应该由用户主动地通过 DELETE 指令显式地删除. 不过, 从实践经验上看, 由数据库系统提供基于过期时间自动删除数据(TTL)的功能, 可以减少用户(应用开发者)的工作量. 所以, Redis, SSDB, MongoDB 这些新开的 NoSQL 数据库都提供了 TTL 功能.

TTL 功能非常有用, 能节省用户的工作, 但是, TTL 也可能被误用和滥用. 在讨论 TTL 的实现原理之前, 我们需要对 TTL 功能的特性和使用场景进行讨论, 形成共识, 然后才能决定应该采取哪种实现手段. 写代码(implementation)从来都是选择题, 而不是技术难题.

TTL 是一种运维功能

由于 Redis 是一种内存数据库, 其特点是速度快, 因此给了用户一种错觉, 误认为数据库的 TTL 都是精确地, 甚至 Redis 的 API 还将 TTL 精确到了毫秒级别. 许多数据库系统的设计者都被 Redis 误导, 把精确过期当成一种设计目标.

我认为, TTL 功能仅仅是一种运维特性, 其本质目的是为了减少用户的工作量, 让用户不再需要写脚本扫描数据库清理数据. TTL 的效果等同于运维人员手动运行脚本删除过期数据, 所以, 不能做到精确过期, 也不应该做到精确过期. 用户基于此原则, 所以, 不能滥用 TTL, 如把 TTL 用来实现所谓的"分布式锁".

实现原理

1. 读时检查过期

这种实现方案, 是把数据的过期时间(绝对时间戳)和数据保存在一起, 所以, 每一次读取数据的时候, 都会将过期时间一起读出来. 然后, 读取数据的代码判断过期时间, 如果已经过期, 则忽略这条数据.

上面这个过滤逻辑, 可以放在服务端, 也可以放在用户端.

但是, 这个方案, 有数据约束的问题. 例如, 某个容器单独维护了元素数量, 那么, 会导致读取元素和读取容器计数两份信息的不一致, 如元素全部过期, 但计数不为零.

这种相互依赖(约束)的数据的一致性问题, 有时候非常恼人. 有时候, 我们情愿读到过期数据, 也不愿意数据不一致.

2. 定期扫描清理

读的时候, 我们不用管数据的过期时间, 只要能读到, 就认为数据没有过期. 然后, 有一个独立的线程(进程, 系统), 请求数据库执行 DELETE 操作, 将过期数据删除.

这种方案的优点是数据库系统的代码简单, 因为只有 DELETE 功能逻辑, 独立的清理线程和数据库系统是解耦的. 缺点是, 清理的即时性得不到保证. 但是, 回到 TTL 的本质上来, 我们不保证 TTL 的时间精确度, 只是尽可能精确, 这样, 在得到用户理解的基础上, 缺点便不存在了.

实例分析

我们来分析 Redis 等流行的数据库的 TTL 实现原理.

Redis

Redis 的数据全放在内存, 所以可以毫无顾忌地实现很多操作. Redis 采取读时检查和定期清理两种机制结合, 所以实现了毫秒级的过期精度.

SSDB

SSDB 为了保证系统的简单可依赖, 仅采取第二种方案, 由一个独立的线程定期扫描过期对象列表(不是扫描整个数据库)并删除. SSDB 扫描的是对象的"指针"列表而不是扫描对象数据本身, 所以没有大量的 IO. 另外, 列表经过动态排序, 每一次扫描没有涉及没过期的数据, 不会浪费 IO. SSDB 一般能保证 100ms 级别的过期精度.

MongoDB

MongoDB 的思想和 SSDB 类似, 它为对象建立了独立的 TTL 索引, 然后有独立的线程扫描并删除.

总结

分布式数据库的数据被分散(sharding)在物理距离长短不一的多台机器里, 数据之间的依赖和约束可能跨越上万公里, 所以, Redis 的 TTL 实现机制没有太大的借鉴意义. TTL 功能非常重要, 受欢迎, 但是, 分布式数据库系统的设计者必须回归本质, 数据库的使用者也要和数据库设计者达成共识, TTL 功能只是一种运维功能, 只有最终性, 没有因果(先后)性.

Related posts:

  1. SSDB在大数据量日志分析中的应用案例
  2. 性能超越 Redis 的 NoSQL 数据库 SSDB
  3. SSDB 支持 Redis 协议!
  4. 使用 Twemproxy 来做 SSDB 负载均衡
  5. 单实例支撑每天上亿个请求的SSDB
Posted by ideawu at 2021-04-30 00:41:40

Leave a Comment