【八股文】Redis篇

目录

关键字

多路I/O复用模型、VM机制、文件事件处理器

1. 为什么要使用缓存

1. 高性能(查询性能比mysql好)

对于非实时变化的数据,查询mysql耗时需要300ms,存到缓存redis,每次查询仅仅1ms,性能瞬间提升百倍。

2. 高并发

mysql 单机支撑到2K QPS就容易报警了,如果系统中高峰时期1s请求1万,仅单机mysql是支撑不了的,但是使用缓存的话,单机支撑的并发量轻松1s几万~十几万。

原因是缓存位于内存,内存对高并发的良好支持

2. Redis的数据结构类型

应用场景:存对象(String和Hash的比较)

1. String类型

字符串对象是使用最广泛的类型。也是redis其他数据类型嵌套使用的对象类型。有三种编码方式:int、raw、embstr三种。String是最常用的一种数据类型,普通的key/value存储都可以归为此类, 不是java语言中的String,可以是字符串、整数或者浮点数。

1. 应用场景:

常规key-value缓存应用。常规计数: 微博数, 粉丝数(用incr,decr)

2. Hash类型

一个键值对集合。key是键,value相当于是一个map(String 类型的 fieldvalue 的映射表),hash 特别适合用于存储对象

1. 应用场景

1. 存对象的方式

  • key是对象的ID,value是对象json序列化后的结果

    以string的方式存储对象的缺点:增加了序列化/反序列化的开销,在需要修改其中一项信息时,需要把整个对象取回,并且修改操作需要对并发进行保护,引入CAS等复杂问题

  • key是对象的ID,value是一个Map(这个Map的key是成员的属性名,value是属性值)

    以Hash存储对象的优点:既不需要重复存储数据,也不会带来序列化和并发修改控制的问题,很好的解决了问题

3. List类型(是个双向链表)

List类型是按照插入顺序排序的字符串链表(类似java中的LinkedList),和数据结构中的普通链表一样,我们可以在其头部(left)和尾部(right)添加新的元素。在插入时,如果该键并不存在,Redis将为该键创建一个新的链表。与此相反,如果链表中所有的元素均被移除,那么该键也将会被从数据库中删除。

1. 应用场景

做消息队列(lpop和rpush)(详情见上文《如何使用redis做消息队列》)

4. Set类型

redis中的set就是常见的无序集合(通过hashtable实现的),特点就是包含的每个字符串都是独一无二各不相同的

常用操作:添加、获取、移除单个元素;检查一个元素是否存在于集合中,计算交集、并集、差集(这个是亮点,不然和Java的HashSet差不多)。从集合中随机获取元素

1. 主要功能

1. 检查一个元素是否存在于集合中

2. 交集、并集、差集

2. Set和List的区别

Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。

3. 应用场景

1. 实现接口幂等性设计

在分布式篇中的接口幂等性设计中,用set类型来存放请求的唯一识别,存进去代表可以调用请求

2. 使用交集,查看共同喜好等

在微博中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合(自动去重)。Redis还为集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中

5. ZSet类型(有序集合)

1. 实现方式

Redis sorted set的内部使用HashMap跳跃表(SkipList)来保证数据的存储有序HashMap里放的是成员到score(优先级)的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率(跳跃表的优点),并且在实现上比较简单。

2. 使用场景(TODO)

Redis sorted set的使用场景与set类似,区别是set不是自动有序的,而sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。当你需要一个有序的并且不重复的集合列表,那么可以选择sorted set数据结构,

比如twitter 的public timeline可以以发表时间作为score来存储,这样获取时就是自动按时间排好序的。

和Set相比,Sorted Set关联了一个double类型权重参数score,使得集合中的元素能够按score进行有序排列,redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数(score)却可以重复。

比如一个存储全班同学成绩的Sorted Set,其集合value可以是同学的学号,而score就可以是其考试得分,这样在数据插入集合的时候,就已经进行了天然的排序。

另外还可以用Sorted Set来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行

3. Redis的优缺点

1. 优点

  • 读写性能优秀:数据直接存放在内存里的

  • 支持数据持久化,支持AOF和RDB两种持久化方式

  • 支持主从复制,主机可以自动将数据复制到从机上,可以实现读写分离

  • 数据结构丰富,支持多种数据的存储,如:字符串String、散列Hash、列表List、集合set、有序集合zset等

2. 缺点

  • redis是内存数据库,所以单台机器存储的数据量跟机器本身的内存大小有关。虽然redis本身有key过期策略,但是还是需要提前预估和节约内存。如果内存增长过快,需要定期删除数据。
  • redis是单线程的,单台服务器无法充分利用多核服务器的CPU(美中不足?)
  • 修改配置文件,进行重启,将硬盘中的数据加载进内存,时间比较久。在这个过程中,redis不能提供服务。(没有像配置中心热部署一样的性质)

3. 题外话:redis为什么单线程 效率还这么高

1. 完全基于内存

绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中

2. 数据结构简单

对数据操作也简单,Redis中的数据结构是专门进行设计的

3. 采用单线程(不用CPU上下文切换、不用锁操作)

避免了CPU的上下文切换、资源竞争问题,不存在加锁释放锁操作,也没有死锁

4. 使用多路I/O复用模型(重要)

