1、Redis 一般都有哪些使用场景?
2、Redis 有哪些常⻅的功能?
- 数据缓存功能
- 分布式锁的功能
- 支持数据持久化
- 支持事务
- 支持消息队列
3、Redis 支持的数据类型有哪些?
主要支持字符串、哈希表、列表、集合、有序集合五种。
-
1、最常见的就是String类型
String类型是二进制安全的,意思是redis的String可以包含任何数据,比如jpg图片或者序列化的对象;String类型是Redis最基本的数据类型,一个键最大能存储512MB。
-
2、Hash(哈希)
- 在电商项目中开发购物车模块时使用的就是Hash结构
-
3、List(列表)
Redis列表是简单的字符串列表,可以类比到C++中的std::list,简单的说就是一个链表或者说是一个队列。可以从头部或尾部向Redis列表添加元素。列表的最大长度为2^32 - 1,也即每个列表支持超过40亿个元素。
-
4、Set(集合)
可以理解为一堆值不重复的列表,类似数学领域中的集合概念,且Redis也提供了针对集合的求交集、并集、差集等操作。
-
5、Zset(有序集合)
当你需要一个有序的并且不重复的集合列表,那么可以选择sorted set数据结构
4、Redis为什么这么快?
- 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速;
- 数据结构简单,对数据操作也简单;
- 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
- 使用多路 I/O 复用模型,非阻塞 IO。
5、什么是缓存穿透?怎么解决?
-
缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。
-
解决方案:
-
缓存空对象:如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
-
缓存空对象带来的问题:
1、空值做了缓存,意味着缓存中存了更多的键,需要更多的内存空间,比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。
2、缓存和存储的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如:过期时间设置为 5分钟,如果此时存储添加了这个数据,那此段时间就会出现缓存和存储数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象。
6、什么是缓存雪崩?该如何解决?
- 如果缓存集中在一段时间内失效,所有的查询都落在数据库上,造成了缓存雪崩。
- 解决方案:
- 1、加锁排队:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个 key 只允许一个线程查询数据和写缓存,其他线程等待;
- 2、数据预热:可以通过缓存 reload 机制,预先去更新缓存,再即将发生大并发访问前手动触发加载缓存不同的 key,设置不同的过期时间,让缓存失效的时间点尽量均匀;
- 3、做二级缓存,或者双缓存策略:Cache1 为原始缓存,Cache2 为拷贝缓存,Cache1 失效时,可以访问 Cache2,Cache1 缓存失效时间设置为短期,Cache2 设置为长期。
- 4、在缓存的时候给过期时间加上一个随机值,这样就会大幅度的减少缓存在同一时间过期。
7、怎么保证缓存和数据库数据的一致性?
1、从理论上说,只要我们设置了合理的键的过期时间,我们就能保证缓存和数据库的数据最终是一致的。因为只要缓存数据过期了,就会被删除。随后读的时候,因为缓存里没有,就可以查数据库的数据,然后将数据库查出来的数据写入到缓存中。除了设置过期时间,我们还需要做更多的措施来尽量避免数据库与缓存处于不一致的情况发生;
2、新增、更改、删除数据库操作时同步更新 Redis,可以使用事物机制来保证数据的一致性;
一般有如下四种方案,详情看这里:
- 先更新数据库,后更新缓存
- 先更新缓存,后更新数据库
- 先删除缓存,后更新数据库
- 先更新数据库,后删除缓存
第一种和第二种方案,没有人使用的,因为第一种方案存在问题是:并发更新数据库场景下,会将脏数据刷到缓存。
第二种方案存在的问题是:如果先更新缓存成功,但是数据库更新失败,则肯定会造成数据不一致。
双写一致性方案一:先删除缓存,后更新数据库
双写一致性方案二:先更新数据库,后删除缓存
8、Redis 持久化有几种方式?
持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。Redis 提供了两种持久化方式:RDB(默认) 和 AOF
-
RDB
RDB 是 Redis DataBase 的缩写。按照一定的时间周期策略把内存的数据以快照的形式保存到硬盘的二进制文件。即 Snapshot 快照存储,对应产生的数据文件为 dump.rdb,通过配置文件中的 save 参数来定义快照的周期。核心函数:rdbSave(生成 RDB 文件)和 rdbLoad(从文件加载内存)两个函数。
-
AOF
AOF 是 Append-only file 的缩写。Redis会将每一个收到的写命令都通过 Write 函数追加到文件最后,类似于 MySQL 的 binlog。当 Redis 重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。每当执行服务器(定时)任务或者函数时,flushAppendOnlyFile 函数都会被调用, 这个函数执行以下两个工作:
- WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件;
- SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。
RDB 和 AOF 的区别:
- AOF 文件比 RDB 更新频率高,优先使用 AOF 还原数据;
- AOF比 RDB 更安全也更大;
- RDB 性能比 AOF 好;
- 如果两个都配了优先加载 AOF。
9、Redis过期策略和内存淘汰策略
1、定期删除
redis默认每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。
问题:因为是随机抽取,定期删除可能导致很多过期key到了时间并没有被删除掉,这时候该怎么办呢?惰性删除该上场了。
2、惰性删除
在你获取某个key时,redis会检查一下,是否设置了过期时间?设置了过期时间是否过期了?如果过期了就删除,不会给你返回任何东西。
问题:实际上,这仍然有问题:比如定期删除漏掉了很多过期key,然后也没及时查询,也就没走惰性删除,这时就会有大量过期的key堆积在内存中,导致redis内存快耗尽了,怎么办?
3、如果内存空间将要满的时候该怎么办呢?
出现这种情况,就应该淘汰策略上场了,淘汰策略从设置过期时间中的key和从所有的key方面分为两类。
-
设置过期时间的key中
▪ volatile-lru:设置了过期时间的键空间中,移除最近最少未使用的key。
▪ volatile-lru:设置了过期时间的键空间中,移除即将过期的key。
▪ volatile-random:设置了过期时间的键空间中,随机移除某个key
-
所有的key中
▪ volatile-random:设置了过期时间的键空间中,随机移除某个key
▪ 键空间中,移除最近最少未使用的key。
▪ allkeys-random:键空间中,随机移除某个key
10、通过expire来设置key 的过期时间,那么对过期的数据怎么处理呢?
除了缓存服务器自带的缓存失效策略之外(Redis默认的有6种策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:
- 1、定时去清理过期的缓存;
- 2、当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存;
两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!具体用哪种方案,大家可以根据自己的应用场景来权衡。
11、什么是 RDB 内存快照?
-
在 Redis 执行「写」指令过程中,内存数据会一直变化。所谓的内存快照,指的就是 Redis 内存中的数据在某一刻的状态数据。
-
好比时间定格在某一刻,当我们拍照的,通过照片就能把某一刻的瞬间画面完全记录下来
-
Redis 跟这个类似,就是把某一刻的数据以文件的形式拍下来,写到磁盘上。这个快照文件叫做 RDB 文件,RDB 就是 Redis DataBase 的缩写。
12、在生成 RDB 期间,Redis 可以同时处理写请求么?
- 可以的,Redis 使用操作系统的多进程写时复制技术 COW(Copy On Write) 来实现快照持久化,保证数据一致性。
- Redis 在持久化时会调用 glibc 的函数
fork
产生一个子进程,快照持久化完全交给子进程来处理,父进程继续处理客户端请求。 - 当主线程执行写指令修改数据的时候,这个数据就会复制一份副本,
bgsave
子进程读取这个副本数据写到 RDB 文件。 - 这既保证了快照的完整性,也允许主线程同时对数据进行修改,避免了对正常业务的影响。
13、如何实现数据尽可能少丢失又能兼顾性能呢?
-
重启 Redis 时,我们很少使用 rdb 来恢复内存状态,因为会丢失大量数据。我们通常使用 AOF 日志重放,但是重放 AOF 日志性能相对 rdb 来说要慢很多,这样在 Redis 实例很大的情况下,启动需要花费很长的时间。
-
Redis 4.0 为了解决这个问题,带来了一个新的持久化选项——混合持久化。将 rdb 文件的内容和增量的 AOF 日志文件存在一起。这里的 AOF 日志不再是全量的日志,而是自持久化开始到持久化结束的这段时间发生的增量 AOF 日志,通常这部分 AOF 日志很小。
-
于是在 Redis 重启的时候,可以先加载 rdb 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,重启效率因此大幅得到提升
14、Redis真的是单线程?
-
1、没错,大家所熟知的 Redis 确实是单线程模型,指的是执行 Redis 命令的核心模块是单线程的,而不是整个 Redis 实例就一个线程,Redis 其他模块还有各自模块的线程的。
-
2、Redis 不仅仅是单线程
一般来说 Redis 的瓶颈并不在 CPU,而在内存和网络。如果要使用 CPU 多核,可以搭建多个 Redis 实例来解决。
其实,Redis 4.0 开始就有多线程的概念了,比如 Redis 通过多线程方式在后台删除对象、以及通过 Redis 模块实现的阻塞命令等
-
3、为什么网络处理要引入多线程?
之前的段落说了,Redis 的瓶颈并不在 CPU,而在内存和网络;
内存不够的话,可以加内存或者做数据结构优化和其他优化等,但网络的性能优化才是大头,网络 IO 的读写在 Redis 整个执行期间占用了大部分的 CPU 时间,如果把网络处理这部分做成多线程处理方式,那对整个 Redis 的性能会有很大的提升。
网上也有对 Redis 单/多线程情况下的 get/set 操作性能做了对比:
Redis 在网络处理方面上了多线程确实会让 Redis 性能上一个新台阶,目前最新的 6.0 版本中,IO 多线程处理模式默认是不开启的,需要去配置文件中开启并配置线程数。
15、什么是分布式锁?为什么用分布式锁?
-
锁在程序中的作用就是同步工具,保证共享资源在同一时刻只能被一个线程访问,Java中的锁我们都很熟悉了,像synchronized 、Lock都是我们经常使用的,但是Java的锁只能保证单机的时候有效,分布式集群环境就无能为力了,这个时候我们就需要用到分布式锁。
-
分布式锁,顾名思义,就是分布式项目开发中用到的锁,可以用来控制分布式系统之间同步访问共享资源。
-
思路是:在整个系统提供一个全局、唯一的获取锁的“东西”,然后每个系统在需要加锁时,都去问这个“东西”拿到一把锁,这样不同的系统拿到的就可以认为是同一把锁。至于这个“东西”,可以是Redis、Zookeeper,也可以是数据库
-
一般来说,分布式锁需要满足的特性有这么几点:
- 1、互斥性:在任何时刻,对于同一条数据,只有一台应用可以获取到分布式锁;
- 2、高可用性:在分布式场景下,一小部分服务器宕机不影响正常使用,这种情况就需要将提供分布式锁的服务以集群的方式部署;
- 3、防止锁超时:如果客户端没有主动释放锁,服务器会在一段时间之后自动释放锁,防止客户端宕机或者网络不可达时产生死锁;
- 4、独占性:加锁解锁必须由同一台服务器进行,也就是锁的持有者才可以释放锁,不能出现你加的锁,别人给你解锁了。
-
一般来说,分布式锁需要满足的特性有这么几点:
- 1、互斥性:在任何时刻,对于同一条数据,只有一台应用可以获取到分布式锁;
- 2、高可用性:在分布式场景下,一小部分服务器宕机不影响正常使用,这种情况就需要将提供分布式锁的服务以集群的方式部署;
- 3、防止锁超时:如果客户端没有主动释放锁,服务器会在一段时间之后自动释放锁,防止客户端宕机或者网络不可达时产生死锁;
- 4、独占性:加锁解锁必须由同一台服务器进行,也就是锁的持有者才可以释放锁,不能出现你加的锁,别人给你解锁了。