Redis

Redis

保证AP(可用性,容错性)

数据类型

String
set key value
get key
incr key
decr key
exists key
del key
setnx key value
List

双端链表

rpush key value1 value2...
lpush key value1 value2...
lset key index value
lpop key
rpop key
Hash
HSET map key value //HSET user name "jyh" age 18
HGET map key //获取指定字段的值
HGETALL map //获取所有键值对
HDEL map key1 key2... //删除字段
HLEN map //获取字段数量

应用场景:购物车(用户id为key 商品id为field 数量为value)

Set
SADD key val1 val2 //向集合中添加元素
SMEMBERS key //获取集合中的所有元素
SCARD key //获取集合中元素数量
SISMEMEBER key val1 //判断元素是否在集合中
SINTER key1 key2... //获取集合的交集
SUNION key1 key2... //获取集合并集
SDIFF key1 key2... //获取集合差集
SPOP key count //随机移除count个元素
SRANDMEMBER key count //随机获取集合中的count个元素

应用场景:共同关注(交集),随机点名

Sorted Set

Sorted Set 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列

ZADD set score1 member1 score member2//添加元素
ZCARD set //获取元素数量
ZSCORE set member1 //获取指定元素的score值
ZRANGE set start end //获取start和end之间的元素(score 从低到高)
ZREVRANGE  set start end//获取start和end之间的元素(score 从高到低)
ZREVRANK set member //获取元素排名(score从大到小排序)

应用场景:排行榜

缓存穿透(查不到)

查询一个不存在的数据,存储层查不到却没写入缓存,导致每次都去数据库查,可能导致数据库挂掉

解决办法:

  • value设置为空
  • 布隆过滤器(用于检索一个元素是否在一个集合中)

缓存击穿(量太大)

设置了过期时间的key,缓存在某个时间点过期的时候,恰好这时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把 DB 压垮。

解决办法:

  • 设置热点数据永不过期

  • 加互斥锁

    在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。保证同时刻只有一个线程访问。

缓存雪崩

设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB 瞬时压力过重雪崩。

解决办法:

  • redis高可用(搭建集群)

  • 数据预热

    提前访问使得数据提前加载到redis缓存中;过期时间尽量设置均匀

  • 限流降级

    在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。

持久化方式

RDB

RDB是一个快照文件,它是把redis某时间点的数据写到磁盘上,当redis实例宕机恢复数据的时候,方便从RDB的快照文件中恢复数据,快照持久化是 Redis 默认采用的持久化方式。

效率较高,但可能会丢失数据。

两个命令来生成RDB快照文件:

  • save :同步保存操作,会阻塞Redis主线程
  • bgsave:fork出一个子线程,子线程去创建RDB文件,父进程继续处理请求,不会阻塞Redis主线程
AOF

AOF的含义是追加文件(即写日志),当redis操作写命令的时候,都会存储这个文件中,当redis实例宕机恢复数据的时候,会从这个文件中再次执行一遍命令来恢复数据

需手动开启:

appendonly yes

AOF持久化实现的流程:

  1. 命令追加(append):将所有写命令追加到AOF缓冲区
  2. 文件写入(write):将缓冲区的数据写入AOF文件中(此时还没有同步到磁盘),需调用write函数(系统调用)
  3. 文件同步(fsync):将文件写入到磁盘,需调用fsync函数(系统调用)
  4. 文件重写(rewrite):随着AOF文件越来越大,需要定期对AOF进行重写(bgwriteof)
  5. 重启加载(load):Redis重启后,可以加载AOF进行数据恢复

文件同步策略:

  • appendfsync always 同步刷盘
  • appendfsync everysec 每秒刷盘(常用)
  • appendfsync no 操作系统控制

数据删除策略

惰性删除

对key设置过期时间,我们不去管他,当需要key时,再去检查是否过期,过期就删除,反之返回该key。

  • 优点:对CPU友好,只会在使用该key时才进行过期检查
  • 缺点:对内存不友好,若key已过期,但一直没使用,该key一直在内存中,占用空间
定期删除

每隔一段时间就对一些key进行检查,删除过期的key。

定期清理有两种模式

  • SLOW :定时任务,执行频率默认为 10hz ,每次不超过 25ms
  • FAST :执行频率不固定,两次间隔不超过2ms,每次耗时不超过1ms

优点:

  • 及时释放空间
  • 减少删除操作对CPU的影响

缺点:

  • 难以确定删除操作执行的时长和频率

Redis的过期删除策略:惰性删除+定期删除 ,两种策略配合使用

数据淘汰策略

Redis中的内存不够用时,此时再向Redis中添加新的key,Redis会按照某种规则将内存中的数据删除。

数据淘汰策略有8种:

  • noeviction :不淘汰任何key,内存满时就不允许写入新数据,直接报错,默认就是这种策略
  • volatile-ttl:对设置了TTL(过期时间)的key,比较剩余的TTL值,值越小,越先淘汰
  • allkeys-random:随机淘汰
  • volatile-random:对设置了TTL的随机淘汰
  • allkeys-lru:对全体key,基于LRU算法(最近最少使用)进行淘汰
  • volatile-lru: 对设置了TTL的,基于LRU算法进行淘汰
  • allkeys-lfu:基于LFU算法(最少频率使用)
  • volatile-lfu:对设置了TTL的,基于LFU算法进行淘汰

