1 Redis有哪些过期键删除策略?
1)定时删除
创建一个定时器,到时间立即执行删除操作
优点:对内存是友好的,保证过期键会尽可能快的被删除,并释放过期键所占用的内存
缺点:对cpu时间是最不友好的,过期键比较多的情况下,删除键会占用想当一部分的cpu时间,无疑会对服务器的响应时间和吞吐量造成影响
2)惰性删除
键过期不管,每次获取键时检查是否过期,过期就删除(对cpu友好,但是只有在使用的时候才可能删除,对内存不友好)
优点:对cpu时间来说是最友好的,可以保证过期键的删除操作只会在非做不可的情况下进行,并且删除目标仅限于当前处理的键。
缺点:对内存最不友好,如果一个键已经过期,而这个键又仍在然保留在数据库中,只要过期键不被删除,占用的内存就不会被释放。
3)定期删除
每隔一段时间执行一次删除过期键的操作,通过限制删除操作执行时长和频率来减少删除操作对cpu时间的影响,是对定时删除策略和惰性删除策略的整合和折中。
2 Redis缓存是如何回收?
当Redis占用内存超出最大限制 (maxmemory) 时,可采用如下策略 (maxmemory-policy) ,让Redis淘汰一些数据,以腾出空间继续提供读写服务 :
- noeviction: 对可能导致增大内存的命令返回错误 (大多数写命令,DEL除外) ;
- volatile-ttl: 在设置了过期时间的key中,选择剩余寿命 (TTL) 最短的key,将其淘汰;
- volatile-lru: 在设置了过期时间的key中,选择最少使用的key (RU) ,将其淘汰;
- volatile-random: 在设置了过期时间的key中,随机选择一些key,将其淘汰;
- allkeys-1Lru: 在所有的key中,选择最少使用的key (LRU) ,将其淘汰;
- allkeys-random: 在所有的key中,随机选择一些key,将其淘汰;
3 Redis的集群方案有哪些?
Redis支持三种集群方案:①主从复制模式 ②Sentinel(哨兵)模式 ③Cluster模式
1)主从复制模式
主从复制模式中包含一个主数据库实例(master)与一个或多个从数据库实例(slave)
工作机制:
- slave启动后,向master发送SYNC命令,master接收到SYNC命令后通过bgsave保存快照(RDB持久化),并使用缓冲区记录保存快照这段时间内执行的写命令
- master将保存的快照文件发送给slave,并继续记录执行的写命令
- slave接收到快照文件后,加载快照文件,载入数据
- master快照发送完后开始向slave发送缓冲区的写命令,slave接收命令并执行,完成复制初始化
- 此后master每次执行一个写命令都会同步发送给slave,保持master与slave之间数据的一致性
优点;
- master能自动将数据同步到slave,可以进行读写分离,分担master的读压力
- master、slave之间的同步是以非阻塞的方式进行的,同步期间,客户端仍然可以提交查询或更新请求
缺点:
- 不具备自动容错与恢复功能,master或slave的宕机都可能导致客户端请求失败,需要等待机器重启或手动切换客户端IP才能恢复
- master宕机,如果宕机前数据没有同步完,则切换IP后会存在数据不一致的问题
- 难以支持在线扩容,Redis的容量受限于单机配置
2)Sentinel(哨兵)模式
哨兵模式基于主从复制模式,只是引入了哨兵来监控与自动处理故障。
哨兵顾名思义,就是来为Redis集群站哨的,一旦发现问题能做出相应的应对处理。其功能包括
- 监控master、slave是否正常运行
- 当master出现故障时,能自动将一个slave转换为master
- 多个哨兵可以监控同一个Redis,哨兵之间也会自动监控
优点:
- 哨兵模式基于主从复制模式,所以主从复制模式有的优点,哨兵模式也有
- 哨兵模式下,master挂掉可以自动进行切换,系统可用性更高
缺点:
- 同样也继承了主从模式难以在线扩容的缺点,Redis的容量受限于单机配置
- 需要额外的资源来启动sentinel进程,实现相对复杂一点,同时slave节点作为备份节点不提供服务
3)Cluster集群模式
Cluster模式实现了Redis的分布式存储,即每台节点存储不同的内容,来解决在线扩容的问题
Cluster采用无中心结构,有如下特点:
- 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽
- 节点的fail是通过集群中超过半数的节点检测失效时才生效
- 客户端与redis节点直连,不需要中间代理层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
优点:
-
无中心架构,数据按照slot分布在多个节点。
-
集群中的每个节点都是平等的关系,每个节点都保存各自的数据和整个集群的状态。每个节点都和其他所有节点连接,而且这些连接保持活跃,这样就保证了我们只需要连接集群中的任意一个节点,就可以获取到其他节点的数据。
- 可线性扩展到1000多个节点,节点可动态添加或删除
-
能够实现自动故障转移,节点之间通过gossip协议交换状态信息,用投票机制完成slave到master的角色转换
缺点:
- 客户端实现复杂,驱动要求实现Smart Client,缓存slots mapping信息并及时更新,提高了开发难度。目前仅JedisCluster相对成熟,异常处理还不完善,比如常见的“max redirect exception”
- 节点会因为某些原因发生阻塞(阻塞时间大于 cluster-node-timeout)被判断下线,这种failover是没有必要的
- 数据通过异步复制,不保证数据的强一致性。
- slave充当“冷备”,不能缓解读压力。
- 批量操作限制,目前只支持具有相同slot值的key执行批量操作,对mset、mget、sunion等操作支持不友好
- key事务操作支持有线,只支持多key在同一节点的事务操作,多key分布不同节点时无法使用事务功能
- 不支持多数据库空间,单机redis可以支持16个db,集群模式下只能使用一个,即db0
4 Redis事务是如何实现的?
Redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行的过程中,会按照顺序执行!
一次性、顺序性、排他性!执行一系列的命令。
Redis事务没有隔离级别的概念!
所有的命令在事务中,并没有直接被执行!只有发起执行命令EXEC
的时候才会执行!
Redis的事务执行:
- 开启事务(MULTI)
- 命令入队(…)
- 执行事务(EXEC)/放弃事务(DISCARD)
5 Zookepeer中的watch监听机制
zookeeper的订阅发布也就是watch机制,是一个轻量级的设计。因为它采用了一种推拉结合的模式。一旦服务端感知主题变了,那么只会发送一个事件类型和节点信息给关注的客户端,而不会包括具体的变更内容,所以事件本身是轻量级的,这就是所谓的“推”部分。然后,收到变更通知的客户端需要自己去拉变更的数据,这就是“拉”部分。watche机制分为添加数据和监听节点。
6 Zookeeper的数据模型和节点类型有哪些?
1)数据模型
ZooKeeper的视图结构和标准的Unix文件系统类似,其中每个节点称为“数据节点”或ZNode
,每个znode可以存储数据,还可以挂载子节点,因此可以称之为“树”。
在Zookeeper中,znode是一个跟Unix文件系统路径相似的节点,可以往这个节点存储或获取数据 ; 且通过客户端可对znode进行增删改查的操作,还可以注册watcher监控znode的变化。 其数据模型可用下图进行表示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lP4rqJtd-1651072091309)(/Users/lu/Documents/%E8%B5%84%E6%96%99/java%E5%AD%A6%E4%B9%A0/%E9%9D%A2%E8%AF%95/src=http%253A%252F%252Fpic.qcsdn.com%252Fimg%252F20190414%252F069882bba2ff0b7cacfd9860aefa5991.png&refer=http%253A%252F%252Fpic.qcsdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto.png)]
2)节点类型
①持久(persistent)节点:
(1) session断开后,数据不会丢失
(2)可以创建子节点
②临时(ephemeral)节点
(1)session断开后,数据会丢失
(2)不可以创建子节点
③持久顺序(PERSISTENT_SEQUENTIAL)节点
(1)、(2)同持久节点
(3)创建顺序节点时会默认设置顺序标识,即znode名称后会附加一个值 — 顺序号 ,顺序号是一个单调递增的计数器,由父节点维护
④临时顺序(EPHEMERAL_SEQUENTIAL)节点
Zookeeper实现分布式锁的基础。
(1)、(2)同临时节点,(3)同持久顺序节点
7 缓存穿透、缓存击穿、缓存雪崩在实际中如何解决
1)缓存穿透
当用户访问的数据, **既不在缓存中,也不在数据库中,**导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据,没办法构建缓存数据,来服务后续的请求。那么当有大量这样的请求到来时,数据库的压力骤增,这就是 缓存穿透的问题。
应对缓存穿透的方案,常见的方案有三种。
- 第一种方案,非法请求的限制;
- 第二种方案,缓存空值或者默认值;
- 第三种方案,使用布隆过滤器快速判断数据是否存在,避免通过查询数据库来判断数据是否存在;
2)缓存击穿
如果缓存中的 某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮,这就是 缓存击穿的问题。
应对缓存击穿可以采取两种方案:
- 互斥锁方案,保证同一时间只有一个业务线程更新缓存,未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。
- 不给热点数据设置过期时间,由后台异步更新缓存,或者在热点数据准备要过期前,提前通知后台线程更新缓存以及重新设置过期时间。
3)缓存雪崩
当 **大量缓存数据在同一时间过期(失效)或者 Redis 故障宕机时,**如果此时有大量的用户请求,都无法在 Redis 中处理,于是全部请求都直接访问数据库,从而导致数据库的压力骤增,严重的会造成数据库宕机,从而形成一系列连锁反应,造成整个系统崩溃,这就是 缓存雪崩的问题。
不同的诱因,应对的策略也会不同。
针对大量数据同时过期,应对方法有下面这几种:
- 均匀设置过期时间;
- 互斥锁;
- 双 key 策略;
- 后台更新缓存;
针对Redis 故障宕机,应对方法有下面这几种:
- 服务熔断或请求限流机制;
- 构建 Redis 缓存高可靠集群;
8 分布式锁在项目中有哪些应用场景?
分布式锁为了解决分布式场景下全局加锁的问题。在单体项目中可以使用synchronize完成对于不同线程之间的资源争抢问题。但是在分布式场景下,synchronize只能对其中一个项目进行资源控制,进程之间的资源增强仍然无法解距。换言之,可以将分布式锁理解为对于整个分布式系统的synchronize。通常使用独立与线程之外的工具控制资源,如redis及框架redisson。
所以使用分布式锁的场景一般需要满足以下情况:
- 系统是一个分布式系统,集群系统,java的锁已经锁不住。
- 操作共享资源,比如库里唯一的用户数据。
- 同步访问,即多个进程同时操作共享资源。
9 分布式锁有哪些实现方案
1)基于数据库(唯一索引)
基于数据库的分布式锁可以有两种实现方式:
①基于数据库表
直接创建一张锁表,然后通过操作该表中的数据来实现了。当我们要锁住某个方法或资源时,我们就在该表中增加一条记录,想要释放锁的时候就删除这条记录。
②基于数据库排他锁
在SQL语句后面增加for update**,**数据库会在执行过程中给数据库表增加排他锁
2)基于缓存(Redis,memcached,tair)
基于Redis的分布式锁也有两种实现方式:
①基于setnx、expire及del三个命令来实现
通过redis setnx(set if not exist)方式,当缓存里key不存在时,才会去set,否则直接返回false。如果返回true则获取到锁,否则获取锁失败,为了防止死锁,我们再用expire命令对这个key设置一个超时时间来避免。非原子实现。执行完业务操作之后,删除该锁。
②基于Redisson来实现
3)基于Zookeeper
使用zookeeper的临时有序节点,每个线程获取锁就在zookeeper创建一个临时有序的节点,比如在/lock目录下。
创建节点成功后,获取/lock目录下的所有临时节点,再判断当前线程创建的节点是否是所有的节点的序号最小的节点;如果当前线程创建的节点是所有节点序号最小的节点,则认为获取锁成功;如果当前线程创建的节点不是所有节点序号最小的节点,则对节点序号的前一个节点添加一个事件监听。删除节点的时候就是解锁了。
10 Redis做分布式锁死锁时有哪些情况,如何解决?
- 加锁,没有释放锁的情况:需要添加释放锁的操作,比如delete key。
- 加锁后,程序还没执行释放锁,程序挂掉,需要使用key过期机制。