Redis 核心设计原理

· 7 min read

场景:大流量查询数据库 比如双十一秒杀和春节抢车票

假设服务需要对外提供 每秒 1w 次查询,但背后的 MySQL 却只能提供每秒 5k 次查询,那 MySQL 根本顶不住!分分钟会被压垮。

有没有办法在 MySQL 不被压垮的同时,让商品服务支持每秒 1w 次查询 ?

当然有,没有什么是加一层中间层不能解决的,如果有,那就再加一层
这次我们要加的中间层是 Redis


本地缓存

查询内存的速度比查询磁盘要快, MySQL 数据主要存放在磁盘里,如果能将 MySQL 里的数据放内存里,查询完全不走磁盘,那必然能大大提升查询性能。

放在服务内部的缓存,就是所谓的本地缓存

远程缓存

为了保证系统高可用,商品服务经常不止一个实例,如果每个实例都重复缓存一份本地内存,那就有些浪费内存条了。 所以更好的解决方案是将这部分字典内存抽出来,单独做成一个服务。就是所谓的远程缓存服务

引入另外一个问题,多个商品服务通过网络去读写同一份远程缓存,会存在并发问题。怎么办呢?

很简单!对外不管有多少有个网络连接,收到读写命令后,都统一塞到一个线程上,在一个线程上对字典进行读写,什么并发问题线程切换开销,完全不存在!

这个远程缓存服务足以满足大部分场景,但它属实过于简陋,我们来看下怎么优化它。

多种数据结构支持

现在缓存服务里,只有一个字典类型。 于是我们对字段的 value 进行扩展,除了 字符串, 还支持先进先出的队列 List 和用于去重的 Set 类型,再加入可以做排行榜的 ZSet,现在缓存服务就更强了。

内存过期策略

给缓存里的数据加个过期时间,一旦过期,就从内存里删掉,可以很大程度缓解内存增长速度。

怎么知道哪些数据该设置多长过期时间呢?
完全没办法,只能交给调用方去做判断,让用户通过 expire 命令的形式来指定哪些数据多久过期

缓存淘汰

在内存接近上限的时候,根据一些策略删除掉一些内存。 比如可以将最近最少使用的内存删掉,也就是所谓的 LRU 这样不仅解决了内存过大的问题,还让 redis 里的数据全是热点数据

持久化

一旦缓存服务重启,那内存就全丢了,所以我们还需要给 Redis 加入最大程度的持久化保证。

在缓存服务里加个异步线程,定期将全量内存数据定期持久化到磁盘文件里,而这种将内存数据生成快照保存到文件的方式,就是所谓的 RDBRedis Database Backup

全量数据备份耗时过高·,那我们化整为零,在每次写入数据时,顺手将数据记录到文件缓存中,并每将文件缓存刷入磁盘,这种持久化机制叫 AOFAppend Only File

AOF 文件会不会很大? 没事,定期重写压缩就行,比如 a 被依次赋值 a=1,a=2,最终保留 a=2 就够了。

简化网络协议

远程缓存服务对外提供读写能力,那是对外提供的 HTTP 接口吗?

当然不是! 我们知道, HTTP 是基于 TCP 做的通信,实现了很多笨重的特性。
既然是为了性能,特地上的缓存服务,那就索性彻底点,抛弃 HTTP,直接基于 TCP 做传输就好! 传输协议也设计得简单点,比如只要通过 TCP 传入 SET key value,就能完成写入。传入”GET key” 就能获得对应的 value。非常简洁。

那传输协议的解析需要我们自己写代码去实现吗?

完全不需要,redis 官方提供了一个命令行工具,redis-cli,通过它,我们可以输入一些命令,读写 Redis 服务器里的各种内存数据。不想用命令行也没关系,各路大神已经用各种语言将 redis-cli 支持的命令实现了一遍,完全不需要自己手写。

总结

Remote Dictionary Server 一个高性能支持多种数据类型和各种缓存淘汰策略,并提供一定持久化能力的超强缓存服务

  • redis 本质上就是个远程字典服务,所有读写命令等核心逻辑,都在一个线程上完成。什么并发问题和线程切换开销,完全不存在!
  • redis 支持多种数据类型、内存过期策略和多种缓存失效策略,通过 TCP 对外提供了一套非常简单的传输协议。
  • redis 加入了最大程度的持久化保证。将数据持久化为 RDB 和 AOF,确保服务重启后不至于什么数据都没有。
  • redis 支持多种扩展,玩法非常多,比如 RediSearch 和 RedisJSON。

最后遗留一个问题 redis 到目前为止就是个单机服务,高性能是有了,但高可用可扩展性是一点没看到。
这就需要聊聊 主从 、哨兵集群模式了 …

参考资料

redis是什么?架构是怎么样的?怎么设计redis

Redis 是什么?架构是怎么样的? | golang全栈指南