非阻塞IO(理解为监控室,忘了再百度以下)

1. 涉及的系统调用函数有哪些

selectpollepoll等,这些函数都可以同时监视多个描述符的读写就绪状况
在这里插入图片描述
图片来源:https://blog.51cto.com/u_12874079/2149260

2. 问:什么是多路I/O复用模型?

利用系统调用函数 ,使多个描述符的I/O操作都能在一个线程内并发交替地顺序完成(理解为监控室可以看到多个摄像头的视频,做统一监控)

这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程(单线程),redis同时对多个IO进行监控

参考相关链接:
什么是多路I/O复用模型
redis的多路复用是什么鬼

5. 使用VM机制(冷热数据分离)

使用VM机制作为底层模型,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求

白话文:VM机制就是可以实现冷热数据分离。使热数据仍在内存中,冷数据保存到磁盘。

4. 题外话:为什么Redis需要把所有数据放到内存中

Redis 为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以 redis 具有快速数据持久化的特征。如果不将数据放在内存中,磁盘 I/O 速度为严重影响 redis 的性能

5. 题外话:内存不够时,redis的运行速度会下降吗

不会,Redis使用到了VM机制,在redis.conf设置vm-enabled yes 即开启VM功能。 通过VM功能可以实现冷热数据分离。使热数据仍在内存中,冷数据保存到磁盘。这样就可以避免因为内存不足而造成访问速度下降的问题。

1. 不懂就问:冷热数据是什么?

  • 热数据:访问次数较多的数据,如点赞数,收藏数
  • 冷数据:访问很少或者从不访问的数据

需要注意的是只有热点数据,缓存才有价值对于冷数据而言,大部分数据可能还没有再次访问到就已经被挤出内存,不仅占用内存,而且价值不大。数据更新前至少读取两次,缓存才有意义。这个是最基本的策略,如果缓存还没有起作用就失效了,那就没有太大价值了。

6. 问:Redis6.0之后为什么变成多线程了?(部分多线程)

1. 为啥要引入多线程?单线程不香吗(网络IO太慢了)

网络 IO 的读写在 Redis 整个执行期间占用了大部分的 CPU 时间,如果把网络处理这部分做成多线程处理方式,那对整个 Redis 的性能会有很大的提升。

白话文:为了加快网络IO操作

1. 引入多线程是不是意味着有并发问题了?

不是的,多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程

2. Redis6.0默认是没有开启线程模式的

在redis.conf文件里配置以下2个参数(一个是开关、一个是线程数目),可以开启:

io-threads-do-reads yes

io-threads 线程数

上面的线程数一般要少于CPU的核数,如果是8核CPU,建议配置6个线程(官方建议的)

3. Redis的多线程的实现原理

在这里插入图片描述
咋看怎么有点像是多路IO复用模型的功能呀?TODO
在这里插入图片描述
图片来源:https://blog.csdn.net/weixin_39098944/article/details/107869323

  1. 主线程负责接收建立连接请求,获取 Socket 放入全局等待读处理队列
  2. 主线程处理完读事件之后,通过 RR(Round Robin)将这些连接分配给这些 IO 线程
  3. 主线程阻塞等待 IO 线程读取 Socket 完毕(等IO多线程读取完)
  4. 主线程通过单线程的方式执行请求命令,请求数据读取并解析完成,但并不执行(单线程执行命令)
  5. 主线程阻塞等待 IO 线程将数据回写 Socket 完毕
  6. 解除绑定,清空等待队列

白话文:1. 主线程阻塞,等多线程接收IO完毕,并放到队列后,主线程才开始运行 2. 还是用单线程的方式去处理命令,不存在并发问题

4. Redis的线程模型(文件事件处理器,一定要记住)

redis 内部使用文件事件处理器(file event handler),这个文件事件处理器是单线程的,所以 redis 才叫做单线程的模型。
它采用 IO 多路复用机制同时监听多个 socket,根据 socket 上的事件来选择对应的事件处理器进行处理。

1. 文件事件处理器的组成部分

在这里插入图片描述

  • 多个socket:多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件
  • IO多路复用的程序:监听多个 socket,会将 socket 产生的事件放入队列中排队
  • 队列(原文没写,但我认为应该算一部分)
  • 文件事件分派器:事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理
  • 事件处理器(包括:连接应答处理器、命令请求处理器、命令回复处理器)

在这里插入图片描述

上图就是,客户端与 redis 的一次通信过程:
原文地址:https://www.jianshu.com/p/8f2fb61097b8

5. Redis的持久化机制RDB和AOF(重要)

1. RDB(全量备份)

关键字:save、besave、dump.rdb文件小、恢复快、全量备份、丢数据多

1. 实现方式(生成dump.rdb文件)

1. save方式(阻塞、同步)

客户端向Redis发送save命令来创建一个快照文件
save方式会使redis处于**阻塞状态,**直到RDB完成,才会继续响应其它的命令,对redis性能影响非常大

1. 触发时机(配置文件里说了)

redis.conf配置文件默认的快照配置,如下

save 900 1
save 300 10
save 60 10000

900 1 表示900秒内如果有1个key发生了变化,那么触发RDB。300 10以此类推。这个是可以自己改的

2. besave方式(非阻塞、异步)

