目 录CONTENT

文章目录

面试不怂之Redis与缓存大全-基础篇

Marionxue
2022-03-12 / 0 评论 / 0 点赞 / 388 阅读 / 8515 字 / 正在检测是否收录...
温馨提示:
文章发布较早,内容可能过时,阅读注意甄别。

基础篇

问题:什么是redis?

答:Redis 是一个基于内存高性能key-value数据库

问题:redis可以用在哪些业务上?

答:redis可以做很多事情,比如:

  • 缓存
  • 分布式锁 (setnx)
  • 简易的消息队列(List/Streams)
  • 简易订阅通知(Pub/Sub)
  • 延时通知(键过期事件通知)
  • 附近的人(GEO)

以下是具体业务场景:

  • 记录帖子的点赞数、评论数和点击数 (hash)。
  • 记录用户的帖子 ID 列表 (排序),便于快速显示用户的帖子列表 (zset)。
  • 记录帖子的标题、摘要、作者和封面信息,用于列表页展示 (hash)。
  • 记录帖子的点赞用户 ID 列表,评论 ID 列表,用于显示和去重计数 (zset)。
  • 缓存近期热帖内容 (帖子内容空间占用比较大),减少数据库压力 (hash)。
  • 记录帖子的相关文章 ID,根据内容推荐相关帖子 (list)。
  • 如果帖子 ID 是整数自增的,可以使用 Redis 来分配帖子 ID(计数器)。
  • 收藏集和帖子之间的关系 (zset)。
  • 记录热榜帖子 ID 列表,总热榜和分类热榜 (zset)。
  • 缓存用户行为历史,进行恶意行为过滤 (zset,hash)。

问题:Redis有哪些数据结构?

答:Redis是一种Key-Value的模型,key是字符串类型,而常说的数据结构一般是指value的数据结构,一般包含以下类型。

  • 最普通常见的,字符串(String),字典(Hash),列表(List),集合(Set),有序集合(SortedSet)。
  • 高级数据结构,HyperLogLogGeobitmap
  • 更高级用户可能还知道Redis Module,像 BloomFilterRedisSearchRedis-ML

问题:使用Redis有哪些好处?

答:

  • 速度快,因为数据存在内存中,类似于HashMapHashMap的优势就是查找和操作的时间复杂度都是O(1)
  • 支持丰富数据类型,支持stringlistsetsorted sethash
  • 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
  • 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除。

问题:redis相比memcached有哪些优势?

答:优势如下

  • memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型
  • redis的速度比memcached快很多
  • redis可以持久化其数据

问题:使用redis有什么缺点?

答:主要有以下四点缺点:

  • 缓存和数据库双写一致性问题
  • 缓存雪崩问题
  • 缓存击穿问题
  • 缓存的并发竞争问题

问题:Redis提供了几种数据淘汰策略?该怎么选择?

答:

  • volatile-lru(Least Recently Used):从已经设置过期时间的数据集中,挑选最近最少使用的数据淘汰。
  • volatile-ttl:从已经设置过期时间的数据集中,挑选即将要过期的数据淘汰。
  • volatile-random:从已经设置过期时间的数据集中,随机挑选数据淘汰。
  • volatile-lfu(Least Frequently Used):从已经设置过期时间的数据集中,会使用LFU算法选择设置了过期时间的键值对。
  • allkeys-lru:从所有的数据集中,挑选最近最少使用的数据淘汰。
  • allkeys-random:从所有的数据集中,随机挑选数据淘汰。
  • no-eviction:禁止淘汰数据,如果redis写满了将不提供写请求,直接返回错误。

注意这里的6种机制,volatileallkeys规定了是对已设置过期时间的数据集淘汰数据还是从全部数据集淘汰数据,后面的lru、ttl以及random是三种不同的淘汰策略,再加上一种no-eviction永不回收的策略。

图中的noeviction错写成了noevction,请注意!

使用策略规则

  • 如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用allkeys-lru
  • 如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用allkeys-random

问题:为什么redis需要把所有数据放到内存中?

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

问题:Redis是单线程的吗?

答:Redis是单线程的,主要是指Redis网络IO和键值对读写是由一个线程来完成的,这也是Redis对外提供键值存储服务的主要流程。但Redis的其他功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执行的。

问题: Redis6.0之前为什么一直不使用多线程?

答:官方曾做过类似问题的回复:使用Redis时,几乎不存在CPU成为瓶颈的情况, Redis主要受限于内存和网络。例如在一个普通的Linux系统上,Redis通过使用pipelining每秒可以处理100万个请求,所以如果应用程序主要使用O(N)或O(log(N))的命令,它几乎不会占用太多CPU。

