面试篇八:NOSQL非关系型数据库Redis
1.Redis是一种key-value的格式存储数据的NO-SQL非关系型数据库。
2.Redis是将所有数据都存储在内存中,采用了基于非阻塞的IO多路复用机制,所以它的读写能力非常出色。
3.同时Redis也提供了将内存的数据持久化到磁盘的两种方式:RDB快照持久化和AOF命令日志持久化,防止服务宕机了内存数据丢失。
- Redis的数据类型
Redis中key存储的都是字符串。
value支持String(字符串)、hash(散列)、list(列表)、set(集合)、zset(有序集合)、Bitmaps(位图)、HyperLogLog、CEO(地理信息定位)等多种数据结构。
Redis可以应用于分布式锁、缓存、计数器、排行榜、社交网络、消息队列。
- Redis的五种基本数据结构的应用场景
(1)String:字符串类型的值实际上可以是字符串(简单字符串、JSON、XML)、数字(整数、浮点数)、二进制(图片、音视频等),但是值最大不能超过512M。主要应用有缓存、计数、共享Session、限速。
(2)hash:哈希类型的值本身又是一个键值对的结构。主要应用有缓存用户信息、缓存对象。
(3)list:列表,用来存储多个有序的字符串,按索引下标作为排序依据;它可以充当队列和栈的角色。主要应用有消息队列、数据列表。
(4)set:集合类型,用于存储多个无序的字符串,且不容易重复。主要应用有标签、共同关注。
(5)zset:也叫sorted set,有序集合,按每个元素设置的权重作为排序依据。主要应用统计用户点赞、用户排序。
- Redis常用命令
查看链接https://www.cnblogs.com/scorpio-cat/p/12777248.html
- Redis为什么快?单机的Redis就可以支持每秒十几万的并发。
(1)基于内存操作;
(2)使用单线程,避免线程切换开销;
(3)采用基于非阻塞的IO多路复用机制。
(4)使用C语言实现,基于几种数据类型,redis做了大量的优化,性能极好。
- Redis哪部分是单线程,哪部分是多线程
Redis执行命令一直都是单线程的。
在Redis4.0中,Redis在处理一些较为缓慢的操作时是使用的子线程处理,如清理脏数据、无用连接的释放、大key的删除。
在Redis6.0中,Redis采用了多线程来处理请求的接收&解析和请求响应,提升了IO读写效率,且仍然保留了IO多路复用。
Redis多线程模型可以查看链接https://www.cnblogs.com/hlxs/p/14037919.html
- I/O多路复用
为了让单线程(进程)的服务端应用同时处理多个客户端的事件,Redis 采用了 IO 多路复用机制。
I/O 多路复用其实是使用一个线程来检查多个 Socket 的就绪状态,在单个线程中通过记录跟踪每一个 socket(I/O流)的状态来管理处理多个 I/O 流。
Redis 的 I/O 多路复用模型:socket客户端、IO多路复用程序、文件事件处理分发器、事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)。
I/O 多路复用模型采用的是 「Reactor 设置模式」的方式来实现,是利用 select、poll、epoll 函数可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉。当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),依次顺序的处理就绪的流,这种做法就避免了大量无用的等待操作。
过程说明:
(1)多个客户端与服务端连接时,每个都会生成对应一个套接字描述符,Redis 使用 「I/O 多路复用程序」 将客户端 socket 对应的 FD 注册到监听列表(一个队列)中。
(2)当客服端执行 read、write 等操作命令时,I/O 多路复用程序会将命令封装成一个事件,并绑定到对应的 FD 上。
(3)文件事件处理器使用 I/O 多路复用模块同时监控多个文件描述符(fd)的读写情况,当 accept、read、write 和 close 文件事件产生时,文件事件处理器就会回调 FD 绑定的事件处理器进行处理相关命令操作。
(4)整个文件事件处理器是在单线程上运行的,但是通过 I/O 多路复用模块的引入,实现了同时对多个 FD 读写的监控,当其中一个 client 端达到写或读的状态,文件事件处理器就马上执行,从而就不会出现 I/O 堵塞的问题,提高了网络通信的性能。
- Redis数据淘汰策略
(1)volatile-lru:从已设置过期时间的数据集中选最近最少使用的数据淘汰。
(2)volatile-ttl:从已设置过期时间的数据集中选要过期的数据淘汰。
(3)volatile-random:从已设置过期时间的数据集中任意选择数据淘汰。
(4)allkeys-lru:从数据集中挑选最近最少使用的数据淘汰。
(5)allkeys-random:从数据集中任意选择数据淘汰。
(6)no-enviction:禁止驱逐数据。
在在redis.conf中配置
maxmemory-policy volatile-lru
- 怎么保证缓存中的数据和数据库中的数据库的一致性?
(1)延时双删策略:就是先删除缓存key,再更新数据库数据;等待不会后,再尝试删除一下缓存key,防止整个过程中,出现还没更新完成,其他线程查询了该key并缓存了旧值的情况。
(2)对更新数据库和更新缓存加锁,使另一个线程获取数据时阻塞等待,保证强一致性。
(2)定时任务定期刷新缓存,保证最终一致性。
- Redis消息模式
(1)使用List类型的lpush和rpop实现消息队列。
rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试,但是在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq等。
(2)如果要实现生产一次消费多次,需要使用pub/sub发布订阅者模式,可以实现1:N的消息队列;要先订阅后发布消息,不然前期发布的数据接收不到。
订阅消息 subscibe my-channel
发布消息 publish my-channel "我是发布者,发布了一个消息,请查收"
- Redis在Spring项目中怎么使用
查看链接https://www.cnblogs.com/RunForLove/p/4903734.html
(1)pom.xml中配置依赖spring-data-redis、jedis。
(2)app-context-cache.xml,其中配置了ip地址,端口号,以及数据库的访问密码。
(3)使用RedisTemplate类操作,不同数据类型分别对应不同方法String->opsForValue(),List->opsForList(),Hash->opsForHash(),Zset->opsForZSet()。
- Redis的主从复制
Redis的主从复制就是将一台Redis服务器的数据复制到其他Redis服务器,以实现数据备份、负载均衡、故障容错等需求。
主从复制是单向的,只能由主节点到从节点。
Redis的后续版本增加了从从同步,来减轻主节点的同步压力。
主从常见的拓扑结构有:一主一从,一主多从、树状主从结构。
一主一从:用于主节点宕机时,从节点提供故障转移支持。
一主多从:用于实现读写分离。可以把读命令发送到从节点来分担主节点的压力。
树状主从结构:一个主节点,多个从节点,从节点又作为其他从节点的主节点;这种是通过引入中间复制层,来减轻主节点负载和降低需要传送给从节点的数据量。
主从复制原理:
工作流程:从节点保存主节点信息(ip和port)-> 主从建立socket网络连接 -> 连接建立后从节点发送ping请求进行通信 -> 权限验证 -> 主从节点正常通信,主节点就把持有的数据全部发给从节点 -> 最后主节点会持续把写命令发送给从节点,来保证主从数据一致性。
主从数据同步方式:使用psync命令完成主从数据同步,同步过程分为全量复制和部分复制。
全量复制:全量复制,发送在初始复制场景。
(1)从节点没有复制偏移量和主节点的运行ID,所以Slave发送psync-1命令;
(2)主节点就知道是为全量复制,回复+FULLRESYNC{renId|{offset}响应;
(3)从节点接收到响应,保存运行ID和偏移量offset;
(4)主节点执行bgsave保存RDB文件到本地,并发送RDB文件给从节点;
(5)从节点把接收到的RDB文件保存并直接作为自己的数据文件;
(6)另外,主节点会把这期间发生的读写命令保存在复制客户端缓冲区,当从节点完成RDB复制后,主节点再把缓冲区的数据发送给从节点。
(7)从节点接收所有数据后,会清除自身原来的数据,然后再加载RDB文件,加载完后;如果当前节点开启了AOF功能,它会立即做bgrewriteaof操作。
部分复制(增量复制):从节点保存有自身已复制的偏移量和主节点的运行ID。
(1)从节点会把复制的偏移量和主节点的运行ID当作psync参数发送给主节点;
(2)主节点接收到psync命令后,会先核对参数是不是自身符合,核对后再根据参数在自身复制积压缓冲区查找到数据,对从节点发送响应并把数据发送给从节点。
主从复制存在问题点:如果主节点出现故障,需要人工干预,将从节点升级为主节点,命令其他从节点从新的主节点复制,同时还要修改应用方的主节点地址。
- Redis哨兵
Redis哨兵解决了高可用问题,实现了自动故障转移。
(1)Redis Sentinel通过三个定时监控任务(主从信息获取、哨兵信息发布、节点心跳检测)来完成对各个节点的发现和监控。
(2)Sentinel节点间会有一个领导者选举工作,使用的是Raft算法实现领导者选举:
每个节点每隔1s对主节点、从节点、其他Sentinel节点发送ping命令做心跳检测,当这些节点没有进行有效回复时, Sentinel节点就会对该节点判定为失效;
这时候如果主节点被判定为主观下线时,会像其他Sentinel节点发送命令,要求将自己设置为领导者;
收到命令的节点,如果还没有收到其他节点的请求,就会同意该请求,否则拒绝。
当一个节点发现自己的票数大于一半以上时[max(quorun, num(sentines)/2+1)],它就成为领导者了;
如果上述过程没有选举出领导者,则会进入下一次选举。
- Redis集群
Redis集群解决了高可用和分布式问题。
集群支持主从复制和主节点的自动故障转移,任一节点发送故障,仍然可以对外提供服务,这点和哨兵类似。
当集群中的任一节点下线,会导致有部分槽有空档,当前集群不可用;这时Redis就是使用的主体复制来实现高可用:检测到该节点宕机后,集群会将该节点的从节点升级为主节点来提供服务。
集群最核心的功能是数据分区,将数据分散到多个节点,突破了单机内存大小限制,存储容量变大;每个这节点都可以读写,提高了集群的响应能力。
Redis集群中数据分区是采用的虚拟槽分区。
哈希槽的分配:所有的键根据哈希函数映射到 0 ~ 16383 整数槽内 ,每个key通过CRC16校验后对16384取模来决定放置哪个槽 (Slot), 每一个节点负责维护一部分槽以及槽所映射的键值数据,计算公式:slot = CRC16 (key) & 16383。
增加和删除节点对槽的影响:我们不需要停用所有redis服务
增加节点:只需要把其他节点的某些哈希槽移到新节点即可。
删除节点:只需要把移除节点的哈希槽移到其他节点即可。
注:(1)节点数量至少有6个才能保证组成完整高可用的集群,每个节点需要开启配置cluster-enabled yes。
(2)Redis集群把所有的数据映射到16384个槽中,每个节点对应若干个槽。通过cluster addslots命令为节点分配槽。
(3)集群中每个节点都会定期向其他节点发送ping消息,接收节点回复pong消息作为响应。如果在cluster-node-timeout时间内通信一直失败,则把接收节点标记为主观下线状态,并在集群内传播,当半数以上持有槽的主节点都标记为主观下线时,则触发客观下线流程。这个过程是故障发现。
(4)故障节点客观下线后,如果下线节点是持有槽的主节点,则会在从节点选举出一个替换它。选举投票过程:假设有N个持有槽的主节点,每个持有槽的主节点只能投票给一个从节点,如果某个从节点获取半数以上的选票,则替换故障的主节点。
- Redis有几种方式将多条命令打包发送?
(1)事务(Tansactions):Redis在执行事务的过程中,不会被其他客户端发送来的命令请求打断,只有事务命令执行完才会执行其他客户端的命令。
Redis事务的原理是multi命令开始事务后,所有的指令在exec前不执行,是缓存在服务器的一个事务队列中;服务器收到exec指令就开始执行整个事务队列,由于Redis执行命令是单线程的,所以这组命令不会被打断。
(2)lua脚本(Lua Scripts):使用eval命令执行脚本。
(3)管道(Pipelining):使用nc命令将多条指令发送给Redis服务端,Redis服务端收到管道发送的命令会一直执行命令并将执行结果缓存,当所有命令执行完成,再将缓存的结果一次性返回给客户端。
- Redis实现分布式锁
setnx命令占用锁,del释放锁;如果异常导致del指令没有被执行,则会导致锁得不到释放。
可以加过期时间解决,但是如果占用锁和设置过期时间是两条指令,也可能异常原因导致设置过期时间失败,导致锁不会被释放。
最终方案使用set命令实现占用锁并设置过期时间,命令set key value ex seconds nx
- keys指令和scan指令
keys指令可以扫出指定模式的key列表,但是会导致线程阻塞。
scan指标可以无阻塞的提取指定模式的key列表,但是有一定重复概率。

浙公网安备 33010602011771号