fork⼀个⼦进程将内存中的数据集快照写⼊磁盘

1. 不懂就问:那besave具体是怎么操作的呢?

先写⼊一个临时⽂件,写⼊成功后,再替换之前的⽂件(用二进制压缩存储)

白话文:这个有点像CopyOnWriteArrayList的add操作写时复制的思想

1. 触发时机(正常退出redis)

正常退出redis

2. 优点(备份文件容量小、支持异步备份)

1. dump.rdb文件小

只包含⼀个经过压缩的二进制文件dump.rdb占用空间很小,方便持久化。

2. 启动快

相对于数据集⼤时,比AOF的启动效率更高

3. besave异步备份

性能最大化,主进程不会进⾏任何 IO 操作,保证了 redis 的高性能 .

4. 适合全量备份

保存了 Redis 某个时间点的数据集,很适合用于做备份

3. 缺点(数据会丢失)

1. 容易丢数据

如果持久化的时候redis发生故障,会丢失最后一次持久化的数据,所以这种方式更适合数据要求不高的时候

2. AOF(增量备份、日志)

关键字:日志、增量备份、rewrite模式、启动慢、丢数据少

日志的形式记录服务器所处理的每⼀个写、删除操作,查询操作不会记录,以文本的方式记录,不断追加,可以打开⽂件看到详细的操作记录

1. 追加日志的方式(fsync)

1. everysec(1s一次,默认方式)

每秒同步,异步完成,先将操作写入一个缓存(复制积压缓冲区?),每秒从缓存同步到磁盘一次,效率高,但是还是可能丢失数据

白话文:每秒从复制积压缓冲区同步一次到磁盘

2. always(每次都要)

每修改同步,每一次发生增删改都记录操作数据安全性高,最多丢失一个操作

白话文:很悲观,每次写操作都会同步。。。

2. no(不操作)

不同步,把操作写入缓存,由操作系统来决定什么时候写入磁盘

2. AOF的优点

1. 比RDB丢的数据少

默认的fsync策略everysec,就算发生机器炸了,也最多只会丢失一秒钟的数据

2. rewrite模式(压缩AOF文件,定期重写)

定期对AOF文件进行重写很重要,要记住),以达到压缩的目的

1. 例子

比如有先有一个set age 21,后面又有一个set age 22,则前面哪个set重写后会被清楚

3. AOF的缺点

1. 文件大

相同的数据集,AOF 文件的大小一般会比 RDB 文件大,且恢复速度慢

2. 启动慢

数据集⼤的时候,比rdb 启动效率低。

3. 总结

  1. RDB和AOF都有,优先加载AOF
  2. 出现意外,AOF丢失数据比RDB少

原文链接:https://www.jianshu.com/p/5b6ba8942b82

题外话:如何选择持久化?

对于数据安全性比较高的,要开启AOF,但是对应的启动速度就慢了

6. Redis的同步机制-主从同步

1. PSYNC概念(2.8版开始)

Redis在2.8版本提供了PSYNC命令来带代替SYNC命令

PSYNC < runid> < offset>
runid:主服务器ID
offset:从服务器最后接收命令的偏移量

2. PSYNC的三要素

1. runid 唯一标识

每个redis节点启动都会生成一个唯一的uuid,每次redis重启之后,runid会变化

2. offset 偏移量

主从节点都会维护各自的复制偏移量offset,当主节点有写入命令时,offset=offset+命令的字节长度。从节点接收到主节点的命令后,也会增加自己的offset,并把自己的offset发送给主节点。主节点同时保存自己的和从节点的offset(通过比较判断数据是否一致)

白话文:主节点发送同步操作,并会把offset一起发给从机,从机收到后,需要改下自己的offset,以保持和主机的一致

3. 复制积压缓冲区(主机才有,1Mb)

repl_backlog_size(英文有个印象就行),保存在主节点上的固定长度的先进先出队列(默认1MB),缓存已经传播出去的命令

作用:缓存主机发出去的命令(可以认为是增量数据)

3. 主从同步机制

在这里插入图片描述
图片相关的视频来源:https://www.zhihu.com/zvideo/1411432217536659456

4. 全量同步(操作RDB文件)

  1. 主节点RDB持久化
  2. 写命令存一份到复制积压缓冲区
  3. 主节点把RDB文件交给从节点
  4. 从节点清理本地数据并加载RDB文件

白话文:全量同步主要操作的是RDB文件

5. 增量同步

主节点每执行一个写命令就会向从节点发送相同的写命令,从节点接收并执行收到的写命令

6. 主从同步的策略

主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。

1. 题外话:从服务器挂了,未接收到主服务器的同步数据(主从偏移量不一致),当从服务器重启的时候,执行增量同步还是全量同步?

视情况而定,主服务器在给从服务发生同步数据的时候,还会把数据发一份到自己的复制积压缓冲区,但是这个复制积压缓冲区容量很小,得看从机未同步的数据在不在复制积压缓冲区

  • 如果在,那么就执行增量同步;
  • 如果不在,数据都没了,没办法只能进行全量同步

在这里插入图片描述
图片来源:https://www.cnblogs.com/zwwhnly/p/12651527.html

2. 题外话:什么情况会全量同步:

  1. 主节点挂了(应该是)
  2. 超出复制积压缓冲区的最大长度,无法做增量同步,所以只能全量同步