Redis分布式锁

redis实现分布式锁主要利用命令setnx(set if not exist),用了这个命令之后,只能有一个客户端对某一个key设置值,在没有过期或删除key时,其他客户端不能设置。

# NX 互斥,NX 设置超时时间(不设置可能会导致死锁)
SET lock value NX EX 10
#释放锁
DEL key
Redisson

自动延时机制:启动watch dog,后台线程每隔10秒检查一下客户端1还持有锁key,操作共享资源的线程还未执行完成的话,会不断的延长锁key的生存时间

加锁、设置过期时间等操作都是基于lua脚本完成,可以保证原子性。

redisson实现的分布式锁的特点:

  • 可重入(同一个线程可以多次获取锁)

    Redis使用hash结构来存储线程信息和重入的次数

  • 不能解决主从一致性

    但是可以使用RedLock:在多个Redis实例上创建锁(n/2+1),但是性能低,推荐使用zookeeper实现的分布式锁保证强一致性

Redis集群方案:

  • 主从复制
  • 哨兵模式
  • 分片集群

主从复制

提高Redis的并发能力,需要搭建主从集群,实现读写分离。

全量同步
  1. 从节点请求主节点同步数据,其中从节点会携带自己的replication id和offset偏移量
  2. 主节点判断是否是第一次请求,判断依据:主节点和从节点的replication id是否相同,若不相同,则是第一次同步,主节点会把自己的replication id和offset发送给从节点,让从节点与主节点的信息保持一致
  3. 在同时,主节点会执行bgsave,生成rdb文件后,发送给从节点去执行,从节点把自己的数据清空,然后执行主节点发送过来的rdb文件,这样就保持了一致
增量同步

当从节点服务重启之后,数据就不一致了,这时从节点会请求主节点同步数据,主节点判断这次请求不是第一次请求了,获取从节点的offset值,主节点从命令日志中获取offset值之后的数据,发送给从节点进行数据同步。

哨兵模式

哨兵(Sentinel)作用:

  • 监控:不断检查master和slave是否正常工作(心跳检测)

  • 自动故障恢复:若master发生故障,会将一个slave推选为master

  • 通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障时,会将最新信息推送给Redis客户端

哨兵选主规则:

  • 首先判断主与从节点断开时间长短,如超过指定值就排该从节点
  • 然后判断从节点的 slave-priority 值,越小优先级越高
  • 如果 slave-prority 一样,则判断 slave 节点的 offset 值,越大优先级越高
  • 最后是判断 slave 节点的运行 id 大小,越小优先级越高。
集群脑裂

由于master节点和slave节点和sentinel处于不同的网络分区,使得sentinel未能心跳感知到master,所以选举将一个slave变为了master,这样就存在了两个master。这样会导致客户端还在旧的master那写入数据,新的master无法同步数据。网络恢复后,旧的master降为slave,这时再从新的master处同步数据,会导致数据丢失。

解决方法:

min-replicas-to-write 1 表示最少的slave节点数为1
min-replicas-max-lag 5 表示数据复制和同步的延迟不能超过5s
  • 设置最少的slave节点个数
  • 设置主从复制和同步的延迟时间,达不到要求就拒绝

分片集群

特点:

  • 集群中有多个master,每个master保存不同的数据
  • 每个master可以有多个slave
  • master通过ping监测彼此健康状态
  • 客户端可以访问集群任意节点,最终都会被转发到正确节点

Redis集群引入了哈希槽,有16384个哈希槽,集群中每个主节点绑定了一定范围的哈希槽,key通过CRC16校验后对16384取模来决定放哪个槽,通过槽找到对应节点进行存储。

Redis为什么这么快

  • 基于内存,执行速度非常快
  • 采用单线程,避免了不必要的上下文切换,多线程还要考虑线程安全问题
  • 采用I/O多路复用模型,非阻塞IO,例如bgsave,bgrewriteof都是在后台执行操作,不会影响主线程的正常使用,不会产生阻塞

I/O多路复用模型

I/O多路复用值利用单个线程来同时监听多个Socket,并在某个Socket可用时得到通知,从而避免无效等待,充分利用CPU资源。目前的I/O多路复用都是采用的epoll模式实现,它会在通知用户进程Socket就绪的同时,把已就绪的Socket写入用户空间,不需要挨个遍历Socket来判断是否就绪,提升了性能。

其中Redis的网络模型就是使用I/O多路复用结合事件的处理器来应对多个Socket请求,比如,提供了连接应答处理器、命令回复处理器,命令请求处理器;

在Redis6.0之后,为了提升更好的性能,在命令回复处理器使用了多线程来处理回复事件,在命令请求处理器中,将命令的转换使用了多线程,增加命令转换速度,在命令执行的时候,依然是单线程。

你可能感兴趣的:(java开发面试题,Redis,redis)