简介
Redis是一款基于内存的数据库,具有持久化功能,使用key-value形式进行数据存储。在后端可通过编码的方式设置key-value的有效期,比如token存储。
数据类型
Redis使用key-value进行存储,其中value可支持5种基本数据类型:
| 类型 | 含义 | 应用 | | --- | --- | | --- | | String | 字符串 | 常规存储 | | List | 列表 | 多条数据记录 | | Hash | 哈希 | 购物车<name,number> | | Set | 唯一集合 | 点赞 | | ZSet | 有序唯一集合 | 排行榜 |
过期策略
Redis拥有一套自己的内存淘汰策略。如果一个key过期了,其在内存中未必立刻删除。
惰性删除
每次访问到这个key的时候才判断是否过期,如果过期则立刻删除,不返回任何值。
定期删除
Redis将每个设置了过期时间的key存放到一个字典中,之后定期遍历这个字典删除过期的key。 Redis默认每100ms扫描一次字典,但不会遍历字典中的所有key,而是使用贪心策略:
- 从过期字典中随机抽取20个key
- 删除这20个key中已经过期的key
- 如果过期的key超过25%,则重复步骤1
定期删除会导致很多过期的key没有被删除,白白浪费内存空间。于是Redis使用惰性删除+定期删除结合的方式,尽可能去回收过期的内存。
但是,这样的方式也可能出现问题,如果定期删除没有扫描到过期的key且后续操作也没有访问到过期的key,那么这个过期的key仍旧不会被删除。因此就需要Redis的内存淘汰策略。
内存淘汰策略
| 策略 | 描述 |
|---|---|
| noeviction | 内存满时,新写入数据则报错 |
| allkeys-lru | 内存满时,移除最近最少使用的key |
| allkeys-random | 内存满时,随机删除key |
| volatile-lru | 内存满时,在设置过期时间的key中,移除最近最少使用的key |
| volatile-random | 内存满时,在设置过期时间的key中,随机删除key |
| volatile-ttl | 内存满时,在设置过期时间的key中,删除马上要过期的key |
LRU 算法
最近最少使用算法,一种常见的页面置换算法,把最近最久未使用的数据淘汰。
设计思想:
- 缓存必须有读和写两种操作,且时间复杂度均为Q(1)
- 存入的数据必须有顺序之分,区分最近使用和很久未使用的数据
经过分析,哈希链表这种数据结构可满足上述设计思想,也就是哈希表+双向链表。 可使用LinkedHashMap实现缓存,核心思想:新值插入头部,旧值从尾部移除。
持久化
Redis具有持久化功能,目的是避免客户端突然宕机,将内存中的数据持久化到磁盘中。
Redis持久化的两种方式:快照(RDB)和追加文件(AOF)
RDB
在指定的时间间隔内将内存中的数据集快照写入磁盘,数据恢复时将快照文件直接读入到内存里。 Redis会单独创建一个子进程进行持久化,子进程将数据持久化到一个临时文件中,持久化结束后用这个临时文件替换上次持久化好的文件。整个过程中主进程不进行任何的io操作,保障了性能。
优点:
- 只有一个dump.rdb文件,持久化高效
- 性能高,使用子进程完成持久化,主进程不参与持久化工作
- 启动效率高于AOF
缺点:隔一段时间持久化,可能会造成两个持久化之间的数据丢失(丢失最后一次)
AOF
利用日志来记录所有的写操作,也就是记录所有的写指令,文件只允许追加不允许修改。Redis启动后会读取这个文件重新加载数据,也就是Redis启动后重新执行所有写操作,完成数据的加载。
优点:
- 安全性高,可保证每次写操作都会被记录
- 自带重写机制,可缩小文件
缺点:
- AOF文件比RDB文件大,数据恢复慢
- AOF比RDB的启动效率低
AOF重写:如果写操作过多,AOF文件越来越大,超过某一阈值时,Redis就会启动AOF的内容压缩机制,保存可恢复数据的最小指令集。也就是通过指令bgrewriteaof完成对AOF文件的压缩。 原理:当AOF文件过大时,创建子进程去重写文件(并不是读取旧的AOF文件,而是根据内存中的数据去写新的指令)。 触发机制:如果当前AOF文件是上次AOF文件大小的2倍且文件大小超过64MB时触发重写操作。
两种持久化方式结合
Redis可以选择同时开启两种持久化机制,RDB主要用于备份,而AOF主要用于恢复。
事务
指令
MULTI:开启事务,返回ok。MUTIL执行后,客户端可向服务器发送任意多条指令,这些指令会被放到一个任务队列中,不会立刻执行。当EXEC指令被调用后,上述指令才被执行。
DISCARD:清空任务队列,放弃执行事务。
WATCH:使得EXEC需要有条件地执行,即事务只能在所有被监视键不被修改的情况下执行。
特性
原子性:某一指令执行失败,不影响其余指令的执行,不具备原子性。
隔离性:事务提交前指令不会执行,没有隔离级别之分,具备严格的单个事务隔离性。
Redis缓存问题
如何保证缓存与数据库一致性
如果进行读操作,则直接去缓存读取即可。但如果进行写操作,则会出现主存与缓存不一致的问题(这里管数据库叫主存)。
解决主存和缓存不一致的方式有多种,大多数情况采用删除缓存策略来保证数据一致性。原因包括但不仅限于以下两种:
- 高并发场景下,无论是先更新主存还是更新缓存,都可能造成数据不一致问题,直接删除会方便很多
- 若每次修改主存后都更新缓存,则性能开销比较大,不如直接删除,等再次读取时,若缓存没有,则去主存里读,再将主存中的数据更新到缓存
雪崩
缓存雪崩是指缓存同一时间大面积失效,后续的请求全都落在数据库上,数据库压力过大可能会崩掉。 解决方案:
- 随机设置缓存的过期时间,避免大量数据同一时间崩掉
- 如果竞争不算激烈,可考虑给key加锁
- 添加缓存标记,记录缓存是否失效,若失效则更新缓存
缓存穿透
缓存和数据库都没有数据,所有请求都落在数据库上,数据库崩掉。 解决方案:
- 使用布隆过滤器,将所有可能存在的数据哈希到一个足够大的表中,那么一个一定不存在的数据会被这个表过滤掉,减轻数据库层的压力
- 若主存和缓存都取不到数据,则将key-value更新为key-null,防止用户短期内用同一id去攻击系统
缓存击穿
缓存中没有数据但数据库中有,大量用户去数据库读数据,数据库崩掉。缓存击穿强调一个key被频繁访问,在不停承担大量请求,大量的并发操作集中与这一个key。在这个key失效的瞬间,持续的大量的并发直接落到数据库上,也就是在这个key上击穿了缓存。而雪崩是指缓存大面积过期。
缓存预热
缓存预热是指系统上线后,将相关的缓存数据直接加载到缓存系统,可避免用户先查数据库再进行缓存的问题。用户直接查询事先被预热的缓存数据。