文章目录
- 一、 Redis 概念理解
- 1. 什么是 Redis?
- 2. Redis 的特点有哪些?
- 3. Memcache 与 Redis 的区别都有哪些?
- 4. Redis 相比 Memcached 有哪些优势?
- 5. 如何实现本地缓存?请描述一下你知道的方式
- 6. Redis 通讯协议是什么?有什么特点?
- 二、Redis 数据结构与指令
- 1. Redis 支持的数据类型
- 2. Redis 常用的命令有哪些?
- 3. 一个字符串类型的值能存储最大容量是多少?
- 4. Redis 各个数据类型最大存储量分别是多少?
- 5. 请介绍一下 Redis 的数据类型 SortedSet(zset)以及底层实现机制?
- 6. Redis 事务相关命令有哪些?
- 7. 什么是 Redis 事务?原理是什么?
- 8. Redis 事务的注意点有哪些?
- 9. Redis 为什么不支持回滚?
- 10. 请介绍一下 Redis 的 Pipeline(管道),以及使用场景
- 11. 请说明一下 Redis 的批量命令与 Pipeline 有什么不同?
- 12. 请介绍一下 Redis 的发布订阅功能
- 13. Redis 的链表数据结构的特征有哪些?
- 14. 请介绍一下 Redis 的 String 类型底层实现?
- 15. Redis 的 String 类型使用 SSD 方式实现的好处?
- 16. 设置键的生存时间和过期时间有哪些命令?
- 三 、Redis 高并发处理策略
- 1. 为什么 Redis 需要把所有数据放到内存中?
- 2. Redis 是单线程的吗?
- 3. Redis 为什么设计成单线程的?
- 4. 什么是缓存穿透?怎么解决?
- 5. 什么是缓存雪崩? 怎么解决?
- 6. 缓存的更新策略有几种?分别有什么注意事项?
- 7. 请介绍几个可能导致 Redis 阻塞的原因
- 8. 怎么去发现 Redis 阻塞异常情况?
一、 Redis 概念理解
1. 什么是 Redis?
Redis 全称为:Remote Dictionary Server(远程数据服务),是一个基于内存且支持持久化的高性能 key-value 数据库。
具备一下几个基本特征:
2. Redis 的特点有哪些?
- Redis 本质上是一个 key-value 类型的数据库
- 整个数据库都是在内存中进行操作,可定期刷新到磁盘进行持久化存储
- 由于是在内存操作,读写能力非常好,每秒可以处理 10 万次读写操作
- Redis 支持多种数据结构,提供了丰富的数据类型选择
- Redis 同时支持数据备份,主从配置
- Redis 的所有操作都是原子性的
3. Memcache 与 Redis 的区别都有哪些?
- 存储方式不同:Memcache 把数据全部存在内存之中,断电后会丢失。Redis 所有数据加载在内存,但也会持久化到磁盘,保证数据的持久性。
- 支持数据类型不同:Memcache 对数据类型支持相对简单,只支持 key-value 结构。Redis 有复杂的数据类型。
- 底层模型不同:底层实现方式以及客户端通信应用协议不一样。 Redis 直接自己构建了 VM 机制。
- 运行环境不同:Redis 目前官方只支持 Linux 上运行。
4. Redis 相比 Memcached 有哪些优势?
- Memcached 所有的值均是简单的字符串,Redis 作为其替代者,支持更为丰富的数据类型
- Redis 的速度比 Memcached 快很多
- Redis 可以持久化其数据
5. 如何实现本地缓存?请描述一下你知道的方式
- 程序中定义内存数据结构来实现, 比如说定义一个成员变量Map 或者 List 均可以实现
- 使用开源的缓存框架 Ehcache,Ehcache 封装了对于内存操作的功能
- Guava Cache 是 Google 开源的工具集, 提供了缓存的边界操作工具
6. Redis 通讯协议是什么?有什么特点?
Redis 的通信协议是 Redis Serialization Protocol,简称 RESP。
有如下特性:
- 是二进制安全的
- 在 TCP 层
- 基于请求—响应的模式
二、Redis 数据结构与指令
1. Redis 支持的数据类型
- String(字符串)
- list(列表):list 是字符串列表,按照插入顺序排序。元素可以在列表的头部(左边)或者尾部(右边)进行添加。
- hash(哈希):Redis hash 是一个键值对(key-value)集合。Redis hash 是一个 String 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。
- set(集合):Redis 的 set 是 String 类型的无序集合。
- zset(sorted set:有序集合):Redis zset 和 set 一样也是 String 类型元素的集合,且不允许重复的成员。不同的 zset 是每个元素都会关联一个 double 类型的分数。zset 通过这个分数来为集合中所有元素进行从小到大的排序。zset 的成员是唯一的,但分数(score)却可以重复。
2. Redis 常用的命令有哪些?
3. 一个字符串类型的值能存储最大容量是多少?
512M
4. Redis 各个数据类型最大存储量分别是多少?
- Strings 类型:一个 String 类型的 value 最大可以存储 512M
- Lists 类型:list 的元素个数最多为 2^32-1 个,也就是 4294967295 个。
- Sets 类型:元素个数最多为 2^32-1 个,也就是 4294967295 个。
- Hashes 类型:键值对个数最多为 2^32-1 个,也就是 4294967295 个。
- Sorted sets 类型:跟 Sets 类型相似。
5. 请介绍一下 Redis 的数据类型 SortedSet(zset)以及底层实现机制?
zset 有顺序,不能重复。在业务场景下,适合做排行榜之类的事情。
底层实现机制:
SortedSet 的实现方式可能有两种:ziplist 或者 skiplist。
当有序集合对象同时满足以下两个条件时,对象使用 ziplist 编码:
-
保存的元素数量小于 128;
-
保存的所有元素长度都小于 64 字节。 不能满足上面两个条件的使用 skiplist 编码。
-
ziplist 编码的有序集合对象使用压缩列表作为底层实现,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,第二个节点保存元素的分值。并且压缩列表内的集合元素按分值从小到大的顺序进行排列,小的放置在靠近表头的位置,大的放置在靠近表尾的位置。
-
skiplist 编码的有序集合对象使用 zset 结构作为底层实现,一个 zset 结构同时包含一个字典和一个跳跃表。字典的键保存元素的值,字典的值则保存元素的分值;跳跃表节点的 object 属性保存元素的成员,跳跃表节点的 score 属性保存元素的分值。
6. Redis 事务相关命令有哪些?
- discard 命令:取消事务,丢弃事务中所有命令。
- exec 命令:执行所有事务内的命令。
- multi 命令:标记一个事务开始。
- unwatch 命令:取消 watch 命令对所有 key 的监视。
- watch 命令:监视一个(或多个)key,如果在执行事务之前这个(这些)key 被其他命令所改动,事务将被打断。
7. 什么是 Redis 事务?原理是什么?
-
Redis 中的事务是一组命令的集合,是 Redis 的最小执行单位,一个事务要么都执行,要么都不执行。
-
Reids 事务保证一个事务内的命令依次执行,而不会被其他命令插入。
-
Redis 事务的原理是先将属于一个事务的命令发送给 Redis,然后依次执行这些命令。
8. Redis 事务的注意点有哪些?
- 不支持回滚,如果事务中有错误的操作,无法回滚到处理前的状态,需要开发者处理。
- 在执行完当前事务内所有指令前,不会同时执行其他客户端的请求。
9. Redis 为什么不支持回滚?
Redis 事务不支持回滚,如果遇到问题,会继续执行余下的命令。 这一点和关系型数据库不太一致。这样处理的原因有:
- 只有语法错误,Redis才会执行失败,例如错误类型的赋值, 这就是说从程序层面完全可以捕获以及解决这些问题
- 支持回滚需要增加很多工作,不支持的情况下,Redis 可以保持简单、速度快的特性
10. 请介绍一下 Redis 的 Pipeline(管道),以及使用场景
Redis 客户端与服务端通信模型使用的 TCP 协议进行连接, 那么在单个指令的执行过程中,都会存在“交互往返”的时间。
Redis 提供了批量操作命令,例如 mget、mset 等,能够一定程度上节省这类时间,但大部分命令还是不支持批量操作。
因此 Pipeline 功能,能够改善这一类问题。 Pipeline 将一组 Redis 命令进行组装,一次性传输给 Redis,再将这些命令执行结果,按照顺序返回给客户端。
适用场景:有批量的数据写入 Redis,并且这些数据允许一定比例的写入失败,那么这种场景就可以适用 Pipeline。失败的数据后期进行补偿即可。
11. 请说明一下 Redis 的批量命令与 Pipeline 有什么不同?
- 批量命令保证原子性的,Pipeline 非原子性
- 批量命令是一个命令对应多个 key,Pipeline 支持多个命令
- 批量命令是 Redis 服务端实现,而 Pipeline 是需要服务端和客户端共同实现
12. 请介绍一下 Redis 的发布订阅功能
Redis 提供了基于“发布/订阅”模式的消息机制,消息发布者和订阅者不能直接通信,客户端发布消息的时候指定发送的频道,然后订阅了该频道的用户可以接收到该消息。具体指令如下:
- 发布消息:publish channel message
- 订阅消息:subscribe channel [……]
- 退订消息:punsubscribe
13. Redis 的链表数据结构的特征有哪些?
- 双端引用:链表的最前和最后节点都有引用,获取前后节点的复杂度为 o(1)
- 无环链表:对于链表的访问都是以 null 结束
- 长度计数器:通过 len 属性来记录链表长度
14. 请介绍一下 Redis 的 String 类型底层实现?
Redis 底层实现了简单动态字符串的类型(SSD),来表示 String 类型。 没有直接使用 C 语言定义的字符串类型。
struct sdshdr{
int len;
int free;
char buf[];
}
15. Redis 的 String 类型使用 SSD 方式实现的好处?
- 避免缓冲区溢出:进行字符修改时候,可以根据 len 属性来检查空间是否满足要求
- 减少内存分配次数:len 和 free 两个属性,可以协助进行空间预分配以及惰性空间释放
- 二进制安全:SSD 不是以空字符串来判断是否结束,而是以 len 属性来判断字符串是否结束
- 常数级别获取字符串长度:获取字符串的长度只需要读取 len 属性就可以获取
- 兼容 C 字符串函数:可以重用 C 语言库的 的一部分函数
16. 设置键的生存时间和过期时间有哪些命令?
- EXPIRE 以秒为单位,设置键的生存时间
- PEXPIRE 以毫秒为单位,设置键的生存时间
- EXPIREAT 以秒为单位,设置键的过期 UNIX 时间戳
- PEXPIREAT 以毫秒为单位,设置键的过期 UNIX 时间戳
三 、Redis 高并发处理策略
1. 为什么 Redis 需要把所有数据放到内存中?
追求更快的读写速度,并异步方式持久化存储到磁盘。 如果不将数据放到内存中,磁盘的 I/O 速度会严重影响 Redis 的性能。
2. Redis 是单线程的吗?
是。这里的单线程指的是 Redis 网络请求模块使用了一个线程(所以不需考虑并发安全性),即一个线程处理所有网络请求,其他模块仍用了多个线程。
3. Redis 为什么设计成单线程的?
- 绝大部分请求是纯粹的内存操作(非常快速)
- 采用单线程,避免了不必要的上下文切换和竞争条件
- 非阻塞 IO,内部采用 epoll,epoll 中的读、写、关闭、连接都转化成了事件,然后利用 epoll 的多路复用特性,避免 IO 代价。
4. 什么是缓存穿透?怎么解决?
缓存穿透是指缓存中查询一个不存在的数据,需要去数据库中获取。
如果数据库也查不到结果,将不会同步到缓存,导致这个不存在数据每次请求都要到数据库查询,失去了缓存的意义。
解决方法有两个:
-
布隆过滤(Bloom filter)
将所有查询的参数都存储到一个 bitmap 中,在查询缓存之前,先再找个 bitmap 里面进行验证。
如果 bitmap 中存在,则进行底层缓存的数据查询; 如果 bitmap 中不存在查询参数,则进行拦截,不再进行缓存的数据查询。
适用范围:可以用来实现数据字典,进行数据的判重,或者集合求交集
-
缓存空对象
如果查询返回的数据为空,仍然把这个空结果进行缓存。那么再次用相同 key 获取数据的时候,即使不存在的数据,缓存也可以直接返回空值,避免重复访问 DB。
缓存空对象有两个不足之处:
- 缓存层将存储更多的键值对,如果是恶意的随机访问,将造成很多内存空间的浪费。这个不足之处可以通过将这类数据设置很短的过期时间来控制。
- DB 与缓存数据不一致。这种可以考虑通过异步消息来进行数据更新的通知,在一定程度上减少这类不一致的时间。
5. 什么是缓存雪崩? 怎么解决?
在集中的一段时间内,有大量的缓存失效,导致大量的访问没有命中缓存,从而将所有查询进行数据库访问,导致数据库的压力增大,从而造成了缓存雪崩。
比如,如果要做一个促销活动,我们将商品信息都刷新到缓存, 过期时间统一为 30 分钟。那么在 30 分钟之后,这批商品将全部过期。这时候这批商品的访问查询,都落到了数据库,对于数据库而言,这一刻的压力会非常大。从而造成系统整体性风险。
解决方案:
-
分散失效时间
分析缓存数据的特点,尽量将热点缓存的失效时间均匀分布。 比如说将相同类型的缓存的失效时间设置成一个在一定区间内的随机值。从而有效的分散失效时间。
-
DB 访问限制
对数据的访问进行限流性质的操作。比如说对数据库访问进行加锁的处理或者限流相关的处理。
-
多级缓存设计
一级缓存为基础缓存,缓存失效时间设置一个较长时间, 二级缓存为应用缓存,失效时间正常设置,一般会比较短。 当二级缓存失效的时候,再从一级缓存里面获取。
6. 缓存的更新策略有几种?分别有什么注意事项?
缓存的更新策略包含:
- 先更新数据库,再更新缓存
- 先删除缓存,再更新数据库
- 先更新数据库,再删除缓存
策略一:先更新数据库,再更新缓存
-
这种策略会导致线程安全问题
例如:线程 1 更新了数据库,线程 2 也更新数据库, 这时候由于某种原因,线程 2 首先更新了缓存,线程 1 后续更新。 这样就导致了脏数据的问题。 因为目前数据库中存储的线程 2 更新后的数据,而缓存存储的是线程1更新的老数据。
-
更新缓存的复杂度相对较高
数据写入数据库之后,一般存入缓存的数据都要经过一系列的加工计算,然后写入缓存。 这时候更新缓存相比较于直接删除缓存要比较复杂。
策略二:先删除缓存,再更新数据库
这种策略可能导致数据不一致的问题。线程 1 写数据删除缓存;这时候有线程 2 查询该缓存,发现不存在,则去访问数据库,得到旧值放入缓存;线程 1 更新数据库。这时候就出现了数据不一致的问题。 如果缓存没有过期时间,这个脏数据一直存在。
解决方案:在写数据库成功之后, 再次淘汰缓存一次。
策略三:先更新数据库,再删除缓存
可能会造成比较短暂的数据不一致。在更新完成数据库, 还没有删除缓存的时刻,如果有缓存数据访问, 就会造成数据不一致的情形。 但这种如果数据同步机制比较科学,一般都会比较快, 不一致的影响比较小。
7. 请介绍几个可能导致 Redis 阻塞的原因
内部原因:
- Redis 的 API 或者指令数据结构使用不合理
- Redis 主机 CPU 负载过高,导致系统崩溃
- 持久化工作资源占用过多
外部原因:
- CPU 竞争
- 内存交换
- 网络问题
8. 怎么去发现 Redis 阻塞异常情况?
-
通过应用服务监控发现
当 Redis 阻塞的时候,线上应用服务应该会感知发现。比如说发现 Redis 链接超时等。这种就需要应用服务增加对于异常的统计,并针对 Redis 相关的异常,进行报警。
-
通过 Redis 自身监控系统
借助 Redis 监控系统发现阻塞问题,当监控系统发现各个监控指标存在异常的时候,发送报警。 指标有:CPU/内存/磁盘等, 慢查询,命令耗时增加等。