7. Redis都有哪些模式(重要)

1. 主从复制模式

关键字:runid、偏移量、复制积压缓冲区、全量同步、增量同步、读写分离

参考上面讲过一个主从同步的原理

主机:支持查询和修改请求
从机:支持查询请求

1. 优点

1. 一个Master,多个SLave

一个Master可以同步多个Slave,当然Slave同样可以接受其它Slaves的连接和同步请求

2. 非阻塞的形式进行主从同步

  • 主机是以非阻塞的方式为Slaves提供服务(给从机发送写命令),客户端仍然可以提交查询或修改请求。
  • 从机也是以非阻塞的方式完成数据同步。在同步期间,如果有客户端提交查询请求,Redis则返回同步之前的数据

3. 读写分离

Master机提供读和写的操作,Slave机只提供操作,从而缓解Master的读操作压力

2. 缺点

1. 主机挂了,需要等待重启或手动切换机子

主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的IP才能恢复。

2. 主机挂掉,数据丢失,系统可用性降低

主机宕机,有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性。

3. 全量同步,浪费资源

  • Redis的主从复制采用全量复制,复制过程中主机会fork出一个子进程对内存做一份快照,并将子进程的内存快照保存为文件发送给从机,这一过程需要确保主机有足够多的空余内存。若快照文件较大,对集群的服务能力会产生较大的影响,而且复制过程是在从机新加入集群或者从机和主机网络断开重连时都会进行,也就是网络波动都会造成主机和从机间的一次全量的数据复制,这对实际的系统运营造成了不小的麻烦。

4. 不好在线扩容(指的是在线!)

在集群容量达到上限时在线扩容会变得很复杂。为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源造成了很大的浪费。

2. 哨兵(Sentinel)集群模式(重要)

关键字:监听、心跳、主观下线、客观下线、自动故障转移

在主从复制模式的基础上,再加一个哨兵集群

1. 哨兵的作用

1. 监控Master主机工作的状态

哨兵隔一定时间给Master和Slaves、其他Sentinel发送心跳,检测是否运作正常。

2. 提醒功能

当被监控的某个Redis节点出现问题时, 哨兵可以通过 API 向管理员或者其他应用程序发送通知。

3. 自动故障转移

