Redis相关知识点小结

在这里插入图片描述

前言

最近重新温习了redis的相关内容,周末净下心总结,如果又觉得异议的地方,欢迎下方留言评论。

redis常见数据结构

  • 字符串String、字典Hash、列表List、集合Set、有序集合SortedSet。

如果你是Redis中高级用户,还需要加上下面几种数据结构HyperLogLog、Geo、Pub/Sub。
如果你说还玩过Redis Module,像BloomFilter,RedisSearch,Redis-ML,面试官得眼睛就开始发亮了。

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

如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?

redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。

redis 主从模式,哨兵模式,cluster

了解redis同步机制之前,我们知道CAP原则,网络分区发生时,一致性和可用性两难全,redis这里的策略选择就是可用性。

主从模式

Redis可以使用主从同步,从从同步。第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将rdb文件全量同步到复制节点,复制节点接受完成后将rdb镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。

面试高开,我觉的还得知道以下几条

  • 增量复制情况下,主节点的buffer是一个定长的环形数组,如果数组内容满了,就会从头开始覆盖前面的内容,那么就要知道快照复制。
  • 快照复制,就是bgsave将当前内存的数据全部快照到磁盘文件中,然后再将快照文件的内容全部传送到从节点,从节点将快照文件接受完毕后,立即执行一次全量加载,加载之前先要将当前内存的数据清空。加载完毕后通知主节点继续进行增量同步。
  • 快照复制还会出现一个问题,就是你的buffer设置的太小,导致指令被覆盖,这样就行成了快照同步死循环。所以设置buffer大小很重要。
  • 无盘复制是指主服务器直接通过套接字将快照内容发送到从节点,从节点还是跟之前一样,先将接收到的内容存储到磁盘文件中,再进行一次性加载。

哨兵模式

Redis Sentinal着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。

cluster

当 Redis Cluster 的客户端来连接集群时,它也会得到一份集群的槽位配置信息。这样当客户端要查找某个 key 时,可以直接定位到目标节点。

Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。

redis过期策略

  • 被动删除:当读/写一个已经过期的key时,会发生一个惰性删除,直接删掉这个过期的key。
  • 主动删除:由于惰性删除的策略无法保证过期数据被及时删掉,所以redis会定期主动淘汰一批已经过期的key
  • 当前已用的内存超过maxmemory限定时,触发主动情理策略。

当 Redis 内存超出物理内存限制时,内存的数据会开始和磁盘产生频繁的交换 (swap)。这种情况倒是redis性能下降,redis提供集中策略腾出空间

  • volatile-lru : 只对设置了过期时间的key进行LRU (默认值) (对已经设置了过期时间的数据结构,挑选最少使用的数据淘汰)
  • allkeys-lru: 删除LRU算法的key(从全部的数据结构,挑选最少使用的数据淘汰)
  • volatile-random : 随机删除即将过期的key(对已经设置了过期时间的数据,采用随机删除的数据淘汰)
  • allkeys-random : 随机删除(对所有的数据,采用随机删除的数据淘汰)
  • volatile-ttl :删除即将过期的(只对设置了过期时间的数据,采用挑选将要过期的数据淘汰)
  • noeviction :永不过期(这个清理过程是阻塞的)

缓存穿透,缓存击穿,缓存雪崩解决方案分析

缓存穿透,缓存击穿,缓存雪崩解决方案分析

为什么说Redis是单线程的以及Redis为什么这么快

1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);

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

3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;

4、使用多路I/O复用模型,非阻塞IO;

5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
以上几点都比较好理解,下边我们针对多路 I/O 复用模型进行简单的探讨:

多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。

这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗),且 Redis 在内存中操作数据的速度非常快,也就是说内存内的操作不会成为影响Redis性能的瓶颈,主要由以上几点造就了 Redis 具有很高的吞吐量。

基于Redis的分布式锁实现、和zk实现有何区别

redis实现分布式锁原理

先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。

这时候对方会告诉你说你回答得不错,然后接着问如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样?

这个锁就永远得不到释放了,set指令有非常复杂的参数,这个应该是可以同时把setnx和expire合成一条指令来用的(set lock:codehole true ex 5 nx OK)

