Reference: http://redis.cn
用处
缓存
数据库
分布式锁(Redission的redlock,自定义的lock等)
过滤器(布隆过滤器/增强的带计数的布隆过滤器/布谷鸟过滤器等)
大规模的计算辅助(bitmap)
消息订阅/监听 --> 例如分布式的websocket发送消息时可用Redis消息订阅/监听将消息发到所有实例上进行推送
延时队列 --> 例如email发送服务中同一个email client的多封邮件发送需要一定间隔
Redis介绍及NIO原理
Redis是二进制安全的,在IO时都为字节流形式。
Redis单线程但能支持高并发的原因:多路复用。多路复用通常有epoll,select,poll等方案,默认为epoll。
epoll能大大增加处理效率原因:用户态和内核态之间提供了一个共享空间,主要有两个数据结构,一个红黑树与一个双向链表。在新的fd(file description/文件描述符)请求进来时,会增添进红黑树,处理完成后进入链表。因此在用户态与内核态通信时无需进行fd的复制,且红黑树查询效率高,用户态只需扫描链表中是否还有数据而不用扫描整个fd list。
Redis的类型
共5种类型:string,list,hash,set,sorted list
type命令能根据key获得值的类型,而object encoding命令可获得redis在内部表示形式的类型。例如string类型会让部分数字value的encoding类型改为int以优化计算效率。
bitmap是string类型中的一种非常适合做大数据处理的数据结构,例如做签到统计、登陆天数计算、大量号码去重等。主要思想是利用每一个二进制位的0/1标志及每一位的index实现用最小空间小号完成统计。
可在redis client中使用 help @<数据类型> 命令查看对应的所有方法。例如 help @list
list类型数据结构为双向链表,有正向索引与反向索引。因此可从左右两边push/pop数据以实现FIFO/FILO;能够进行阻塞pop操作,当没有数据出现时阻塞x秒或一直阻塞下去。
sorted set的存储方式是跳跃表(skip list),类似于平衡二叉树,每层节点以双向链表关联,能达到平均CRUD耗时最少。增加元素时的层数为随机造层得到。
Redis的消息订阅与事务
消息订阅的监听方知能收到监听之后过来的消息,之前publish的消息都不会收到。
Redis不支持事物回滚,因为只有当命令错误时才会失败。为了性能考虑,事务失败不回滚而是继续执行余下命令。
Redis过滤器
Redis缓存过滤器为解决缓存穿透问题而生。基础思想是:把系统中已有的数据通过多个映射函数计算出几个值并向bitmap中标记,在请求时使用相同算法去砸后bitmap对应结果的位置。如果都为1则能获得“此数据可能存在”的结论,可以继续向下寻找;如果有一个为0则获得“数据必不存在”的结论。
布隆过滤器优缺点:
优点:占用空间少,使用较简单
缺点:随着数据增加,误判率增加;不能从布隆过滤器中删除元素
增强版计数器布隆过滤器(Counting Bloom Filter)能解决不能删除过滤器中元素的问题。它使用了更多空间记录了每一个位的计数,删除时所有对应位数字减一。消耗空间增加但能实现精准删除。
布谷鸟过滤器:每个元素通过算法得到一组对偶节点,存元素指纹进入其中随机一个空节点。如果都不为空则随机挤走一个,被挤走的重复计算-->挤走操作直到成功进入节点。重复过程有次数阈值限制,达到阈值则自动扩容,所有元素重新计算。布谷鸟过滤器可实现删除,但不同数据在同一节点的指纹有小概率重复,因此删除操作有误删可能。
增强的布谷鸟过滤器:每个节点能存的指纹席位扩大到4个(类似hashmap中数组链表)
Reference: Redis布隆过滤器和布谷鸟过滤器_JavaShark的博客-CSDN博客_布谷鸟过滤器
Redis解决内存空间不足的多种方案(8种)
新加数据直接报错;
LRU(Least Recently Used)算法清除最久没有碰的数据 (它又拆分位两种细化方案:从所有的数据中找/只从设置了ttl的数据中找);
LFU(Least Frequently Used)算法清除碰到次数最少的数据(同上,它也拆分位两种细化方案:从所有的数据中找/只从设置了ttl的数据中找);
清除即将过期的数据;
随机清除数据释放空间(同上,它也拆分位两种细化方案:从所有的数据中找/只从设置了ttl的数据中找)。
Redis key的过期判定
如果key设置了ttl,不会随着访问延长ttl,但如果发生对此key的写操作,则改为无过期时间;
过期时间判定有两种:
当访问时被动判定,若过期则清除;
主动周期轮询的过期检测。Redis每10秒随机抽取20个keys进行过期检测并删除所有已过期key,若>25%的key过期,再取20个重复操作直到不满足25%。(这是一个平凡的概率算法,意味着在任何给定时刻,最多会清除25%的过期key)。
Redis持久化
两种方式,RDB与AOF。在4.0版本之后AOF中包含RDB,为RDB+增量的AOF写操作记录。
两种方式能同时开启,如果同时开启则使用AOF恢复。
RDB数据恢复速度较快但易丢更多数据,AOF体量岁时间增多变大,数据恢复慢但能丢失更少数据。
RDB:通过SAVE或BGSAVE命令(BGSAVE不阻塞)。使用rdbsave和Linux的fork()方法,底层是copy-on-write导致速度快:创建子进程持久化数据,父子进程公用同一份无力存储空间,只有当数据发生改变时,改变的那个内存页会复制一份,让父子中数据出现差别,因此额外空间开销小。具有时点性,RDB进程创建那一刻的样子就是最后复制出来的样子。后续的数据改变会存入复制缓冲区中。
AOF将redis的写操作存入缓存中,按规则(每秒一次/每个操作一次等)刷进文件中,保存的是可读的写操作命令。每次达到一定存储量时自动使用BGREWRITEOF命令优化合并AOF命令,此过程称为重写命令,也是通过fork一个子进程实现的,重写完成直接覆盖更新,并把重写过程中缓存的新命令刷入文件。
在新版中支持RDB+AOF混合存储方式:老数据RDB生成二进制文件在AOF文件的开头,增量以指令形式Append到AOF文件。
Redis高可用系列
主从,哨兵和集群实现了redis高可用;
Redis每台服务器都有一个唯一的runid来标识对应服务器;
Offset记录了命令的偏移量。
Redis有4类缓冲区增强高可用。
Redis主从
将主节点的数据复制到从Redis服务器。主要为了解决单台Redis读写压力大的问题,实现读写分离(主:读写;从:只读);并达到数据备份的目的。
第一次连接从服务器或从服务器断开后进行重链接都会进行主从复制,从服务器会记录主服务器的runid。
主从复制分为全量复制和增量复制。选择全量/增量复制的流程图如下
![](https://i-blog.csdnimg.cn/blog_migrate/7ea6bcbc9a80f3289d28f345c382fc28.png)
全量复制流程:
⚠️Tips: 主节点上会为每个从节点都维护一个复制缓冲区。在全量复制时,主节点在向从节点传输 RDB 文件的同时,会继续接收客户端发送的写命令请求,并保存在复制缓冲区中,等 RDB 文件传输完成后,再发送给从节点去执行。
![](https://i-blog.csdnimg.cn/blog_migrate/26e381753ec49a38c0dd94c9a69385ae.png)
增量复制流程:
⚠️Tips: 复制积压缓冲区是一个固定长度,先进先出的队列,默认 1MB。当主服务器进行命令传播时,不仅会将命令发送给从服务器,还会发送给这个缓冲区。
![](https://i-blog.csdnimg.cn/blog_migrate/49748ffa24c52e34b6cf1173c1760c70.png)
Redis的四类缓冲区
客户端输入缓冲区:暂存客户端发送过来的请求数据;Redis 主线程再从输入缓冲区中读取命令进行处理。大量数据高速请求或执行耗时操作会导致生产速度大于消费速度,最终溢出。
客户端输出缓冲区:redis处理后的输出不会直接给客户端而是放进输出缓冲区,防止客户端消费慢阻塞redis。若服务端大量数据快速处理后返回导致消费速度大于客户端接受速度,导致溢出。可以通过`client-output-buffer-limit`参数来调整缓冲区大小。
复制缓冲区:主节点上会为每个从节点都维护一个复制缓冲区。在全量复制时,主节点在向从节点传输 RDB 文件的同时,会继续接收客户端发送的写命令请求,并保存在复制缓冲区中,等 RDB 文件传输完成后,再发送给从节点去执行。若从节点接收和加载 RDB 较慢,同时主节点接收到了大量的写命令则溢出,也可通过`client-output-buffer-limit`参数来调整缓冲区大小。
复制积压缓冲区:为增量主从复制服务。它是一个固定长度,先进先出的队列,默认 1MB。当主服务器进行命令传播时,不仅会将命令发送给从服务器,还会发送给这个缓冲区。不会溢出。可通过`repl_backlog_size`参数调整大小。
Redis哨兵模式
在主从模式中如果主机连接中断,则需要将某一个从机升级为主机,在原主机恢复时设置为从机。哨兵模式能自动化此操作;
通常多台哨兵一起工作,并且哨兵之间也互相监控检测形成多哨兵模式。
原理是哨兵模式每秒向所有redis节点发送ping命令检测返回来判断是否在线。当一台哨兵redis检测到某机器不可用,称为主观下线。当一定数量(通常配置为过半)哨兵检测到机器不可用,则哨兵进行一次投票,由一台哨兵进行故障转移,成功后发布订阅消息,让各节点修改配置切换到新的主服务器,称为客观下线。
客户端来连接 Redis 集群时,会首先连接 Sentinel,通过 Sentinel 来查询主节点的地址,然后再去连接主节点进行数据交互。当主节点发生故障时,客户端会重新向 Sentinel 要地址,Sentinel 会将最新的主节点地址告诉客户端。因此应用程序无需重启即可自动完成主从节点切换。
选择新的主节点通常先找优先级最大的,然后找偏移量最大的,如果这都相同就选runid最小的。这样能让原主节点和新节点差异最小。
Redis集群
集群能解决高并发写入的问题,且解决持久化耗时因占用空间大增加太多的问题,使用的是数据分片思想。
3种分片方案:
客户端分区,在客户端就已经决定数据会被存储到哪个redis节点或者从哪个redis节点读取。大多数客户端已经实现了客户端分区。
代理分区,意味着客户端将请求发送给代理,然后代理决定去哪个节点写数据或者读数据。代理根据分区规则决定请求哪些Redis实例,然后根据Redis的响应结果返回给客户端。redis和memcached的一种代理实现就是Twemproxy
查询路由(Query routing),客户端随机地请求任意一个redis实例,然后由Redis将请求转发给正确的Redis节点。Redis Cluster实现了一种混合形式的查询路由,但并不是直接将请求从一个redis节点转发到另一个redis节点,而是在客户端的帮助下直接redirected到正确的redis节点。
Redis Cluster使用CRC16 mod 16384算法计算出所引射到的slot(哈希槽),每个分片维护一部分slot。集群中每个实例都知道其他实例与slot的映射关系。
Redis缓存击穿、穿透与雪崩
缓存击穿通常是高并发下某redis缓存过期导致大量请求并发访问数据库。我们需要尽量使这类热数据在redis中维持不过期;或在缓存过期后使用带有效期的分布式锁去再次从DB中拿到数据并存入缓存,这样只会出现一个线程进行数据库请求,其他没有获得锁的线程需要等待后再次重复从缓存中获取数据的过程。
缓存穿透通常是因为收到大量连DB也不存在当条数据的请求。可增加API参数验证限制不合理的参数传入,并且根据需求使用过滤器(布隆过滤器/布谷鸟过滤器等),最后将仍旧穿透的空数据也缓存下来。
缓存雪崩通常是由于大量缓存同时过期导致。如果缓存不要求时点性(即必须在某时刻过期,例如每天00:00必须过期),则可以给缓存过期时间做一个小的偏移量以达到一批缓存不会同时过期;如果要求时点性,可以在业务逻辑上略做微延迟以减少高并发。
分布式锁
setnx可以实现分布式锁,但我们使用Redission做分布式锁是因为它有一个守护线程watchdog,能保证在被锁住的业务逻辑未完成时不会释放锁资源。
Redission使用redlock,能在分布式体系中更稳定的获取锁状态。只要多于一半的redis主从实例获取到锁则认为锁成功获取,否则获取锁失败并自动解锁。