主题
Redis
1.什么是 Redis,为什么用 Redis?
Redis 是一种支持 key-value 等多种数据结构的存储系统。可用于缓存,事件发布或订阅,高速队列等场景。支持网络,提供字符串,哈希,列表,队列,集合结构直接存取,基于内存,可持久化。
- 读写性能优异
- Redis 能读的速度是 110000 次/s,写的速度是 81000 次/s (测试条件见下一节)。
- 数据类型丰富
- Redis 支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
- 原子性
- Redis 的所有操作都是原子性的,同时 Redis 还支持对几个操作全并后的原子性执行。
- 丰富的特性
- Redis 支持 publish/subscribe, 通知, key 过期等特性。
- 持久化
- Redis 支持 RDB, AOF 等持久化方式
- 发布订阅
- Redis 支持发布/订阅模式
- 分布式
- Redis Cluster
2.为什么 Redis 是单线程的以及为什么这么快?
- redis 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速.
- 数据结构简单,对数据操作也简单,redis 中的数据结构是专门进行设计的
- 采用单线程模型, 避免了不必要的上下文切换和竞争条件, 也不存在多线程或者多线程切换而消耗 CPU, 不用考虑各种锁的问题, 不存在加锁, 释放锁的操作, 没有因为可能出现死锁而导致性能消耗
- 使用了多路 IO 复用模型,非阻塞 IO
- 使用底层模型不同,它们之间底层实现方式及与客户端之间的 通信的应用协议不一样,Redis 直接构建了自己的 VM 机制,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求
3.Redis 一般有哪些使用场景?
可以结合自己的项目讲讲,比如
- 热点数据的缓存
缓存是 Redis 最常见的应用场景,之所有这么使用,主要是因为 Redis 读写性能优异。而且逐渐有取代 memcached,成为首选服务端缓存的组件。而且,Redis 内部是支持事务的,在使用时候能有效保证数据的一致性。
- 限时业务的运用
redis 中可以使用 expire 命令设置一个键的生存时间,到时间后 redis 会删除它。利用这一特性可以运用在限时的优惠活动信息、手机验证码等业务场景。
- 计数器相关问题
redis 由于 incrby 命令可以实现原子性的递增,所以可以运用于高并发的秒杀活动、分布式序列号的生成、具体业务还体现在比如限制一个手机号发多少条短信、一个接口一分钟限制多少请求、一个接口一天限制调用多少次等等。
- 分布式锁
这个主要利用 redis 的 setnx 命令进行,setnx:"set if not exists" 就是如果不存在则成功设置缓存同时返回 1,否则返回 0 ,这个特性在俞你奔远方的后台中有所运用,因为我们服务器是集群的,定时任务可能在两台机器上都会运行,所以在定时任务中首先 通过 setnx 设置一个 lock,如果成功设置则执行,如果没有成功设置,则表明该定时任务已执行。 当然结合具体业务,我们可以给这个 lock 加一个过期时间,比如说 30 分钟执行一次的定时任务,那么这个过期时间设置为小于 30 分钟的一个时间就可以,这个与定时任务的周期以及定时任务执行消耗时间相关。
在分布式锁的场景中,主要用在比如秒杀系统等。
4.Redis 有哪些数据类型?
- 5 种基础数据类型,分别是:String、List、Set、Zset、Hash。
| 结构类型 | 结构存储的值 | 结构的读写能力 |
|---|---|---|
| String 字符串 | 可以是字符串、整数或浮点数 | 对整个字符串或字符串的一部分进行操作;对整数或浮点数进行自增或自减操作; |
| List 列表 | 一个链表,链表上的每个节点都包含一个字符串 | 对链表的两端进行 push 和 pop 操作,读取单个或多个元素;根据值查找或删除元素; |
| Set 集合 | 包含字符串的无序集合 | 字符串的集合,包含基础的方法有看是否存在添加、获取、删除;还包含计算交集、并集、差集等 |
| Hash 散列 | 包含键值对的无序散列表 | 包含方法有添加、获取、删除单个元素 |
| Zset 有序集合 | 和散列一样,用于存储键值对 | 字符串成员与浮点数分数之间的有序映射;元素的排列顺序由分数的大小决定;包含方法有添加、获取、删除单个元素以及根据分值范围或成员来获取元素 |
- 三种特殊的数据类型 分别是 HyperLogLogs(基数统计), Bitmaps (位图) 和 geospatial (地理位置)
5.谈谈 Redis 的对象机制(redisObject)?
比如说, 集合类型就可以由字典和整数集合两种不同的数据结构实现, 但是, 当用户执行 ZADD 命令时, 他/她应该不必关心集合使用的是什么编码, 只要 Redis 能按照 ZADD 命令的指示, 将新元素添加到集合就可以了。
这说明, 操作数据类型的命令除了要对键的类型进行检查之外, 还需要根据数据类型的不同编码进行多态处理.
为了解决以上问题, Redis 构建了自己的类型系统, 这个系统的主要功能包括:
- redisObject 对象.
- 基于 redisObject 对象的类型检查.
- 基于 redisObject 对象的显式多态函数.
- 对 redisObject 进行分配、共享和销毁的机制.
/* * Redis 对象 */ typedef struct redisObject { // 类型 unsigned type:4; // 编码方式 unsigned encoding:4; // LRU - 24 位, 记录最末一次访问时间(相对于 lru_clock); 或者 LFU(最少使用的数据:8 位频率,16 位访问时间) unsigned lru:LRU_BITS; // LRU_BITS: 24 // 引用计数 int refcount; // 指向底层数据结构实例 void *ptr; } robj;
@pdai: 代码已经复制到剪贴板
6.Redis 数据类型有哪些底层数据结构?
- 简单动态字符串 - sds
- 压缩列表 - ZipList
- 快表 - QuickList
- 字典/哈希表 - Dict
- 整数集 - IntSet
- 跳表 - ZSkipList
7.Redis 的持久化机制是什么?各自的优缺点?一般怎么用?
- RDB 持久化是把当前进程数据生成快照保存到磁盘上的过程; 针对 RDB 不适合实时持久化的问题,Redis 提供了 AOF 持久化方式来解决.
- AOF 是“写后”日志,Redis 先执行命令,把数据写入内存,然后才记录日志。日志里记录的是 Redis 收到的每一条命令,这些命令是以文本形式保存。
- Redis 4.0 中提出了一个混合使用 AOF 日志和内存快照的方法。简单来说,内存快照以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。
这样一来,快照不用很频繁地执行,这就避免了频繁 fork 对主线程的影响。而且,AOF 日志也只用记录两次快照间的操作,也就是说,不需要记录所有操作了,因此,就不会出现文件过大的情况了,也可以避免重写开销。
8.Redis 过期键的删除策略有哪些?
在单机版 Redis 中,存在两种删除策略:
- 惰性删除:服务器不会主动删除数据,只有当客户端查询某个数据时,服务器判断该数据是否过期,如果过期则删除。
- 定期删除:服务器执行定时任务删除过期数据,但是考虑到内存和 CPU 的折中(删除会释放内存,但是频繁的删除操作对 CPU 不友好),该删除的频率和执行时间都受到了限制。
在主从复制场景下,为了主从节点的数据一致性,从节点不会主动删除数据,而是由主节点控制从节点中过期数据的删除。由于主节点的惰性删除和定期删除策略,都不能保证主节点及时对过期数据执行删除操作,因此,当客户端通过 Redis 从节点读取数据时,很容易读取到已经过期的数据。
Redis 3.2 中,从节点在读取数据时,增加了对数据是否过期的判断:如果该数据已过期,则不返回给客户端;将 Redis 升级到 3.2 可以解决数据过期问题。
9.Redis 如何做内存优化?
- 缩减键值对象: 缩减键(key)和值(value)的长度,
key 长度:如在设计键时,在完整描述业务情况下,键值越短越好。
value 长度:值对象缩减比较复杂,常见需求是把业务对象序列化成二进制数组放入 Redis。首先应该在业务上精简业务对象,去掉不必要的属性避免存储无效数据。其次在序列化工具选择上,应该选择更高效的序列化工具来降低字节数组大小。以 JAVA 为例,内置的序列化方式无论从速度还是压缩比都不尽如人意,这时可以选择更高效的序列化工具,如: protostuff,kryo 等,下图是 JAVA 常见序列化工具空间压缩对比。
- 共享对象池
对象共享池指 Redis 内部维护 [0-9999] 的整数对象池。创建大量的整数类型 redisObject 存在内存开销,每个 redisObject 内部结构至少占 16 字节,甚至超过了整数自身空间消耗。所以 Redis 内存维护一个 [0-9999] 的整数对象池,用于节约内存。 除了整数值对象,其他类型如 list,hash,set,zset 内部元素也可以使用整数对象池。因此开发中在满足需求的前提下,尽量使用整数对象以节省内存。
- 字符串优化
- 编码优化
- 控制 key 的数量
10.Redis key 的过期时间和永久有效分别怎么设置?
EXPIRE 和 PERSIST 命令
11.redis 优缺点
优点:
1 因为是纯内存操作,Redis 的性能非常出色,每秒可以处理超过 10 万次读写操 作,是已知性能最快的 Key-Value 数据库。 Redis 支持事务 、持久化
2、单线程操作,避免了频繁的上下文切换。
3、采用了非阻塞 I/O 多路复用机制。I/O 多路复用就是只有单个线程,通过跟 踪 每个 I/O 流的状态,来管理多个 I/O 流。
缺点:
1.缓存和数据库双写一致性问题
一致性的问题很常见,因为加入了缓存之后,请求是先从 redis 中查询,如果 redis 中 存在数据就不会走数据库了,如果不能保证缓存跟数据库的一致性就会导致请求获取到 的数据不是最新的数据。
解决方案: 1、编写删除缓存的接口,在更新数据库的同时,调用删除缓存 的接口删除缓存中的数据。这么做会有耦合高以及调用接口失败的情况。
2、消息队列:ActiveMQ ,消息通知。
2.缓存的并发竞争问题
并发竞争,指的是同时有多个子系统去 set 同一个 key 值。 解决方案: 1、最简单的方式就是准备一个分布式锁,大家去抢锁,抢到 锁就做 set 操作即可
3.缓存雪崩问题
缓存雪崩,即缓存同一时间大面积的失效,这个时候又来了一波 请求,结果请求都怼到数据库上,从而导致数据库连接异常。
解决方案: 1、给缓存的失效时间,加上一个随机值,避免集体失效。
2、使用互斥锁,但是该方案吞吐量明显下降了。
3、搭建 redis 集群。
4.缓存击穿问题
缓存穿透,即黑客故意去请求缓存中不存在的数据,导致所有的请求都怼到数据库上 , 从而数据库连接异常。
解决方案: 1、利用互斥锁,缓存失效的时候,先去获得锁,得到锁了, 再去请求数据库。没得到锁,则休眠一段时间重试
2、采用异步更新策略,无论 key 是否取到值,都直接返回, value 值中维护一个缓存失效时间,缓存如果过期,异步起一个线程 去读数据库,更新缓存。