如果在加锁和释放锁之间的逻辑执行的太长,以至于超出了锁的超时限制,这个时候就会导致多个线程获取锁,怎么解决?

  1. 为了避免这个问题,Redis 分布式锁不要用于较长时间的任务。
  2. 可以考虑使用redssion,它有一个看门狗策略,可以给快要超时的业务续期时间。

解锁的时候能直接del么,为什么,怎么解决?
答案肯定是不行的。

  1. 这种不先判断锁的拥有者而直接解锁的方式,会导致任何客户端都可以随时进行解锁,即使这把锁不是它的。
  2. 使用Lua脚本setnx把value也设置上,释放锁时先匹配随机数是否一致,然后再删除 key。但是匹配 value 和删除 key 不是一个原子操作 ,这个时候使用lua脚本,jedis有eval命令,保证原子性。

那使用主从模式的时候,主节点加了锁,从节点还没有同步到,这个时候会导致多个客户端拿到锁,怎么解决?

  1. Redlock算法,加锁时,它会向过半节点发送 set(key, value, nx=True, ex=xxx) 指令,只要过半节点 set 成功,那就认为加锁成功。释放锁时,需要向所有节点发送 del 指令。(补充:从库不会进行过期扫描,而是异步等主库的del指令,所以会出现数据主从不一致)

zk实现分布式锁

TODO 这个开专门学习下,有待更新

使用redis实现限流

使用过Redis做异步队列么

一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。

如果对方追问可不可以不用sleep呢?
list还有个指令叫blpop,在没有消息的时候,它会阻塞住直到消息到来。

如果对方追问能不能生产一次消费多次呢?
使用pub/sub主题订阅者模式,可以实现1:N的消息队列。

如果对方追问pub/sub有什么缺点?
在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq等。

如果对方追问redis如何实现延时队列?

使用sortedset,拿时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。

Redis如何做持久化的

bgsave做镜像全量持久化,aof做增量持久化。因为bgsave会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据,所以需要aof来配合使用。在redis实例重启时,会使用bgsave持久化文件重新构建内存,再使用aof重放近期的操作指令来实现完整恢复重启之前的状态。

对方追问那如果突然机器掉电会怎样?

取决于aof日志sync属性的配置,如果不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据。但是在高性能的要求下每次都sync是不现实的,一般都使用定时sync,比如1s1次,这个时候最多就会丢失1s的数据。

对方追问bgsave的原理是什么?

你给出两个词汇就可以了,fork和cow。fork是指redis通过创建子进程来进行bgsave操作,cow指的是copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。

Pipeline有什么好处,为什么要用pipeline

可以将多次IO往返的时间缩减为一次,前提是pipeline执行的指令之间没有因果相关性。

Redis的rehash

rehash是一个比较复杂的过程,为了不阻塞Redis的进程,它采用了一种渐进式的rehash的机制。

rehash过程中,读取、删除、更新是怎么样的?
因为在进行渐进式 rehash 的过程中, 字典会同时使用 ht[0] 和 ht[1] 两个哈希表, 所以在渐进式 rehash 进行期间, 字典的删除(delete)、查找(find)、更新(update)等操作会在两个哈希表上进行: 比如说, 要在字典里面查找一个键的话, 程序会先在 ht[0] 里面进行查找, 如果没找到的话, 就会继续到 ht[1] 里面进行查找, 诸如此类。

rehash过程中,增加数据是怎么样的?
在渐进式 rehash 执行期间, 新添加到字典的键值对一律会被保存到 ht[1] 里面, 而 ht[0] 则不再进行任何添加操作: 这一措施保证了 ht[0] 包含的键值对数量会只减不增, 并随着 rehash 操作的执行而最终变成空表。

渐进式rehash带来的问题

渐进式rehash避免了redis阻塞,可以说非常完美,但是由于在rehash时,需要分配一个新的hash表,在rehash期间,同时有两个hash表在使用,会使得redis内存使用量瞬间突增,在Redis 满容状态下由于Rehash会导致大量Key驱逐。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章