请简单介绍一下 Redis?
Redis 就是一个使用 C 语言开发的数据库,不过与传统数据库不同的是 Redis 的数据是存在内存中的 ,也就是它是内存数据库,所以读写速度非常快,因此 Redis 被广泛应用于缓存方向。Redis 除了做缓存之外,也经常用来做分布式锁,甚至是消息队列。Redis 提供了多种数据类型来支持不同的业务场景。Redis 还支持事务 、持久化、 集群方案
分布式缓存常见的技术选型方案有哪些?
使用的比较多的主要是 Memcached 和 Redis
为什么要用 Redis/为什么要用缓存?
使用缓存主要是为了提升用户体验以及应对更多的用户,
高性能 :用户访问的数据属于高频数据并且不会经常改变的话,那么我们就可以将数据存在缓存中。保证用户下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。不过,要保持数据库和缓存中的数据的一致性。 如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据
高并发:直接操作缓存能够承受的数据库请求数量是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。进而,我们也就提高了系统整体的并发。
Redis 常见数据结构以及使用场景?
string:string 数据结构是简单的 key-value 类型。Redis 并没有使用 C 的字符串表示,而是自己构建了一种 简单动态字符串 ,不光可以保存文本数据还可以保存二进制数据,并且获取字符串长度复杂度为 O(1)(C 字符串为 O(N)),除此之外,Redis 的 SDS API 是安全的,不会造成缓冲区溢出。应用场景: 一般常用在需要计数的场景,比如用户的访问次数、热点文章的点赞转发数量等等
list:list 即是 链表,Redis 的 list 的实现为一个 双向链表。常用命令: rpush,lpop,lpush,rpop,lrange,llen
等。应用场景: 发布与订阅或者说消息队列、慢查询
hash: hash 类似于 JDK1.8 前的 HashMap,是一个 string 类型的 field 和 value 的映射表。常用命令: hset,hmset,hexists,hget,hgetall,hkeys,hvals
等。应用场景: 系统中对象数据的存储
set: set 类似于 Java 中的 HashSet
。Redis 中的 set 类型是一种无序集合,集合中的元素没有先后顺序,不出现重复数据。并且 set 提供了判断某个成员是否在一个 set 集合内的重要接口,还可以基于 set 轻易实现交集、并集、差集的操作。常用命令: sadd,spop,smembers,sismember,scard,sinterstore,sunion
等。
sorted set:和 set 相比,sorted set 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,还可以通过 score 的范围来获取元素的列表。应用场景: 需要对数据根据某个权重进行排序的场景。比如在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜
bitmap:bitmap 存储的是连续的二进制数字。应用场景: 比如是否点赞过某个视频
请讲一下Redis 单线程模型?
Redis 基于 Reactor 模式来设计开发了自己的一套高效的事件处理模型 ,这套事件处理模型对应的是 Redis 中的文件事件处理器(file event handler)。由于文件事件处理器(file event handler)是单线程方式运行的,所以我们一般都说 Redis 是单线程模型。
既然Redis是单线程,那怎么监听大量的客户端连接呢?.
Redis 通过IO 多路复用程序 来监听来自客户端的大量连接(或者说是监听多个 socket), I/O 多路复用技术的使用让 Redis 不需要额外创建多余的线程来监听客户端的大量连接,降低了资源的消耗
Redis 为什么不使用多线程?
- 单线程编程容易并且更容易维护;
- Redis 的性能瓶颈不在 CPU ,主要在内存和网络;
- 多线程就会存在死锁、线程上下文切换等问题,甚至会影响性能。
虽然说 Redis 是单线程模型,但是,实际上,**Redis 在 4.0 之后的版本中就已经加入了对多线程的支持,主要是针对一些大键值对的删除操作的命令,**但大体上来说,Redis 6.0 之前主要还是单线程处理。
Redis6.0 之后为何引入了多线程?
Redis6.0 引入多线程主要是为了提高网络 IO 读写性能,因为这个算是 Redis 中的一个性能瓶颈(Redis 的瓶颈主要受限于内存和网络)。虽然,Redis6.0 引入了多线程,但是 Redis 的多线程只是在网络数据的读写这类耗时操作上使用了,执行命令仍然是单线程顺序执行。因此,你也不需要担心线程安全问题。Redis6.0 的多线程默认是禁用的,只使用主线程。如需开启需要修改 redis 配置文件
Redis 给缓存数据设置过期时间有啥用?
因为内存是有限的,如果缓存中的所有数据都是一直保存的话,会导致Out of memory。除此之外,很多时候,我们的业务场景就是需要某个数据只在某一时间段内存在,比如我们的短信验证码可能只在 1 分钟内有效
Redis 是如何判断数据是否过期的呢?
Redis 通过一个叫做过期字典(可以看作是 hash 表)来保存数据过期的时间。过期字典的键指向 Redis 数据库中的某个 key(键),过期字典的值是一个 long long 类型的整数,这个整数保存了 key 所指向的数据库键的过期时间
redis的过期的数据的删除策略了解么?
Redis 采用的是 定期删除+惰性/懒汉式删除
- 惰性删除 :只会在取出 key 的时候才对数据进行过期检查。这样对 CPU 最友好,但是可能会造成太多过期 key 没有被删除。
- 定期删除 : 每隔一段时间抽取一批 key 执行删除过期 key 操作。并且,Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影响。
Redis 内存淘汰机制了解么?
仅仅通过给 key 设置过期时间还是有问题的。因为还是可能存在定期删除和惰性删除漏掉了很多过期 key 的情况。这样就导致大量过期 key 堆积在内存里,然后就 Out of memory 了。Redis 内存淘汰机制解决这个问题。Redis 提供 6 种数据淘汰策略,最常用的是allkeys-lru(least recently used):当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key
怎么保证 Redis 挂掉之后再重启数据可以进行恢复?
Redis 支持持久化,而且支持两种不同的持久化操作。Redis 的一种持久化方式叫快照(snapshotting,RDB),另一种方式是只追加文件(append-only file, AOF)
RDB: 通过创建快照来获得存储在内存里面的数据在某个时间点上的副本,是 Redis 默认采用的持久化方式,在 Redis.conf 配置文件中默认有此下配置
save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
AOF:
与快照持久化相比,AOF 持久化的实时性更好,因此已成为主流的持久化方案
开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到内存缓存 server.aof_buf
中,然后再根据 appendfsync
配置来决定何时将其同步到硬盘中的 AOF 文件。
appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
appendfsync everysec #每秒钟同步一次,显式地将多个写命令同步到硬盘
appendfsync no #让操作系统决定何时进行同步
为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec
选项 ,让 Redis 每秒同步一次 AOF 文件,Redis 性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据
Redis 4.0 开始支持 RDB 和 AOF 的混合持久化
什么是AOF 重写?
AOF 重写可以产生一个新的 AOF 文件,这个新的 AOF 文件和原有的 AOF 文件所保存的数据库状态一样,但体积更小,不会包含任何浪费空间的冗余命令
什么是 bigkey?
简单来说,如果一个 key 对应的 value 所占用的内存比较大,那这个 key 就可以看作是 bigkey。具体多大才算大呢?有一个不是特别精确的参考标准:string 类型的 value 超过 10 kb,复合类型的 value 包含的元素超过 5000 个(对于复合类型的 value 来说,不一定包含的元素越多,占用的内存就越多)
除了会消耗更多的内存空间,bigkey 对性能也会有比较大的影响。因此,我们应该尽量避免写入 bigkey!
什么是Redis 事务?
Redis 可以通过 MULTI
,EXEC
,DISCARD
和 WATCH
等命令来实现事务(transaction)功能
使用MULTI
命令后可以输入多个命令。Redis 不会立即执行这些命令,而是将它们放到队列,当调用了 EXEC
命令将执行所有命令
也可以通过 DISCARD
命令取消一个事务,它会清空事务队列中保存的所有命令
WATCH
命令用于监听指定的键,当调用 EXEC
命令执行事务时,如果一个被 WATCH
命令监视的键被修改的话,整个事务都不会执行,直接返回失败。
Redis 的事务和关系型数据库的事务有哪些不同?
我们知道事务具有四大特性: 1. 原子性,2. 隔离性,3. 持久性,4. 一致性。
Redis 是不支持 roll back 的,因而不满足原子性的(而且不满足持久性)。Redis 官网也解释了自己为啥不支持回滚。简单来说就是 Redis 开发者们觉得没必要支持回滚,这样更简单便捷并且性能更好。Redis 开发者觉得即使命令执行错误也应该在开发过程中就被发现而不是生产过程中。你可以将 Redis 中的事务就理解为 :Redis 事务提供了一种将多个命令请求打包的功能。然后,再按顺序执行打包的所有命令,并且不会被中途打断。
什么是缓存穿透?有哪些解决办法?
缓存穿透说简单点就是大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。举个例子:某个黑客故意制造我们缓存中不存在的 key 发起大量请求,导致大量请求落到数据库。
解决方案:
- 最基本的就是首先做好参数校验,一些不合法的参数请求直接抛出异常信息返回给客户端。
- 缓存无效 key。如果缓存和数据库都查不到某个 key 的数据,就写一个到 Redis 中去并设置过期时间,这种方式可以解决请求的 key 变化不频繁的情况。但如果黑客恶意攻击,每次构建不同的请求 key,会导致 Redis 中缓存大量无效的 key 。很明显,这种方案并不能从根本上解决此问题。
- 布隆过滤器。把所有可能存在的请求的值都存放在布隆过滤器中,当用户请求过来,先判断用户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信息给客户端,存在的话才会走下面的流程。
什么是缓存雪崩?有哪些解决办法?
缓存雪崩描述的就是这样一个简单的场景:缓存在同一时间大面积的失效,后面的请求都直接落到了数据库上,造成数据库短时间内承受大量请求。
还有一种缓存雪崩的场景是:有一些被大量访问数据(热点缓存)在某一时刻大面积失效,导致对应的请求直接落到了数据库上。
解决方案:
针对 Redis 服务不可用的情况:
- 采用 Redis 集群,避免单机出现问题导致整个服务都没办法使用。
- 限流,避免同时处理大量的请求。
针对热点缓存失效的情况:
- 设置不同的失效时间比如随机设置缓存的失效时间。
- 缓存永不失效。
如何保证缓存和数据库数据的一致性?
缓存常用的3种读写策略:
- Cache Aside Pattern(旁路缓存模式) 是我们平时使用比较多的一个缓存读写模式,比较适合读请求比较多的场景。
- 写 :
- 先更新 DB
- 然后直接删除 cache 。
- 读 :
- 从 cache 中读取数据,读取到就直接返回
- cache中读取不到的话,就从 DB 中读取数据返回
- 再把数据放到 cache 中。
- Read/Write Through Pattern(读写穿透)
- Write Behind Pattern(异步缓存写入)
为什么会有 Redis 内存碎片? 如何清理 Redis 内存碎片?
1、Redis 存储存储数据的时候向操作系统申请的内存空间可能会大于数据实际需要的存储空间。
2、频繁修改 Redis 中的数据也会产生内存碎片。 当 Redis 中的某个数据删除时,Redis 通常不会轻易释放内存给操作系统。
Redis4.0-RC3 版本以后自带了内存整理,可以通过命令开启和配置, 但Redis 自动内存碎片清理机制可能会对 Redis 的性能产生影响