1. 应用场景
缓存:根据键值过期时间设置
请求频率限制:比如短信验证码120秒内只能发送一次,则将标志性的key-value键值对设置过期时间为120秒,用户请求的时候判断一下【SET key value EX 120 NX】
排行榜:利用zset数据类型
计数器:利用 INCR KEY 命令,key不存在初始化为0,存在则自增1
用户爱好:利用set集合,其特有的命令方法能够计算用户共同爱好
消息队列:利用list数据类型的lpush和brpop
分布式锁:利用 SET key value [EX seconds] [PX milliseconds] [NX|XX]
2.速度快的原因
- 纯内存访问
- 底层I/O用多路复用技术epoll实现
- 单线程避免了线程切换和竞争
3. 数据类型
- 字符串(String):内部编码【8个字节长整型:int】【小于等于39个字节的字符串:embstr】【大于39个字节的字符串:raw】
- 哈希(Hash):内部编码【元素个数小于512个并且每个值都小于64字节:ziplist-压缩列表】【否则是hashtable-哈希表】
- 列表(List):内部编码【元素个数小于512个并且每个值都小于64字节:ziplist-压缩列表】【否则是linkedlist-链表】
- 集合(Set):内部编码【元素都是整数并且个数小于512个:intset-整数集合】【否则是hashtable-哈希表】
- 有序集合(ZSet):内部编码【元素个数小于128个并且每个值都小于64字节:ziplist-压缩列表】【否则是skiplist-跳跃表】
4. 持久化-RDB
触发:save或者bgsave命令。不同的是save会阻塞服务器,直到持久化完成;bgsave会fock子进程,子进程负责完成持久化的任务,这种只在fock的时候会阻塞很短一部分时间。
RDB持久化流程:
- 执行bgsave命令,如果已存在相关的子进程,则直接返回。
- 父进程执行fock操作创建子进程。fock完成后不再阻塞父进程。
- 子进程创建RDB文件,根据父进程的内存生成临时快照文件,完成后对原有文件进行原子替换。
- 最后给父进程发送完成信号,父进程更新统计信息。
Redis默认采用LZF算法对生成的RDB文件做压缩处理,压缩后的文件远远小于内存大小。
5. 持久化-AOF
工作流程:
- 命令写入:所有的写入命令都会追加到AOF缓冲区(处于性能的考量,不追加到硬盘)
- 文件同步:缓冲区根据对应的策略向硬盘做同步操作(策略:always,everysec,no,默认everysec)
- 文件重写:AOF文件越来越大,所以定期对AOF文件进行重写,达到压缩的目的(无效命令比如del、set,文件只保留最终数据的写入命令;多条命令合并成一条)
- 重启加载:Redis服务器重启后,会根据AOF文件进行数据恢复
AOF重写流程:
首先主进程会fock一个子进程。Redis会维护一个AOF重写缓冲区,该缓冲区会在子进程创建新的AOF文件期间,记录服务器执行的所有写命令。当子进程完成创建新AOF文件之后,服务器会将重写缓冲区中的所有内容追加到新的AOF文件的末尾。最后,服务器用新的AOF文件替换旧的AOF文件,以此来完成AOF文件重写操作。
如果同时配置了RDB和AOF,则Redis会优先加载AOF
6. 数据分区
分布式环境下要解决把数据映射到多个节点的问题。
A:节点取余分区
【对key取hash】%【节点数量】,来决定数据打到哪一个节点上。
B:一致性哈希分区
为系统中每个节点分配一个token,范围为2的32次幂,这些token构成一个hash环。读写数据的时候,根据key取hash,然后顺时针找到第一个大于等于该hash值的token所对应的节点。
一致性哈希存在的问题:
- 加减节点会导致一部分数据无法命中,需要手动处理或者忽略,所以一致性哈希常用语缓存场景。
- 节点比较少的情况,节点发生变化将影响很大一部分数据命中,所以不适合节点少的情况。
C:虚拟槽分区
Redis Cluser采用虚拟槽分区,所有的键根据哈希函数映射到0~16383整数槽内,计算公式:slot=CRC16(key)&16383。每一个节点负责维护一部分槽以及槽所映射的键值数据
优点:解耦数据和节点之间的关系,简化了节点扩容和收缩难度。
7. 集群功能限制
不支持批量操作比如:mget,mset
不支持多数据库空间,即只有一个db0
8. 缓存穿透
原因:请求不存在的数据,而导致每次请求都要跳过缓存直接去存储层拿数据,失去了缓存保护后端存储的意义。
解决:
- 方法一:存储层不命中,依然缓存空对象,之后再访问这个数据就会从缓存中获取。可是:如果是恶意攻击,意味着这部分无意义的数据会占用内存,可以通过设置过期时间来缓解。
- 方法二:利用布隆过滤器把存在的key缓存起来,作为访问的第一层,之后才是缓存和数据库,如果布隆过滤器认为请求的key不存在,则不会访问数据库,进而保护了存储层。
- 比较:存储空对象适用于:数据频繁变化实时性高的场景。布隆过滤器适用于:数据相对固定实时性低的场景。
9. 缓存击穿
原因:比如有个热点key承载了大量请求,在key失效的一瞬间,大量请求跳过缓存直接去请求了数据库
解决:
- 方法一:一般是因为做促销活动导致key成为这种热点,对于这类key要么设置过期时间长一些以至于熬过高峰期,要么直接设置成永不过期。
- 方法二:加互斥锁,即如果缓存不存在,则加锁去读取数据库并更新缓存,保证失效的时候只有一个线程能去访问数据库。
10. 缓存雪崩
原因:
- 在同一时间点,承载大量请求的缓存集中失效,大量请求会到达存储层,进而造成存储层压力过大而宕机。
- 缓存层由于异常不能提供服务后,大量请求会到达存储层,进而造成存储层压力过大而宕机。
解决:
- 保证缓存服务高可用
- 将key的过期时间设置随机,这样避免同一时间大量失效
11. 淘汰策略(6种)
这6种策略的前提都是:当内存不足以容纳新写入数据时
noeviction:新写入操作会报错(默认策略)
范围:在主键空间中
allkeys-lru:移除最近最少使用的key
allkeys-random:随机移除某个key
范围:在设置了过期时间的键空间中
volatile-lru:移除最近最少使用的key
volatile-random:随机移除某个key
volatile-ttl:有更早过期时间的key优先移除