Redis
Redis基础
什么是Redis
Redis是基于C语言开发的NoSQL数据库,它是一种存储KV键值对数据的内存数据库,因此读写速度非常快,被广泛应用于分布式缓存方向。
Redis内置了多种数据类型实现:
- 5种基础数据类型
- String
- List
- Set
- Hash
- Zset (有序集合)
- 3种特殊数据类型
- HyperLogLog(基数统计)
- Bitmap(位图)
- Geospatial (地理位置)
Reids还支持事务、持久化(将内存数据保存在磁盘中,重启时可以再次加载使用)、Lua脚本、多种开箱即用的集群方案(Redis Sentinel、Redis Cluster)。
Redis为什么速度快?
Redis基于内存,内存的访问速度是磁盘的上千倍。
Redis基于Reactor模式,设计开发了一套高效的事件处理模型,主要是单线程事件循环和IO多路复用。
Redis内置了多种优化过后的数据类型/结构实现,性能非常高。
Redis持久化机制
使用缓存的时候,我们经常需要对内存中的数据进行持久化,也就是将内存中的数据写入到硬盘中。大部分原因是为了之后重用数据(比如重启机器、机器故障之后恢复数据),或者是为了做数据同步(比如 Redis 集群的主从节点通过 RDB 文件同步数据)。
Redis 支持 3 种持久化方式:
- 快照(snapshotting,RDB)
- 只追加文件(append-only file, AOF)
- RDB 和 AOF 的混合持久化(Redis 4.0 新增)
RDB 持久化
Redis 可以通过创建快照来获得存储在内存里面的数据在 某个时间点 上的副本。Redis 创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis 主从结构,主要用来提高 Redis 性能),还可以将快照留在原地以便重启服务器的时候使用。
快照持久化是 Redis 默认采用的持久化方式,在 redis.conf
配置文件中默认有此下配置:
1 | save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发bgsave命令创建快照。 |
AOF持久化
与快照持久化相比,AOF (append only file)持久化的实时性更好。Redis 6.0 之后默认开启了AOF持久化,可以通过 appendonly
参数开启:
1 | appendonly yes |
开启 AOF 持久化后,每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到 AOF 缓冲区 server.aof_buf
中,然后再写入到 AOF 文件中(此时还在系统内核缓存区未同步到磁盘),最后再根据持久化方式( fsync
策略)的配置来决定何时将系统内核缓存区的数据同步到硬盘中的。
只有同步到磁盘中才算持久化保存了,否则依然存在数据丢失的风险,比如说:系统内核缓存区的数据还未同步,磁盘机器就宕机了,那这部分数据就算丢失了。
AOF 文件的保存位置和 RDB 文件的位置相同,都是通过 dir
参数设置的,默认的文件名是 appendonly.aof
。
Redis线程模型
对于读写命令来说,Redis 一直是单线程模型。在 Redis 4.0 版本之后引入了多线程来执行一些大键值对的异步删除操作, Redis 6.0 版本之后引入了多线程来处理网络请求(提高网络 IO 读写性能)。
内存是有限的,如果缓存中的所有数据都是一直保存的话,分分钟直接 Out of memory。
Redis 中除了字符串类型有自己独有设置过期时间的命令
setex
外,其他方法都需要依靠expire
命令来设置过期时间 。另外,persist
命令可以移除一个键的过期时间。很多时候,我们的业务场景就是需要某个数据只在某一时间段内存在,比如我们的短信验证码可能只在 1 分钟内有效,用户登录的 Token 可能只在 1 天内有效。
如果使用传统的数据库来处理的话,一般都是自己判断过期,这样更麻烦并且性能要差很多。
常见的缓存更新策略
Cache Aside Pattern 旁路缓存模式
比较适合读比较多的场景。
Cache Aside Pattern中,服务端需要同时维系数据库和缓存,并且以db为准。
写步骤:
更新db
直接删除cache
Cache Aside Pattern 旁路缓存模式下,写数据的时候,为什么选择删除cache,而不是更新cache?
对服务端造成资源浪费
删除cache更直接,如果频繁修改db,就会导致需要频繁更新cache,而cache中的数据可能都没有被访问到。
产生数据不一致问题
并发场景下,更新cache产生数据不一致问题的概率会更大。
在写数据的过程中,可以先删除cache,后更新db吗?
不行,可能会造成数据库和缓存数据不一致。
- 请求1先把cache中的A数据删除;
- 请求2从db中读取数据;
- 请求1再把db中的A数据更新。
在写数据的过程中,先更新db,后删除cache就没问题了吗?
还是可能出现数据不一致的问题,不过概率很小,因为缓存写入速度比数据库写入速度快很多。
- 请求1从db中读取数据 (说明此时cache没有缓存该数据)
- 请求2更新db中的数据,删除缓存
- 请求1将读取的数据写入cache (步骤2很难比步骤3快,因为写数据库一般会先加锁)
读步骤:
- 从cache中读,读取到直接返回
- cache中读取不到,就从db读
- 把db中读取到的数据放到cache中
Cache Aside Pattern 旁路缓存模式 的缺陷:
首次请求的数据一定不在cache中。
可以将热点数据提前放入cache。
如果写操作比较频繁,会导致cache中的数据被频繁删除,影响缓存命中率。
数据库和缓存数据强一致
加一个锁/分布式锁,更新db的时候,同样更新cache,保证不存在线程安全问题。
短暂地允许数据库和缓存数据不一致
更新db的时候,同样更新cache,给缓存加一个比较短的过期时间,即使数据不一致,影响也比较小。
Read/Write Through Pattern 读写穿透模式
读写穿透模式中,服务端把cache视为主要数据存储,从中读取数据并将数据写入其中。cache服务负责将此数据读取和写入db,从而减轻应用程序的职责。
这个模式在平时开发过程中非常少见,大概率是因为Redis没有提供cache将数据写入db的功能。
Read-Through Pattern实际只是在Cache Aside Pattern之上进行了封装。在Cache
Aside Pattern下,发生读请求的时候,如果cache中不存在对应的数据,是由客户端自己负责把数据写入cache,而Read Through Pattern则是cache服务自己来写入缓存的,这对客户端是透明的。
Write Behind Pattern 异步缓存写入模式
Write Behind Pattern 异步缓存写入模式 和 Read/Write Through Pattern 读写穿透模式很相似,两者都是由cache服务负责cache和db的读写。
但是读写穿透模式同步更新cache和db,而异步缓存写入模式只是更新缓存,不直接更新db,改为异步批量的方式更新db。
这对数据一致性带来了更大的挑战,比如cache数据可能还没异步更新db,但cache服务就挂掉了。
这种策略在我们平时开发过程中也非常非常少见,但是不代表它的应用场景少。比如消息队列中消息的异步写入磁盘、MySQL的Innodb Buffer Pool机制都用到了这种策略。
Write Behind Pattern下db的写性能非常高,非常适合一些数据经常变化又对数据一致性要求没那么高的场景,比如浏览量、点赞量。