Redis 核心设计原理
场景:大流量查询数据库 比如双十一秒杀和春节抢车票
假设服务需要对外提供 每秒 1w 次查询,但背后的 MySQL 却只能提供每秒 5k 次查询,那 MySQL 根本顶不住!分分钟会被压垮。
有没有办法在 MySQL 不被压垮的同时,让商品服务支持每秒 1w 次查询 ?
当然有,没有什么是加一层中间层不能解决的,如果有,那就再加一层。
这次我们要加的中间层是 Redis。
本地缓存
查询内存的速度比查询磁盘要快, MySQL 数据主要存放在磁盘里,如果能将 MySQL 里的数据放内存里,查询完全不走磁盘,那必然能大大提升查询性能。
放在服务内部的缓存,就是所谓的本地缓存。
远程缓存
为了保证系统高可用,商品服务经常不止一个实例,如果每个实例都重复缓存一份本地内存,那就有些浪费内存条了。 所以更好的解决方案是将这部分字典内存抽出来,单独做成一个服务。就是所谓的远程缓存服务。
引入另外一个问题,多个商品服务通过网络去读写同一份远程缓存,会存在并发问题。怎么办呢?
很简单!对外不管有多少有个网络连接,收到读写命令后,都统一塞到一个线程上,在一个线程上对字典进行读写,什么并发问题和线程切换开销,完全不存在!
这个远程缓存服务足以满足大部分场景,但它属实过于简陋,我们来看下怎么优化它。
多种数据结构支持
现在缓存服务里,只有一个字典类型。 于是我们对字段的 value 进行扩展,除了 字符串, 还支持先进先出的队列 List 和用于去重的 Set 类型,再加入可以做排行榜的 ZSet,现在缓存服务就更强了。
内存过期策略
给缓存里的数据加个过期时间,一旦过期,就从内存里删掉,可以很大程度缓解内存增长速度。
怎么知道哪些数据该设置多长过期时间呢?
完全没办法,只能交给调用方去做判断,让用户通过 expire 命令的形式来指定哪些数据多久过期。
缓存淘汰
在内存接近上限的时候,根据一些策略删除掉一些内存。 比如可以将最近最少使用的内存删掉,也就是所谓的 LRU 这样不仅解决了内存过大的问题,还让 redis 里的数据全是热点数据。
持久化
一旦缓存服务重启,那内存就全丢了,所以我们还需要给 Redis 加入最大程度的持久化保证。
在缓存服务里加个异步线程,定期将全量内存数据定期持久化到磁盘文件里,而这种将内存数据生成快照保存到文件的方式,就是所谓的 RDB,Redis Database Backup。
全量数据备份耗时过高·,那我们化整为零,在每次写入数据时,顺手将数据记录到文件缓存中,并每秒将文件缓存刷入磁盘,这种持久化机制叫 AOF,Append 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 到目前为止就是个单机服务,高性能是有了,但高可用和可扩展性是一点没看到。
这就需要聊聊 主从 、哨兵和集群模式了 …