在Master主服务器发生故障的时候,可以实现Master和Slave服务器的切换(故障迁移

2. 哨兵工作方式中的主观下线和客观下线是什么?

1. 主观下线

哨兵会定时对redis集群的所有节点发送心跳,如果一个节点在指定时间内没有回复,则该redis节点被该哨兵节点主观下线

白话文:我叫你,你不应,我就认为你掉线了(但你有没有掉线我不知道,这是我主观认为的)

2. 客观下线

在哨兵模式中,当多数哨兵判断该节点为主观下线,那么该节点就是客观下线

白话文:大家都认为你掉线了,那你就是真的掉线了(客观事实嘛)

如果客观下线的节点是主节点,那么就要进行自动故障转移

3. 如何进行自动故障转移的?

1. 具体操作

通过选举机制(投票,半数通过就行),将其中一个Slave升级为新的Master,其他的Slave重新指向(slave of)找个新的Master,并告诉客户端这个新的Master地址

2. 故障转移后的变化

1. Master和Slave的配置文件会改变

Master的redis.conf、Slave的redis.conf的配置文件的内容都会发生相应的改变,slave of指向发生改变

2. 哨兵的配置文件也会改变

sentinel.conf的监控目标会随之调换

4. 哨兵集群模式的优点

1. 保证系统的高可用

有提醒功能、还有自动故障转移功能

2. 保证系统的稳定性(哨兵也是集群的)

哨兵不是一个,也是一个集群,一个哨兵挂了还有其他的哨兵,且哨兵模式是基于主从模式的,所有主从的优点,哨兵模式同样具有

5. 哨兵集群模式的缺点:

1. 配置复杂

又要配置主从模式、又要配置哨兵集群的,头大。。。

3. Cluster集群模式(最重要!)

关键字:去中心化、

1. 特点

1. 去中心化(人人平等)

任何两个节点之间都是相互连通的,客户端连接任何一个节点,然后就可以访问集群中的任何一个节点,对其进行存取和其他操作

像上面说的2中模式,客户端都是需要连接Master节点,才能访问其他节点

2. 每个节点都有一个备用从节点(人人都有备胎)

因为如果集群的话,是有好多个redis一起工作的,那么,就需要这个集群不是那么容易挂掉(保证稳定性),所以呢,理论上就应该给集群中的每个节点至少一个备用的redis服务。这个备用的redis称为从节点(slave)

2. Redis集群各节点之是如何交互的(ping-pong)

采用一种ping-pong的交互方式,可以认为是心跳检测机制

1. 有ping也有pong正常

节点连接正常

2. 有ping但无pong,不正常

有一下两种情况:

  1. 如果一半以上的节点去ping一个节点A,如果都没反应,则认为节点A挂了,然后去连接节点A的slave节点
  2. 如果连从节点也挂了,那么集群就不可用了
1. 问:什么时候导致整个集群不可用?(cluster_state:fail)

如果集群任意master挂掉,且当前master没有slave

3. 优点

1. 支持水平扩容

这可是支持在线扩容吼!很牛,区别上面两个模式

2. 配置简单

一个Redis实例具备了“数据存储”和“路由重定向”,完全去中心化的设计。这带来的好处是部署非常简单,直接部署Redis就行

4. 缺点

1. 只有一个备份节点

容易挂掉,且从节点只能复制自己的主节点,不能复制其他节点

2. 出问题的话要回滚整个集群

很难对业务进行无痛的升级,如果哪天Redis集群出了什么严重的Bug,就只能回滚整个Redis集群。

原文地址:https://www.cnblogs.com/zhuyeshen/p/11737273.html

5. 集群中节点的组成部分以及作用

1. 插槽(slot)或哈希槽

是一个可以存储两个数值的:

  1. 一个变量(取值范围:0-16383)16*1024也就是2^14
  2. 一个集群管理的插件(cluster)

槽是 Redis 集群管理数据的基本单位

2. 作用

1. 判断key要保存在哪个节点(就是下面要讲的hash算法)

一个key过来,进行hash取余运算(hashcode(key)%16384),通过这个值,去找到对应的插槽所对应的节点,从而得知自己要存到哪个节点

6. Cluster集群模式的原理(数据分片)

1. hash算法

比如你有 N 个 redis实例,那么如何将一个key映射到redis上呢,你很可能会采用类似下面的通用方法计算 key的 hash 值,然后均匀的映射到到 N 个 redis上:hash(key)%N

  1. 如果增加一个redis,映射公式变成了 hash(key)%(N+1)
  2. 如果一个redis宕机了,映射公式变成了 hash(key)%(N-1)

在这两种情况下,几乎所有的缓存都失效了。会导致数据库访问的压力陡增,严重情况,还可能导致数据库宕机。

2. 一致性hash算法(重要,hash环的思想)

1. 定义

一致性hash算法主要应用于分布式存储系统中,可以有效地解决分布式存储结构下普通余数Hash算法带来的伸缩性差的问题(不好扩容),可以保证在动态增加和删除节点的情况下尽量有多的请求命中原来的机器节点。

2. 流程
1. 创建Hash环

把对象映射到0-2的32次幂减1的空间里。现在假设有4个对象:object1-object4,将四个对象hash后映射到环形空间中

2. 把cache映射到hash空间

基本思想就是将对象和cache都映射到同一hash数值空间中,并且使用相同的hash算法,可以使用cache的ip地址或者其他因子,假设现在有三个cache

3. 每个key顺时针往下走

找到的第一个cache节点就是存储位置

4. 移除一个cacheB节点

移除一个cacheB节点、这时候key4将找不到cache,key4继续使用一致性hash算法运算后算出最新的cacheC,以后存储与读取都将在cacheC上

在这里插入图片描述

图片摘自原文链接:https://www.cnblogs.com/kenwar/p/9264856.html

3. 影响范围(记住这个结果)
1. 移除节点影响范围

在该节点逆时针计算到遇到的第一个cache节点之间的数据节点。(影响上一个节点)

2. 增加节点影响范围

添加节点逆时针遇到的第一个cache节点之间的数据节点(影响上一个节点)

7. 数据分片策略

原文链接:https://www.e-learn.cn/content/redis/2344485

1. 数据分片的定义(将数据集分配到多个节点上)

为了使集群能够水平扩展,首要解决的问题就是如何将整个数据集按照一定的规则分配到多个节点上

2. 常见的数据分片策略

范围分片,哈希分片,一致性哈希算法和虚拟哈希槽

1. 范围分片

假设数据集是有序,将顺序相临近的数据放在一起,可以很好的支持遍历操作

1. 缺点(节点数据不均匀)

存在热点。比如日志类型的写入,一般日志的顺序都是和时间相关的,时间是单调递增的,因此写入的热点永远在最后一个分片

2. 虚拟哈希槽分片

所有的key根据哈希函数映射到 0 ~ 16383 整数槽内,每一个节点负责维护一部分槽以及槽所映射的键值数据
在这里插入图片描述

1. 优点
  1. 解耦:解耦数据和节点之间的关系,简化了节点扩容和收缩难度
  2. 节点自身维护槽的映射关系,不需要客户端或者代理服务维护槽分区元数据
  3. 支持节点、槽和键之间的映射查询,用于数据路由,在线集群伸缩等场景。
2. 新节点加入,集群是怎么扩容

关键命令:meet、importing、migrating、setkeysinslot、migrate(超时、pipeline机制)、node

新节点指定槽的迁移计划,确保迁移后每个节点负责相似数量的槽,从而保证这些节点的数据均匀

  1. 启动一个Redis新节点
  2. 使用 cluster meet 命令,让新的Redis节点加入到集群中。新节点因为没有负责的槽,不能接受任何读写操作,需要迁移槽和填充数据
  3. 对新节点发送 cluster setslot { slot } importing { sourceNodeId } 命令,准备导入槽的数据
  4. 对原节点发送 cluster setslot { slot } migrating { targetNodeId } 命令,准备迁出槽的数据
  5. 对原节点发送 cluster getkeysinslot { slot } { count } 命令,获取 count 个属于槽 { slot } 的键,然后执行步骤六的操作进行迁移键值数据。
  6. 对原节点执行 migrate { targetNodeIp} " " 0 { timeout } keys { key… } 命令,把获取的键通过 pipeline 机制批量迁移到新节点
  7. 重复步骤5、6,直到完成新节点槽和key数据的迁移
  8. 向集群内所有主节点发送 cluster setslot { slot } node { targetNodeId } 命令,通知槽分配给目标节点。为了>保证槽节点映射变更及时传播,需要遍历新节点上的槽,确保转移成功
    在这里插入图片描述
3. 原节点下线,集群是怎么操作的
  1. 确认下线节点是否有负责的槽,如果是,需要把槽迁移到其他节点,保证节点下线后整个集群槽节点映射的完整性
  2. 当下线节点不再负责槽或者本身是从节点时,就可以通知集群内其他节点忘记下线节点,当所有的节点忘记该节点后可以正常关闭

8. 缓存雪崩、缓存穿透、缓存击穿、缓存预热、缓存更新、缓存降级和解决方案(必考)

1. 缓存雪崩

1. 概念

指在某一个时间段,缓存集中过期失效。所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。

2. 本质原因

大量key同时过期

3. 解决方案

1. 防止缓存集中失效

过期时间+随机数:尽量让缓存失效的时间均匀分布,最次也得随机分布,尤其是一些访问大的接口。

2. 保护数据库

加锁或者队列:防止大量线程对数据库的一次性进行读写,避免缓存失效时对数据库造成的巨大冲击,但吞吐量就降低了。

2. 缓存穿透

1. 概念

查询一个一定不存在的数据。由于缓存不命中,并且出于容错考虑,如果从数据库查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询缓存失去意义

2. 本质原因

黑客攻击,查询数据库中不存在的数据

3. 解决方案

1. 将空对象记录在缓存中

如果数据库返回信息为null,也可以将这个空对象设置到缓存里边去。下次再请求的时候,就可以从缓 存里边获取了

注意:将空对象设置一个较短的过期时间(因为没有意义,存着就是为了防止黑客高频null攻击)

2. 使用布隆过滤器

关键字:bitmap数组、hash算法、误判率

1. 定义(一个很大的bitmap)

将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。由于请求的参数是不合法的(每次都请求不存在的参数),于是我们可以使用布隆过滤器(BloomFilter)提前拦截,不合法就不让这个请求到数据库层

下面是关于布隆过滤器的介绍:

2. 构造方法
	private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size,0.01);