使用了单线程后,可维护性高。多线程模型虽然在某些方面表现优异,但是它却引入了程序执行顺序的不确定性,带来了并发读写的一系列问题,增加了系统复杂度、同时可能存在线程切换、甚至加锁解锁、死锁造成的性能损耗。Redis通过AE事件模型以及IO多路复用等技术,处理性能非常高,因此没有必要使用多线程。单线程机制使得 Redis 内部实现的复杂度大大降低,Hash 的惰性 Rehash、Lpush 等等 线程不安全 的命令都可以无锁进行。

问题:Redis6.0为什么要引入多线程呢?

答:Redis将所有数据放在内存中,内存的响应时长大约为100纳秒,对于小数据包,Redis服务器可以处理8万到10万QPS,这也是Redis处理的极限了,对于80%的公司来说,单线程的Redis已经足够使用了。

但随着越来越复杂的业务场景,有些公司动不动就上亿的交易量,因此需要更大的QPS。常见的解决方案是在分布式架构中对数据进行分区并采用多个服务器,但该方案有非常大的缺点,例如要管理的Redis服务器太多,维护代价大;某些适用于单个Redis服务器的命令不适用于数据分区;数据分区无法解决热点读/写问题;数据偏斜,重新分配和放大/缩小变得更加复杂等等。

从Redis自身角度来说,因为读写网络的read/write系统调用占用了Redis执行期间大部分CPU时间,瓶颈主要在于网络的 IO 消耗, 优化主要有两个方向:

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

协议栈优化的这种方式跟 Redis 关系不大,支持多线程是一种最有效最便捷的操作方式。所以总结起来,redis支持多线程主要就是两个原因:

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

问题:Redis6.0默认是否开启了多线程?

答:Redis6.0的多线程默认是禁用的,只使用主线程。如需开启需要修改redis.conf配置文件:io-threads-do-reads yes

问题:Redis6.0多线程开启时,线程数如何设置?

答:开启多线程后,还需要设置线程数,否则是不生效的。同样修改redis.conf配置文件
关于线程数的设置,官方有一个建议:4核的机器建议设置为2或3个线程,8核的建议设置为6个线程,线程数一定要小于机器核数。还需要注意的是,线程数并不是越大越好,官方认为超过了8个基本就没什么意义了。

问题:Redis6.0采用多线程后,性能的提升效果如何?

友情提示: 在Redis6.0中进行多线程性能测试中,如果使用redis-benchmark工具,需要加上--threads参数,指定使用几个线程,同时Redis.conf中开启io-threads-do-reads yes

答:Redis 作者 antirez 在 RedisConf 2019分享时曾提到:Redis 6 引入的多线程 IO 特性对性能提升至少是一倍以上。国内也有大牛曾使用unstable版本在阿里云esc进行过测试,GET/SET 命令在4线程 IO时性能相比单线程是几乎是翻倍了。

测试环境:

  • Redis Server: 阿里云 Ubuntu 18.04,8 CPU 2.5 GHZ, 8G 内存,主机型号 ecs.ic5.2xlarge Redis

  • Benchmark Client: 阿里云 Ubuntu 18.04,8 CPU 2.5 GHZ CPU, 8G 内存,主机型号 ecs.ic5.2xlarge

    测试结果:

详见:zhuanlan.zhihu.com/p/76788470

详见:zhuanlan.zhihu.com/p/76788470

  • 说明1:这些性能验证的测试并没有针对严谨的延时控制和不同并发的场景进行压测。数据仅供验证参考而不能作为线上指标。
  • 说明2:如果开启多线程,至少要4核的机器,且Redis实例已经占用相当大的CPU耗时的时候才建议采用,否则使用多线程没有意义。所以估计80%的公司开发人员看看就好。

问题:Redis6.0多线程的实现机制?

答:如图

流程简述如下:

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

图片

该设计有如下特点:

  1. IO 线程要么同时在读 socket,要么同时在写,不会同时读或写
  2. IO 线程只负责读写 socket 解析命令,不负责命令处理

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

答:从上面的实现机制可以看出,Redis的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程顺序执行。所以我们不需要去考虑控制 key、lua、事务,LPUSH/LPOP 等等的并发及线程安全问题。

问题:Redis线程中经常提到IO多路复用,如何理解?

这是IO模型的一种,即经典的Reactor设计模式,有时也称为异步阻塞IO。

多路指的是多个socket连接,复用指的是复用一个线程。多路复用主要有三种技术:select,poll,epoll。epoll是最新的也是目前最好的多路复用技术。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗),且Redis在内存中操作数据的速度非常快(内存内的操作不会成为这里的性能瓶颈),主要以上两点造就了Redis具有很高的吞吐量。

问题:redis如何做到高可用?

答:redis具备的高可用,其实包含两层含义:一是数据尽量少丢失,二是服务尽量少中断。对于前者redis使用AOFRDB两种持久化方式保证,对于后者Redis的做法就是增加副本冗余量,将一份数据同时保存在多个实例上。

0

评论区