redis面试题

缓存有哪些类型

缓存的类型分为:本地缓存、分布式缓存和多级缓存

本地缓存:

本地缓存就是在进程的内存中进行缓存,本地缓存是内存访问,没有远程交互开销,性能最好,但是受限于单机容量,一般缓存较小且无法扩展

分布式缓存:

分布式缓存一般都具有良好的水平扩展能力,对较大数据量的场景也能应付自如。缺点就是需要进行远程请求,性能不如本地缓存

多级缓存:

实际业务中一般采用多级缓存,本地缓存只保存访问频率最高的部分热点数据,其他的热点数据放在分布式缓存中

Redis和Memcache比较

  • 与 MC 不同的是,Redis 采用单线程模式处理请求。这样做的原因有 2 个:一个是因为采用了非阻塞的异步事件处理机制;另一个是缓存数据都是内存操作 IO 时间不会太长,单线程可以避免线程上下文切换产生的代价。
  • Redis 支持持久化,所以 Redis 不仅仅可以用作缓存,也可以用作 NoSQL 数据库。
  • 相比 MC,Redis 还有一个非常大的优势,就是除了 K-V 之外,还支持多种数据格式,例如 list、set、sorted set、hash 等。
  • Redis 提供主从同步机制,以及 Cluster 集群部署能力,能够提供高可用服务。

Redis字符串存储为什么使用SDS,而不是C字符串,它有哪些优点?

SDS数据定义

struct sdshdr {
    int len; // 记录 buf 数组中已使用字节的数量,等于 SDS 所保存字符串的长度
    int free; // 记录 buf 数组中未使用字节的数量
    char buf[]; // 字节数组,用于保存字符串
};
  • O(1)复杂度获取字符串长度
  • 杜绝缓冲区溢出
  • 减少修改字符串长度时所需的内存重分配次数
  • 二进制安全
  • 兼容部分C字符串函数

redis的缺点

  • 由于是内存数据库,所以,单台机器,存储的数据量,跟机器本身的内存大小。虽然redis本身有key过期策略,但是还是需要提前预估和节约内存。如果内存增长过快,需要定期删除数据。
  • 如果进行完整重同步,由于需要生成rdb文件,并进行传输,会占用主机的CPU,并会消耗现网的带宽。不过redis2.8版本,已经有部分重同步的功能,但是还是有可能有完整重同步的。比如,新上线的备机。
  • 修改配置文件,进行重启,将硬盘中的数据加载进内存,时间比较久。在这个过程中,redis不能提供服务。

什么是缓存预热,如何实现

缓存预热这个应该是一个比较常见的概念,相信很多小伙伴都应该可以很容易的理解,缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!

解决思路:

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

Redis 为什么是单线程的

因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。

单线程的redis为什么这么快

  • 纯内存操作
  • 单线程操作,避免了频繁的上下文切换
  • 采用了非阻塞I/O多路复用机制

redis的hashtable为什么需要渐进式rehash呢?

在元素数量较少时,rehash会非常快的进行,但是当元素数量达到几百、甚至几个亿时进行rehash将会是一个非常耗时的操作。如果一次性将成万上亿的元素的键值对rehash到ht[1],庞大的计算量可能会导致服务器在一段时间内停止服务,这是非常危险的!所以,rehash这个动作不能一次性、集中式的完成,而是分多次、渐进式地完成。

Redis列表list 底层原理

  • 在版本3.2之前,Redis 列表list使用两种数据结构作为底层实现:压缩列表ziplist和双向链表linkedlist;因为双向链表占用的内存比压缩列表要多, 所以当创建新的列表键时,列表会优先考虑使用压缩列表, 并且在有需要的时候,才从压缩列表实现转换到双向链表实现。
  • Redis3.2+ list的新实现quickList,可以认为quickList,是ziplist和linkedlist二者的结合;quickList将二者的优点结合起来。

这两种存储方式的优缺点

  • 双向链表linkedlist便于在表的两端进行push和pop操作,在插入节点上复杂度很低,但是它的内存开销比较大。首先,它在每个节点上除了要保存数据之外,还要额外保存两个指针;其次,双向链表的各个节点是单独的内存块,地址不连续,节点多了容易产生内存碎片。
  • ziplist存储在一段连续的内存上,所以存储效率很高。但是,它不利于修改操作,插入和删除操作需要频繁的申请和释放内存。特别是当ziplist长度很长的时候,一次realloc可能会导致大批量的数据拷贝。

Redis Set的底层原理

redis的集合对象set的底层存储结构特别神奇,我估计一般人想象不到,底层使用了intset和hashtable两种数据结构存储的,intset我们可以理解为数组,hashtable就是普通的哈希表(key为set的值,value为null)。

使用intset存储必须满足下面两个条件

  • 结合对象保存的所有元素都是整数值
  • 集合对象保存的元素数量不超过512个

Redis Zset的底层原理

采用ziplist和skiplist两种方案,满足如下两个条件使用ziplist.
  • 元素数量少于128的时候
  • 每个元素的长度小于64字节

skiplist优势

skiplist本质上是并行的有序链表,但它克服了有序链表插入和查找性能不高的问题。它的插入和查询的时间复杂度都是O(logN)

Redis的LRU

算法

如果按照HashMap和双向链表实现,需要额外的存储存放 next 和 prev 指针,牺牲比较大的存储空间,显然是不划算的。Redis初始的实现算法很简单,随机从dict中取出五个key,淘汰一个lru字段值最小的。(随机选取的key是个可配置的参数maxmemory-samples,默认值为5).在3.0的时候,又改进了一版算法,首先第一次随机选取的key都会放入一个pool中(pool的大小为16),pool中的key是按lru大小顺序排列的。接下来每次随机选取的keylru值必须小于pool中最小的lru才会继续放入,直到将pool放满。放满之后,每次如果有新的key需要放入,需要将pool中lru最大的一个key取出。淘汰的时候,直接从pool中选取一个lru最小的值然后将其淘汰。

策略(举例)

  • volatile-lru 设置了过期时间的key参与近似的lru淘汰策略
  • allkeys-lru 所有的key均参与近似的lru淘汰策略

Redis事务原理和特点

  • 对于Redis的事务功能来说,事务队列中的命令要么就全部执行,要么就一个都不执行,但是Redis的事务是不支持回滚操作的,即使事务队列中的某个命令在执行期间出现错误,整个事务也会继续执行下去,直到将事务队列中的所有命令都执行完毕为止。在EXEC的时候发现事务队列里有命令存在错误,所以事务里的命令就全都不执行
  • Redis事务不像InnoDB那样复杂的多版本控制机制,而是将所有的命令放在一个事务队列中,收到EXEC命令后按照顺序一一执行。从而事务间就不会导致数据脏读、不可重复读、幻读的问题,因此就没有隔离级别

Redis 6.0 为什么要引入多线程呢?

  • 可以充分利用服务器 CPU 资源,目前主线程只能利用一个核
  • 多线程任务可以分摊 Redis 同步 IO 读写负荷

Redis 6.0 多线程的优化方向如下:

  • 提高网络 IO 性能,典型的实现比如使用 DPDK 来替代内核网络栈的方式。
  • 使用多线程充分利用多核,典型的实现比如 Memcached

开启多线程后,是否会存在线程并发安全问题?

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