Java后端面经(八股——Redis)

Redis

定义:

Redis是一个Key-Value基于内存的数据库

适合存储数据种类:

(1)热点数据、需要频繁访问(热门商品信息、实时排行榜):访问内存的速度远远快于

磁盘。

(2)临时数据、易失数据(验证码、限流计数器):redis支持自动过期。

(3)需要原子操作的数据(执行Lua脚本过程中不能被其他命令打断):redis为单线程模型。

启动:

应当使用cmd命令行启动并指定其配置文件,否则会使用其内置配置。

数据持久化:

由于基于内存所以当Redis服务由于故障异常中断时,redis中的数据会丢失,但redis提供两种方式持久化,RDB和AOF

RDB:

会将当前内存中存储快照转化为磁盘文件,其在配置文件中配置,如save 60 1表示当60秒进行一次数据修改操作时会存储一次快照,即维护一个计时器(其每次衰减时间由配置中的hz控制默认为0.1s),当计时器结束时,如果这段时间中发生了1次修改则会生成一个RDB文件,其中使用shutdown默认参数为shundown save即立刻生成一次快照

AOF:

记录的是操作记录而非数据,其配置有always、everysec、no,分别对应每次执行操作都写入磁盘(主进程IO),每秒将该秒内操作写入磁盘(后台IO),由操作系统决定何时写入磁盘(后台IO)。

需要注意RDB的操作为Redis的子进程执行,Redis运行时会fork一个子进程,完全复制主进程的内存数据,当需要存储快照时由子进程进行IO操作;AOF则由主进程在执行完命令后再将命令写入缓冲池,根据配置决定主或后台进程进行IO;

当同时启用RDB和AOF时优先使用AOF恢复数据。

其中RDB对性能影响更小,但恢复时更容易丢失文件,AOF则由于主线程需要写命令到缓冲池开销更大,但设置always时能够保证每条命令都被记录,恢复时不会丢失数据,但aof文件更大

支持的存储类型:

del key 删除键,所有类型

String :

set key value

get key

HashMap:

hset key field value 向一个名称为Key的Map插入Entry<field,value>

hget key field 获取Key名称的Map的键为field的value

hdel key field 删除Key名称的Map的field对应的键值对

List:

lpop key 弹出并获取名称为key的list的左侧的值

lpush key value 向名称为key的list的左侧插入value

rpop key

rpush key value

lrange key start stop 获取名称为key的list指定范围的value

Set:

SADD key member:向集合添加一个或多个成员。

SREM key member:移除集合中的一个或多个成员。

SMEMBERS key:返回集合中的所有成员。

SISMEMBER key member:判断 member 是否是集合的成员。

SUNION key1 key2 ...:返回多个集合的并集。

Sorted Set, ZSet

ZADD key score member:向有序集合添加成员及其分数。

ZRANGE key start stop [WITHSCORES]:按排名返回指定区间的成员。

ZREM key member:移除有序集合中的一个或多个成员。

ZSCORE key member:返回成员的分数。

ZCOUNT key min max:计算指定分数区间内成员的数量

并发问题:

redis是单线程模型,即所有操作均由主线程按序操作,无需加锁,不存在多线程竞争和并发冲突的问题。

事务:

尽管单个增删改查不会出现并发问题,但如果设计涉及多个操作必须连续执行,不被其他客户端命令插入,需要事务。

事务的声明multi,声明后输入需要操作的指令,输入时不会影响其他客户端执行命令,命令编写完毕后,输入exec开始执行事务,在执行事务时会阻止其他客户端执行命令。discard撤掉事务的创建

watch key可以实现对key的乐观锁,在watch key命令执行瞬间记录其版本信息,当事务执行exec时发现当前watch的key值发送变化则不会再执行事务,返回nil。watch 主要用于保证在获取数据后,通过命令对数据进行修改输入的这段时间,key值不会变化。

redis本身不提供事务回滚功能,当执行事务出错时不会撤销已经执行过的命令。

Lua脚本具有原子性,可以将其看作一个事务,其中间出错时同样不会回滚。

分布式锁:

单节点:

SET key random_value NX PX timeout 其中NX表示当键不存在时才能操作成功,否则返回nil,PX则设置锁超时的时间,防止死锁,当服务操作时长可能超过设置时长时,应当使用看门狗机制,后台线程定期续期,看门狗线程应当是守护线程,当业务线程挂掉时会自动停止。random_value则确保不会删除服务的锁。其中释放锁的时候需要使用Lua脚本将验证random_value和删除锁作为一整个原子操作防止验证value后锁自动过期然后其他服务加锁后又被自己删除。

多节点:

使用相同的key和value,向所有节点发送加锁请求,如果超过一半的节点加锁成功且用时小于过期时长则加锁成功,否则向所有节点发送释放锁的请求