3. 相关概念
1. hash算法

一般都是哈希取余运算吧,将数据库中查出来的数据都塞到这个bitmap里,并设置对应的bit字段为1,代表有数据

2. 容器大小

bitmap的初始容量大小,其内部维护一个全为0的bit数组

3. 误判率

误判率越低,要求精确度更高,则数组越长,所占空间越大。误判率越高则数组越小,所占的空间越小。

白话文:要求精确度更高,肯定要牺牲更多的资源,所以要根据业务选择合适误判率

2. 作用(类似containKey的功能)

用于快速判读某个元素是否存在于集合中,类似于Java中HashSet的功能

3. 原理

布隆过滤器的关键就在于hash算法容器大小(容器大小应该和误判率有关)

白话文:知道了上面的相关概念,应该也就知道原理了,不多解释

3. 缓存击穿

1. 概念

缓存击穿指的是热点key在某个特殊的场景时间内恰好失效了,恰好有大量并发请求过来了,造成DB压力(屋漏偏逢连夜雨)

其实缓存击穿缓存雪崩从概念上来讲差不多,只是缓存击穿是某些热点key,而雪崩指的是大规模的key

2. 本质原因

热点key过期

3. 解决方案

1. 过期时间长一点

对于一些热点key,过期时间可以无限调长

2. 保护数据库

加锁或者队列的方式:和缓存雪崩处理方式意义

4. 缓存降级(丢卒保帅)

1. 定义

指当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。

2. 本质原因

资源紧张,但要保证核心业务可用

3. 降级的目的

让自己不可用,以保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。

4. 如何设置降级策略(可以参考日志)

在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案

  • 一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;

  • 警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;

  • 错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;

  • 严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。

5. 缓存预热(热身运动)

1. 概念

缓存预热也是一个比较常见的概念,缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题。用户直接查询事先被预热的缓存数据。

2. 实现思路

  • 直接写个缓存刷新接口,上线时手工操作下
  • 数据量不大,可以在项目启动的时候自动进行加载
  • 定时刷新缓存

6. 缓存更新(见上面的《redis缓存刷新策略有哪些》这个问题)

除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:

1. 实现

  • 定时任务去清理过期的缓存;
  • 提供手动清理缓存的接口
  • 当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存

9. redis缓存刷新策略有哪些

1. 内存溢出淘汰策略(内存满了)

Redis使用maxmemory-policy(最大内存策略),即Redis中的数据占用的内存超过设定的最大内存时的操作策略,用的是LRU算法

1. LRU算法

LRU算法,least RecentlyUsed,最近最少使用算法。也就是说默认删除最近最少使用的键

redis中并不会准确的删除所有键中最近最少使用的键,而是随机抽取3个键,删除这三个键中最近最少使用的键。那么3这个数字也是可以设置的,对应位置是配置文件中的maxmeory-samples.

