Redis笔记整理
内容参考微信读书《redis设计与实现》 《redis核心设计》 极客时间专栏18 | 波动的响应延迟:如何应对变慢的Redis?(上) (geekbang.org)
redis数据类型
常见的种类有
String(字符串)、List(列表)、Hash(哈希)、Set(集合)和 Sorted Set(有序集合)
hash: 解决哈希冲突的方式,就是链式哈希。同一个哈希桶中的多个元素用一个链表来保存,它们之间依次用指针连接,采用类似go map渐进式扩容机制
List ,hash,set ,sorted set被统称为集合类型,一个键对应了一个集合的数据
string:简单动态字符串
list:双向链表,压缩列表
hash:压缩列表,哈希表
set:哈希表,整数数组
Sorted Set:压缩列表,跳表
补充的种类有
HyperLogLog bitmap(位图) GEO(地理坐标) stream(流)
类型的应用
- 增量更新一致性:EXPIRE、ZADD/HSET 等,保证索引结构体务必存在的情况下去操作新增数据;
- BITSET: 存储每日登陆用户,单个标记位置(boolean),为了避免单个 BITSET 过大或者热点,需要使用 region sharding,比如按照mid求余 %和/ 10000,商为 KEY、余数作为offset;
- List:抽奖的奖池、顶弹幕,用于类似 Stack PUSH/POP操作;
- Sortedset: 翻页、排序、有序的集合,杜绝 zrange 或者 zrevrange 返回的集合过大;
- Hashs: 过小的时候会使用压缩列表、过大的情况容易导致 rehash 内存浪费,也杜绝返回hgetall,对于小结构体,建议直接使用 memcache KV;
- String: SET 的 EX/NX 等 KV 扩展指令,SETNX 可以用于分布式锁、SETEX 聚合了SET + EXPIRE; Sets: 类似 Hashs,无 Value,去重等;
- 尽可能的 PIPELINE 指令,但是避免集合过大; 避免超大 Value
Redis是单线程
主要是指 Redis 的网络 IO 和键值对读写是由一个线程来完成的,这也是 Redis 对外提供键值存储服务的主要流程。
Redis为什么这么快?
1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
2、数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;
3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
4、使用多路I/O复用模型,非阻塞IO;这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程
5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
Redis的并发竞争问题如何解决?
单进程单线程模式,采用队列模式将并发访问变为串行访问。Redis本身没有锁的概念,Redis对于多个客户端连接并不存在竞争,利用setnx实现锁。
Redis 中设置过期时间主要通过以下四种方式
expire key seconds:设置 key 在 n 秒后过期;
pexpire key milliseconds:设置 key 在 n 毫秒后过期;
expireat key timestamp:设置 key 在某个时间戳(精确到秒)之后过期;
pexpireat key millisecondsTimestamp:设置 key 在某个时间戳(精确到毫秒)之后过期;
Reids三种不同删除策略
定时删除:在设置键的过期时间的同时,创建一个定时任务,当键达到过期时间时,立即执行对键的删除操作
惰性删除:放任键过期不管,但在每次从键空间获取键时,都检查取得的键是否过期,如果过期的话,就删除该键,如果没有过期,就返回该键
定期删除:每隔一点时间,程序就对数据库进行一次检查,删除里面的过期键,至于要删除多少过期键,以及要检查多少个数据库,则由算法决定。
定时删除
优点:对内存友好,定时删除策略可以保证过期键会尽可能快地被删除,并释放国期间所占用的内存
缺点:对cpu时间不友好,在过期键比较多时,删除任务会占用很大一部分cpu时间,在内存不紧张但cpu时间紧张的情况下,将cpu时间用在删除和当前任务无关的过期键上,影响服务器的响应时间和吞吐量
定期删除
由于定时删除会占用太多cpu时间,影响服务器的响应时间和吞吐量以及惰性删除浪费太多内存,有内存泄露的危险,所以出现一种整合和折中这两种策略的定期删除策略。
定期删除策略每隔一段时间执行一次删除过期键操作,并通过限制删除操作执行的时长和频率来减少删除操作对CPU时间的影响。
定时删除策略有效地减少了因为过期键带来的内存浪费。
惰性删除
优点:对cpu时间友好,在每次从键空间获取键时进行过期键检查并是否删除,删除目标也仅限当前处理的键,这个策略不会在其他无关的删除任务上花费任何cpu时间。
缺点:对内存不友好,过期键过期也可能不会被删除,导致所占的内存也不会释放。甚至可能会出现内存泄露的现象,当存在很多过期键,而这些过期键又没有被访问到,这会可能导致它们会一直保存在内存中,造成内存泄露。
Reids6种淘汰策略:
• noeviction: 不删除策略, 达到最大内存限制时, 如果需要更多内存, 直接返回错误信息。大多数写命令都会导致占用更多的内存(有极少数会例外。
• allkeys-lru:所有key通用; 优先删除最近最少使用(less recently used ,LRU) 的 key。
• allkeys-random:所有key通用; 随机删除一部分 key。
• volatile-random: 只限于设置了 expire 的部分; 随机删除一部分 key。
• volatile-lru:只限于设置了 expire 的部分; 优先删除最近最少使用(less recently used ,LRU) 的 key。
• volatile-ttl: 只限于设置了 expire 的部分; 优先删除剩余时间(time to live,TTL) 短的key。
Redis 持久化方案:
AOF持久化
AOF 是以文件的形式在记录接收到的所有写命令。随着接收的写命令越来越多,AOF 文件会越来越大。这个时候aof会采取重写回传机制 重写后的文件会变小
三种写回策略其实,对于这个问题,AOF 机制给我们提供了三个选择,也就是 AOF 配置项 appendfsync 的三个可选值。
Always,同步写回:每个写命令执行完,立马同步地将日志写回磁盘;
Everysec,每秒写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘;
No,操作系统控制的写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘;
RDB
内存快照。所谓内存快照,就是指内存中的数据在某一个时刻的状态记录
Redis 提供了两个命令来生成 RDB 文件,分别是 save 和 bgsave。
save:在主线程中执行,会导致阻塞;
bgsave:创建一个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,这也是 Redis RDB 文件生成的默认配置。好了,这个时候,我们就可以通过 bgsave 命令来执行全量快照,
这既提供了数据的可靠性保证,也避免了对 Redis 的性能影响。
RDB和AOF的优缺点
RDB持久化
优点:RDB文件紧凑,体积小,网络传输快,适合全量复制;恢复速度比AOF快很多。当然,与AOF相比,RDB最重要的优点之一是对性能的影响相对较小。
缺点:RDB文件的致命缺点在于其数据快照的持久化方式决定了必然做不到实时持久化,而在数据越来越重要的今天,数据的大量丢失很多时候是无法接受的,因此AOF持久化成为主流。此外,RDB文件需要满足特定格式,兼容性差(如老版本的Redis不兼容新版本的RDB文件)。
AOF持久化
与RDB持久化相对应,AOF的优点在于支持秒级持久化、兼容性好,缺点是文件大、恢复速度慢、对性能影响大。
持久化策略选择
(1)如果Redis中的数据完全丢弃也没有关系(如Redis完全用作DB层数据的cache),那么无论是单机,还是主从架构,都可以不进行任何持久化。
(2)在单机环境下(对于个人开发者,这种情况可能比较常见),如果可以接受十几分钟或更多的数据丢失,选择RDB对Redis的性能更加有利;如果只能接受秒级别的数据丢失,应该选择AOF。
(3)但在多数情况下,我们都会配置主从环境,slave的存在既可以实现数据的热备,也可以进行读写分离分担Redis读请求,以及在master宕掉后继续提供服务。
RDB持久化的触发分为手动触发和自动触发两种。
Redis 开启AOF
Redis服务器默认开启RDB,关闭AOF;要开启AOF,需要在配置文件中配置:appendonly yes
AOF常用配置总结
下面是AOF常用的配置项,以及默认值;前面介绍过的这里不再详细介绍。
• appendonly no:是否开启AOF
• appendfilename “appendonly.aof”:AOF文件名
• dir ./:RDB文件和AOF文件所在目录
• appendfsync everysec:fsync持久化策略
• no-appendfsync-on-rewrite no:AOF重写期间是否禁止fsync;如果开启该选项,可以减轻文件重写时CPU和硬盘的负载(尤其是硬盘),但是可能会丢失AOF重写期间的数据;需要在负载和安全性之间进行平衡
• auto-aof-rewrite-percentage 100:文件重写触发条件之一
• auto-aof-rewrite-min-size 64mb:文件重写触发提交之一
• aof-load-truncated yes:如果AOF文件结尾损坏,Redis启动时是否仍载入AOF文件
Redis 的主从复制
一旦主从库完成了全量复制,它们之间就会一直维护一个网络连接,主库会通过这个连接将后续陆续收到的命令操作再同步给从库,这个过程也称为基于长连接的命令传播
持久化保证了即使redis服务重启也不会丢失数据,因为redis服务重启后会将硬盘上持久化的数据恢复到内存中,但是当redis服务器的硬盘损坏了可能会导致数据丢失,如果通过redis的主从复制机制就可以避免这种单点故障,
复制是高可用Redis的基础,哨兵和集群都是在复制基础上实现高可用的。复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复。缺陷:故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制。
分布式环境下非常容易出现缓存和数据库间的数据一致性问题,针对这一点的话,只能说,如果你的项目对缓存的要求是强一致性的,那么请不要使用缓存。我们只能采取合适的策略来降低缓存和数据库间数据不一致的概率,而无法保证两者间的强一致性。合适的策略包括 合适的缓存更新策略,更新数据库后要及时更新缓存、缓存失败时增加重试机制,例如MQ模式的消息队列。
布隆过滤器
bloomfilter就类似于一个hash set,用于快速判某个元素是否存在于集合中,其典型的应用场景就是快速判断一个key是否存在于某容器,不存在就直接返回。布隆过滤器的关键就在于hash算法和容器大小
Redis分布式
redis支持主从的模式。原则:Master会将数据同步到slave,而slave不会将数据同步到master。Slave启动时会连接master来同步数据。这是一个典型的分布式读写分离模型。我们可以利用master来插入数据,slave提供检索服务。这样可以有效减少单个机器的并发访问数量
读写分离模型
通过增加Slave DB的数量,读的性能可以线性增长。为了避免Master DB的单点故障,集群一般都会采用两台Master DB做双机热备,所以整个集群的读和写的可用性都非常高。读写分离架构的缺陷在于,不管是Master还是Slave,每个节点都必须保存完整的数据,如果在数据量很大的情况下,集群的扩展能力还是受限于单个节点的存储能力,而且对于Write-intensive类型的应用,读写分离架构并不适合。数据分片模型
为了解决读写分离模型的缺陷,可以将数据分片模型应用进来。
可以将每个节点看成都是独立的master,然后通过业务实现数据分片。
结合上面两种模型,可以将每个master设计成由一个master和多个slave组成的模型。
redis常见性能问题和解决方案:
Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
尽量避免在压力很大的主库上增加从库
Redis分布式锁实现
先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样?set指令有非常复杂的参数,这个应该是可以同时把setnx和expire合成一条指令来用的!
Redis做异步队列
一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。缺点:在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq等。能不能生产一次消费多次呢?使用pub/sub主题订阅者模式,可以实现1:N的消息队列。
缓存雪崩
大量数据同时失效会导致缓存雪崩
Redis 缓存实例发生故障宕机了,无法处理请求,这就会导致大量请求一下子积压到数据库层,从而发生缓存雪崩。
将到期时间打散可以解决,但并不是一个很好的高可用的解决方案,可以考虑限流降级?
缓存穿透解决方案
第一种方案是,缓存空值或缺省值。
第二种方案是,使用布隆过滤器快速判断数据是否存在,避免从数据库中查询数据是否存在,减轻数据库压力。
缓存击穿,热点数据永不过期
(27 封私信 / 25 条消息) 怎样实现redis分布式锁? - 知乎 (zhihu.com)
Redis缓存雪崩、缓存穿透、缓存击穿了,你该如何挽救 - 公众号_IT老哥 - 博客园 (cnblogs.com)
(2条消息) 导致Redis超时(Timeouts)常见问题_黎林果的专栏-CSDN博客_redis timeout
为什么 Redis 选择单线程模型 - 面向信仰编程 (draveness.me)
《进大厂系列》系列-Redis常见面试题(带答案) - 知乎 (zhihu.com)
全网最硬核 Redis 高频面试题解析(2021年最新版) - 知乎 (zhihu.com)