以及如果一个线程已经拿到锁,另外一个线程试图set时会返回nil而非阻塞等待,因此需要手动实现重试机制。

淘汰策略(可配置):

淘汰发生在当redis占用内存满时,有新的数据添加

  1. 默认报错
  2. 所有键最近最少lru
  3. 设置超时键最近最少
  4. 全部键随机
  5. 设置超时键随机
  6. 剩余超时时间最短
  7. 全部键最不常使用lfu
  8. 超时键最不常使用

lru原理:最近被访问的键未来更可能访问,淘汰最久没访问的键

lfu:淘汰访问频次更少的键

过期键删除策略(可配置):

  1. 惰性删除:过期时不直接删除,当再次使用这个键时检查是否过期,过期则删除掉
  2. 定期删除:每隔固定时间随机抽取部分键检查,过期则删除

Pipline

传统redis需要发一条指令->redis执行并返回->再下一条命令

使用pipline能够将多条命令打包一起发送,redis按顺序执行指令并返回所有的结果

增加了网络吞吐量,减少了等待时间

注意这些命令需要没有因果依赖

数据库与缓存数据一致性:

缓存和数据库主流模式

读:先读缓存,未命中则需要再去读数据库,并更新缓存

写:修改数据库中数据,并直接使缓存中数据失效(使用失效而非更新原因在于一是可能后续不再访问该数据,二是两次并发更新可能出现缓存错误覆盖问题)

可能出现缓存击穿的问题,以及更新数据库和删除缓存间的时间窗口可能导致数据不一致。

(当慢读快写时,当一个数据在缓存中不存在,A去读并准备将旧数据更新到缓存,B去写并执行删除缓存操作尽管目前缓存中并没有对应数据,B删除完毕后A读完并将读取的数据放入缓存即导致缓存中和数据库中数据不一致,解决策略延迟双删,即B先删除一次等待一段时间后再次删除缓存)

缓存穿透/击穿/雪崩

缓存穿透:

频繁请求数据库不存在的数据导致每次都需要访问数据库

  1. 参数校验

接口参数校验,异常用户/IP限流

2.缓存空值

即使查询不到对应数据也缓存一个空对象,设置一个较短的过期时间(当数据库更新后需要等待一段时间才能同步到用户)

3.布隆过滤器

每次向数据库插入数据时先更新过滤器,访问数据库时先访问过滤器判断是否有数据

过滤器原理,对于请求数据的多个字段进行hash,并维护一个bitmap将map中对应hash的值设置1,可能将不存在的数据判为存在,即部分字段相同,但不会将不存在的判为存在。不能同步数据库的删除,一位为1可能是多条数据设置过。如果自己手动实现需要注意同步过滤器的记录到磁盘,或者不同步直接冷启动从数据库重建,可以使用redis提供的实现。

缓存击穿:

(强调一个热点key过期)

当热点key过期时大量的请求同时访问数据库

1.逻辑过期

设置数据永不过期,数据内部维护一个过期字段,线程拿到数据后判断是否过期,过期则开启另外一个线程,异步地拿取分布式锁并更新缓存,其他线程直接返回旧数据(数据一致性差,性能好)

2.分布式锁更新

当发现数据不存在时,线程去拿分布式锁,然后去更新缓存再释放锁,其他线程阻塞等待,锁释放后拿到锁的线程判断是否缓存中有数据(数据一致性强,性能差)

缓存雪崩:

(强调多个热点key过期)

大量缓存数据过期/不可用,或redis宕机导致大量请求访问数据库

  1. 过期添加随机值防止同时过期
  2. 对于不常变化的数据设置永不过期,更新数据时异步更新缓存
  3. 业务高峰前对缓存做缓存预热
  4. 对于不可控的因素,做主从,集群,异地容灾
相关推荐
郝学胜-神的一滴2 小时前
谨慎地迭代函数所收到的参数 (Effective Python 第31条)
开发语言·python·程序人生·软件工程
哈哈很哈哈3 小时前
Flink SlotSharingGroup 机制详解
java·大数据·flink
真的想不出名儿3 小时前
springboot - 邮箱验证码登录
java·springboot·邮箱验证
the beard3 小时前
JVM垃圾回收器深度解析:从Serial到G1,探索垃圾回收的艺术
java·jvm
大虾别跑3 小时前
vc无法启动
java·开发语言
郭老二3 小时前
【JAVA】从入门到放弃-01-HelloWorld
java·开发语言
卷Java3 小时前
CSS模板语法修复总结
java·前端·css·数据库·微信小程序·uni-app·springboot
北城以北88883 小时前
JavaScript--基础ES(一)
开发语言·javascript·intellij-idea
say_fall4 小时前
C语言底层学习(2.指针与数组的关系与应用)(超详细)
c语言·开发语言·学习