2. 缓存过期失效策略:惰性删除 + 定时删除

1. 惰性删除(用的时候再去看有没有被删)

Redis的每个库都有一个过期字典,过期字典中保存所有key的过期时间。当客户端读取一个key时会先到过期字典内查询key是否已经过期,如果key已经超过,会执行删除操作并返回空。这种策略是出于节省CPU成本考虑,但是单独用这种方式存在内存泄露的问题,当过期键一直没有访问将无法得到及时删除,从而导致内存不能及时释放。

2. 定时删除

Redis内部维护一个定时任务,默认每秒运行10次过期扫描(通过 redis.conf 中通过 hz 配置 修改运行次数),扫描并不是遍历过期字典中的所有键,而是采用了自适应算法,根据键的过期比例、使用快慢两种速率模式回收键:
1. 从过期字典中随机取出 20 个键
2. 删除这 20 个键中过期的键
3. 如果过期键的比例超过 25% ,重复步骤 1 和 2

为了保证扫描不会出现循环过度,一直在执行定时删除定时任务无法对外提供服务,导致线程卡死现象,还增加了扫描时间的上限,默认是 25 毫秒(即默认在慢模式下,25毫秒还未执行完,切换为块模式,模式下超时时间为1毫秒且2秒内只能运行1次,当慢模式执行完毕正常退出,会重新切回快模式)

原文链接:Redis 缓存更新策略

3. 主动更新

tips:我们对缓存的操作应该是删除,而不是更新,由下个请求去去缓存,发现不存在后再读取数据库,写入缓存(重要)

以下的两种操作,都可能涉及主动更新缓存

1. 查询数据操作

先去缓存中查询数据,如果没有,则从数据库中查询,并放到缓存中

2. 更新数据操作(引出了经典的先更新数据库还是先删缓存问题)

按照操作顺序可以分为以下几种:

1. 先删除缓存,后更新数据库

1. 存在的问题

A请求删除缓存后,还未更新数据库的时候,突然!B请求来了(一次查询操作),查缓存发现没数据,就去数据库查询,B请求把老的数据查询出来,并放到缓存中,之后A请求将数据库更新

2. 结果

缓存的数据是老的,数据库的是新的

大概率事件,因为B请求的查询速度>A请求的更新速度,很可能存在的

2. 先更新数据库,后删除缓存(感觉用这个方法没啥问题的)

1. 存在的问题

小概率情况下可能会存在脏读:缓存刚好失效,A请求直接查询数据库(旧值),突然!来了个B请求(一次更新操作),一气呵成,更新了数据库,且删除了缓存,之后,A请求将查询到的旧值写入缓存

2. 结果

缓存的数据是老的,数据库的是新的,A请求出现了脏读

总结:缓存刚好失效,A查询到旧值还没来得及操作redis,刚好B请求来一波操作(更新数据库、删除缓存)

小概率事件?因为读速度远大于写速度,B请求很难做到一气呵成,所以一般做到这个情况就可以了

3. 缓存延时双删:实现最终一致性

先删缓存,再更新数据库,延迟1s,再删缓存

延迟多久怎么判断:确保确保读请求结束,写请求可以删除读请求造成的缓存脏数据

优秀的文章:缓存和数据库一致性问题,看这篇就够了

如何使用redis做消息队列(rpush、lpop、blpop)

使用list类型作为消息队列,生产者负责生产待处理的消息,消费者监视list队列并负责处理消息。

1. 基本流程

  1. 生产者在队列的尾部增加消息,使用rpush queue message 命令。
  2. 消费者在队列的头部读取消息,使用lpop queue 命令。

2. 使用blpop(是lpop的阻塞版本)

业务过程实现先进先出(FIFO)。客户端总是要不断监控新的消息(不断去判断队列中还有没有消息,浪费资源),因此需要使用BLPOP命令——lpop的阻塞版本。同时我们在while循环中调用blpop命令,模拟一直在监控。

个人理解:有点类似BlockingQueue中的take操作(队列中没有元素的时候会阻塞)

Redis事务

关系型数据中的事务都是原子性的,而Redis 的事务是非原子性的

严格的说Redis的命令是原子性的,而事务是非原子性的,我们要让Redis事务完全具有事务回滚的能力,需要借助于命令WATCH来实现。

tips:redis也可以通过Lua表达式实现原子性操作

1. 不懂就问:Redis如何保证多个操作的原子性?

  1. 改成Lua表达式实现原子性操作
  2. 通过WATCH等Redis事务相关的命令实现

2. Redis事务相关命令(核心是Watch命令)

1. MULTI(开启事务)

redis会将后续的命令逐个放入队列中,然后使用EXEC命令来原子化执行这个命令系列

2. EXEC(执行命令)

执行事务中的所有操作命令

3. DISCARD(取消事务)

放弃执行事务块中的所有命令

4. WATCH(监视事务中的Key)

监视一个或多个key,如果事务在执行前,这个key(或多个key)被其他命令修改,则事务被中断,不会执行事务中的任何命令

5. UNWATCH(取消Key监视)

取消WATCH对所有key的监视

在这里插入图片描述

原文链接:https://www.cnblogs.com/fengguozhong/p/12161363.html

3. 问:Redis事务和我们见到的数据库事务有什么区别?

