目录
1、什么是Redis? Redis有哪些特点?Redis有哪些常见的应用场景?
2、讲一下什么是缓存穿透、缓存击穿和缓存雪崩,以及给出解决方案
8、Redis 的持久化机制有哪些?说说各自的优缺点和应用场景?
1、什么是Redis? Redis有哪些特点?Redis有哪些常见的应用场景?
Redis是一个基于内存的键值型(key-value)的NoSQL数据库(非关系型数据库)。
key一般是String类型,而value支持丰富的数据类型,包括String、Hash、List、Set、SortedSet五种基本的类型,此外还有GEO、BitMap、HyperLogLog等其他类型。
Redis特点
- 高性能:Redis使用内存来存储数据类型,并且数据存储在单一的进程中,因此速度非常快。
- 多样的数据类型:Redis支持多种数据类型结构
- 所有的操作命令都是原子性的
- 支持数据持久化
- RDB:通过创建快照来获取存储在内存里面的数据在某个节点上的副本
- AOF:执行完更改数据的命令后,会将命令记录到日志中
- 支持事务
- 允许多个命令按顺序执行并不会被打断
- 不支持回滚
- Redis支持分布式部署,可以将数据分布在多个节点上
应用场景
- 缓存:
- 将热点数据存储到Redis中,提高数据读取和系统的响应速度
- 将用户凭证(如token)存入到Redis中,实现单点登录
- 分布式锁:
- 利用Redis的setnx命令实现互斥锁
- 基于Redisson实现分布式锁
- 限流
- 通过Redis + Lua脚本的方式来实现限流
- 消息队列
- 基于List实现:利用LPUSH和RPOP
- 基于PUB/SUB实现
- 基于Stream实现
- 排行榜
- 由于SortedSet是支持排序的,可以用于各种排行榜的场景
2、讲一下什么是缓存穿透、缓存击穿和缓存雪崩,以及给出解决方案
缓存穿透
- 缓存穿透
就是查询一个不存在的数据,MySQL查询不到数据,也不会直接写入缓存,就会导致每次请求都会查询数据库
- 解决方案
方案一:
如果缓存和数据库都查不到某个 key 的数据就写一个空值到 Redis 中去并设置过期时间
优点:简单方便
缺点、:消耗内存,也有可能会发生缓存和数据库数据不一致的问题
方案二:
使用布隆过滤器
- 把所有可能存在的请求的值都存放在布隆过滤器中,当用户请求过来,先判断用户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信息给客户端,存在的话才会走下面的流程。
- 但是,需要注意的是布隆过滤器可能会存在误判的情况。总结来说就是:布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不在,那么这个元素一定不在。
- 布隆过滤器加入数据流程
- 使用布隆过滤器中的哈希函数对元素值进行计算,得到哈希值
- 根据得到的哈希值,在位数组中吧对应下标的值置位1
- 布隆过滤器判断元素是否存在流程
- 对元素进行哈希计算
- 得到值后判断位数组中的每一个元素是否都为1,如果都为1,则存在布隆过滤器中,如果存在一个不为1,说明该元素不在布隆过滤器中
优点:内存占用较少,没有多余的key
缺点:可能会存在误判
缓存击穿
缓存击穿中,请求的 key 对应的是 热点数据 ,该数据 存在于数据库中,但不存在于缓存中(通常是因为缓存中的那份数据已经过期) 。这就可能会导致瞬时大量的请求直接打到了数据库上,对数据库造成了巨大的压力,可能直接就被这么多请求弄宕机了。
解决办法:
- 设置热点数据不过期或者过期时间长
- 请求数据库写数据到缓存之前,先获取互斥锁,保证只有一个请求会落到数据库上,减少数据库的压力(数据的强一致性)
- 设置当前key逻辑过期 (高可用性,但数据同步可能做不到强一致性)
- 在设置可以的时候,设置一个过期时间字段一块存入到缓存中,不给当前的key设置过期时间
- 当查询的时候,从Redis取出数据后判断时间是否过期
- 如果过期了就开通另一个线程进行数据同步,当前线程正常返回数据
缓存雪崩
缓存在同一时间大面积的失效,导致大量的请求都直接落到了数据库上,对数据库造成了巨大的压力。
解决方案:
- 设置不同的失效时间,比如随机设置缓存的失效时间
3、讲一下Redis中的内存淘汰机制、有哪些内存淘汰策略
Redis是一种基于内存的键值数据库,由于内存有限,当Redis占用的内存达到上限的时,就需要进行内存淘汰,从而腾出一些内存空间。
内存淘汰机制
- 定期删除:Redis设置一个定时器,定期扫描键空间中的键,删除里面过期的key(从一定数量的数据库中取出一定数量的随机key进行检查,并删除其中过期的key)
- 优点:可以通过限制删除操作执行的时长和频率来减少删除操作对CPU的影响。也能有效释放过期键的占用空间
- 缺点:难以确认删除操作执行的时长和频率
- 惰性删除:设置key的过期时间后,但需要该key的时候,再检查其是否过期,如果过期就删掉,反之返回该key
- 优点:对CPU友好,只有在使用该key时才会进行过期检查,对于很多用不到的key不用浪费时间进行过期检查
- 对内存不友好,如果一个key已经过期了,但一直没有使用,那么该key一直就会存在内存中,不释放
- 内存淘汰策略:当Redis内存占用达到上限时,会根据内存淘汰策略选择一些键进行删除
内存淘汰策略
当Redis中内存不够用时,此时向Redis中添加新的key,那么Redis就会按照某一种规则将内存中的数据删除掉,这种数据的删除规则被称为内存的淘汰策略
- noeviction:不淘汰任何key,但内存满时不允许写入新数据,默认就是这种策略
- volatile-ttl:对设置了TTL的key,比较key剩余TTL值,TTL越小越先被淘汰
- allkeys-random:对全体key,随机进行淘汰
- volatile-random:对设置了TTL的key,随机进行淘汰
- allkey-lru:对所有key,基于LRU算法进行淘汰
- volatile-lru:对于设置了TTL的key,基于LRU算法进行淘汰
- allkey-lfu:对所有key,基于LFU算法进行淘汰
- volatile-lfu:对于设置了TTL的key,基于LFU算法进行淘汰
LRU:最近最少使用页面置换算法(Least Recently Used),也就是首先淘汰最长时间未被使用的页面!
LFU:最近最不常用页面置换算法(Least Frequently Used),也就是淘汰一定时期内被访问次数最少的页!
如何设置Redis的内存淘汰策略
- Redis的配置文件中,找到maxmemory和maxmenory-policy两个参数
- 将maxmemory设置为你想要的内存限制,比如100Mb
- 将maxmenory-policy设置为你想要的内存淘汰策略,比如allkeys-lru
- 保存配置文件并重启Redis服务
也可以通过命令行修改这两个参数
redis-cli config set maxmemory 1--mb
redis-cli config set maxmemory-policy allkeys-lru
4、Redis 有哪些数据类型
Redis ⽀持多种数据类型,不同的数据类型可以满⾜不同的需求。
- String(字符串):Redis 中最基本的数据类型,可以存储任何形式的数据,例如整数、浮点数、⼆进制数据等。
- Hash(哈希):Redis 中的⼀种键值对类型,可以存储多个键值对,每个键值对⼜是⼀个键值对结构。
- List(列表):Redis 中的⼀个有序列表类型,可以存储多个元素,每个元素都有⼀个索引,⽀持多种列表操作,例如插⼊、删除、查找等。
- Set(集合):Redis 中的⼀种⽆序集合类型,可以存储多个元素,每个元素都是唯⼀的,⽀持多种集合操作,例如交集、并集、差集等。
- ZSet(有序集合):Redis 中的⼀种有序集合类型,可以存储多个元素,每个元素都有⼀个分值,⽀持根据分值进⾏排序和查询等操作。
Redis 的基础数据结构有三种:字符串、列表和哈希,其他的数据类型都是基于这三种数据结构进⾏扩展和衍⽣的。例如,Redis 的 Set 数据类型就是基于字符串实现的。
除了基础数据结构之外,Redis 还提供了多种⾼级数据结构,例如:
- HyperLogLog:⼀种基数估计算法,⽤于估计⼀个数据集合的基数。
- GeoHash:⼀种地理位置编码算法,可以对地理位置信息进⾏编码和查询。
- Pub/Sub:⼀种消息队列机制,可以实现消息的订阅和发布。
- Bitmaps:⼀种位图数据结构,可以进⾏⾼效的位运算,⽤于统计⽤户在线时⻓、⽹站访问量等。
- Lua 脚本:Redis 中可以使⽤ Lua 脚本进⾏扩展和定制,可以实现⼀些复杂的业务逻辑和算法。
5、Redis为什么快
- 内存存储:Redis 的数据都是存储在内存中的,相⽐于硬盘存储的数据库,内存存储速度快。
- 单线程模型:Redis 的单线程操作是指,Redis 使⽤⼀个主线程来处理所有的客户端请求和数据操作,不会创建新的线程来处理请求。这种单线程模型的优点是可以避免多线程并发访问共享数据时的竞争和死锁问题,从⽽提⾼了 Redis 的性能和稳定性。此外,由于 Redis 的内存访问速度⾮常快,因此单线程处理请求也能够保证⾜够的性能。
- 非阻塞I/O:Redis 使⽤⾮阻塞 I/O 处理⽹络通信,当⼀个客户端请求到来时,Redis 不会⼀直等待客户端的响应,⽽是会先处理其它的请求,这样就避免了 I/O 阻塞带来的性能问题。
- 精简高效的数据结构:Redis 内置了多种⾼效的数据结构,如哈希表、跳表等,这些数据结构的实现⾮常精简⾼效,减少了 Redis 对内存和 CPU 的占⽤,从⽽提⾼了 Redis 的性能。
- 持久化策略:Redis ⽀持多种持久化策略,如 RDB(快照)和 AOF(追加式⽂件)等,这些策略可以将内存中的数据保存到硬盘中,以保证数据的持久性和安全性。同时,Redis 可以将数据以压缩的⽅式存储在硬盘中,减少了硬盘的占⽤,提高了数据的读写速度。
- redis6.0 后引入多线程,因为 Redis 的瓶颈不在内存,⽽是在⽹络 I/O 模块带来 CPU 的耗时,所以 Redis6.0的多线程是⽤来处理⽹络 I/O 这部分,充分利⽤ CPU 资源,减少⽹络 I/O 阻塞带来的性能损耗。
综上所述,Redis 之所以快,主要得益于其内存存储结构、单线程模型、非阻塞I/O、精简高效的数据结构、持久化策略。
6、Redis如何实现分布式锁?
Redis实现分布式锁的方式有两种:
- 通过Redis提供的setnx进行实现
- 客户端尝试获取锁,可以使⽤ Redis 的 SET 命令结合NX和EX选项来实现。SET 命令⽤于设置⼀个键值对,NX 选项表示只有在键不存在时才设置,EX 选项表示设置⼀个过期时间(单位为秒)
- 持有锁:在持有锁的客户端执⾏临界区代码。在此期间,其他客户端⽆法获取锁
- 释放锁:在临界区代码执⾏完毕后,需要释放锁以供其他客户端使⽤。为了避免误解锁的情况(例如,由于执⾏时间过⻓导致锁过期,然后被其他客户端获取),在释放锁时需要检查锁的值是否与获取锁时设置的值相同。这可以通过 Redis 的 EVAL 命令来实现,使⽤ Lua 脚本进⾏原⼦性操作。
- 通过使用Redisson来实现分布式锁
- 可以调⽤ Redission 提供的 api,即 lock(),unlock()⽅法进行加锁和锁的释放。此外, Redission 还提供了 watchdog,即看门狗,来对加锁的 key 每隔 10 s对该 key进⾏续时,(从而避免锁的过期)
- Redission 的所有指令是通过 lua 脚本进⾏实现的,lua 脚本可以保证所有指令执⾏的原⼦性
7、缓存更新策略
数据库更新
- 先更新数据库
- 直接删除缓存
读缓存
- 从缓存中读取数据,读取到了就直接返回
- 缓存读取不到,就从数据库读取数据返回
- 把数据库读取到的数据放到缓存中
为什么直接删除缓存,而不会更新缓存
- 对服务端资源造成浪费:删除缓存更加直接,如果频繁修改数据库,导致需要频繁更新缓存,如果更新的缓存没有被访问,其实就是资源浪费的一种表现。不如直接删了,下次在读数据的时候,发现缓存再建立该数据的缓存
- 产生数据不一致问题:在并发场景下,更新 cache 产生数据不一致性问题的概率会更大。先更新数据库,再删除缓存在理论上还是可能会出现数据不一致性的问题,不过概率非常小,因为缓存的写入速度是比数据库的写入数据快很多
8、Redis 的持久化机制有哪些?说说各自的优缺点和应用场景?
Reids的持久化机制主要有两种
一种是 RDB 快照,指使⽤快照的形式将内存中的全部数据写⼊到磁盘中,存的是非常紧凑经过压缩的数据⽂件。
⼀种是 AOF ⽇志,指以追加的形式,⼀条⼀条地将操作命令写⼊到 AOF ⽇志中
优缺点:
- 占用内存空间
- RDB存的是压缩过的数据集,占用空间更小
- AOF 存的是每条操作命令,占用空间更大
- 速度方面:
- RDB 存储慢:周期性备份, 每次都是内存中的全部数据,但恢复时更快
- AOF 存储快: 追加的形式⼀条条地写⼊,每次写⼊⼀条指令,但恢复时更慢(需执行全部指令)
- 安全性
- RDB 是周期性备份, 可能会丢失没来得及备份的数据
- 根据策略⽽定,可以记录全部数据的操作过程,更可靠
- 资源消耗方面
- RDB 会造成 IO 压⼒,数据集⽐较⼤时可能会阻塞正常的服务进程
- AOF 每次记录⼀条,消耗资源更低
场景
- 关闭持久化:数据不敏感,且可以从其他地方重新生成
- RDB:数据集较大、备份、恢复数据和迁移数据等场景,可以承受数分钟级别的数据丢失
- AOF:适合用于数据可靠要求高、数据恢复稳健等场景