Redis事务没有回滚操作,是直接中断操作

4. 再问:为什么Redis不支持事务回滚?

因为多数事务失败是由语法错误或者数据结构类型错误导致的,语法错误是在命令入队前就进行检测的,而类型错误是在执行时检测的,Redis为提升性能而采用这种简单的事务,这是不同于关系型数据库的,特别要注意区分。

什么是bigkey,会产生什么影响

1. 什么是bigkey

  • 对于字符串类型:它的big体现在单个value值很大,一般认为超过10KB就是bigkey。
  • 对于非字符串类型:哈希、列表、集合、有序集合,它们的big体现在元素个数太多。

2. 产生原因

1. 社交类(存Set里,但数量增长太快)

如粉丝列表

2. 统计类

如按天存储某项功能或者网站的用户集合

3. 缓存类(对象序列化后存String中)

如全表的字段都缓存,将数据从数据库都加载出来序列化放到Redis里,这个方式非常常用

3. 危害

1. 导致慢查询

由于Redis单线程的特性,操作bigkey的通常比较耗时,也就意味着阻塞Redis可能性越大,这样会造成客户端阻塞或者引起故障切换,它们通常出现在慢查询中。

2. 占用内存

有个bigkey,不怎么用到,不怎么查询,当它过期时间到了,因为过期删除的惰性策略,还会一直存在内存中,占用内存

3. 内存空间不均匀

这样会不利于集群对内存的统一管理,存在丢失数据的隐患。

4. 网络拥塞

bigkey也就意味着每次获取要产生的网络流量较大,假设一个bigkey为1MB,客户端每秒访问量为1000,那么每秒产生1000MB的流量,对于普通的千兆网卡(按照字节算是128MB/s)的服务器来说简直是灭顶之灾,而且一般服务器会采用单机多实例的方式来部署,也就是说一个bigkey可能会对其他实例造成影响,其后果不堪设想。

5. 备份困难或备份比较耗时

当需要对bigkey进行迁移(例如Redis cluster的迁移slot),实际上是通过migrate命令来完成的,migrate实际上是通过dump + restore + del三个命令组合成原子命令完成,如果是bigkey,可能会使迁移失败,而且较慢的migrate会阻塞Redis。

Pipeline(批处理redis命令)

多个指令之间没有依赖关系,可以使用 pipeline 一次性执行多个指令,减少 IO,缩减时间。

使用 redis-benchmark 进行压测的时候可以发现影响 redis 的 QPS峰值的一个重要因素是 pipeline 批次指令的数目。

1. 使用注意事项

多个指令之间要求没有互不相干,不存在依赖,才能用

2. 和原生的批处理命令(mget、mset)的区别?

1. 是否原子性

mset, mget是原子性,pipeline不是原子性

Redis常见性能问题和解决方案(背)

  • Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件(让Slave去做)
  • 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
  • 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
  • 尽量避免在压力很大的主库上增加从库

假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如何将它们全部找出来?

  1. 使用keys指令可以扫出指定模式的key列表。

如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?
这个时候你要回答redis关键的一个特性:redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。

  1. 使用scan指令,scan指令可以无阻塞的提取(异步的方式吗?TODO)出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。

如果有大量的 key 需要设置同一时间过期,一般需要注意什么?

  • 现象:到过期点,redis可能会出现短暂的卡顿现象(为啥?)
  • 解决方案:在过期时间上加一个随机值,使得过期时间分散一些

Redis阻塞是什么问题造成的?

  • Redis数据结构或API使用不合理
  • 存在大对象bigkey)且对大对象进行复杂的较高的命令(耗时操作)
  • Redis的CPU使用率接近100%:主从同步、数据持久化
  • Cpu竞争
  • 内存交换:Redis是一个内存型数据库,所有数据全部放在内存中。所以强烈建议不开启内存交换
  • 网络问题:从机经常断线重连,导致全量同步

如何提高缓存命中率

定义

  • 命中:可以直接通过缓存获取到需要的数据,不用去其他地方(如数据库)中查找
  • 不命中:无法直接通过缓存获取到想要的数据,需要再次查询数据库或者执行其它的操作。原因可能是由于缓存中不存在,或者缓存已经过期

影响缓存命中率的几个因素

  • 业务场景和业务需求
    缓存适合“读多写少”的业务场景,反之,使用缓存的意义其实并不大,命中率会很低
  • 缓存的设计(粒度和策略)
    缓存粒度越小,命中率会越高,此外,缓存的更新/过期策略也直接影响到缓存的命中率。当数据发生变化时,直接更新缓存的值会比移除缓存(或者让缓存过期)的命中率更高,当然,系统复杂度也会更高。

提高缓存命中率的方法

缓存尽可能用在高频访问且时效性要求不高的热点业务上

原文地址:https://www.cnblogs.com/dinglang/p/6117309.html

使用redis时候如何避免key冲突?

1. 业务隔离

不同的业务使用不同的redis集群,或者协议使用redis的不同db

2. Redis Key命名设计规范

多用冒号,命名可以包括业务标识模块名称等等

Redis内存满了的几种解决方法

1. 加内存

2. 使用Redis的内存淘汰策略

3. 使用Redis集群

  • 4
    点赞
  • 24
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论

打赏作者

爱吃鱼饼的猫

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值