Redis学习
文章目录
Redis学习 1. Redlock算法(MultiLock)和底层源码分析 2. Redis缓存过期淘汰策略
1. Redlock算法(MultiLock)和底层源码分析
自研一个分布式锁的要点:Lock规范 、独占性、高可用、防死锁、不乱抢、可重入
按照JUC的Lock接口规范编写 tryLock()加锁关键逻辑:加锁定期 (当没有hash类型的key或者存在当前线程分布式锁时使用lua脚本实现原子操作 进行HINCRBY加锁并设置过期时间 )、自旋延迟重试 (当加锁失败时必须延迟一段时间再重试,且不要递归 ),一定先判断是否有key时再加锁 unlock()解锁关键逻辑:当不存在锁或者不是当前锁时直接报错 ,否则使用HINCRBY -1 解锁,并当=0时删除当前锁 ,即删除hash类型的key(uuid只是标识不同锁的field,其值是加当前锁的个数 ),必须解自己加的锁 ,且必须加几次锁就解几次锁 工厂设计模式使用工厂设计模式使得同一个线程中申请的分布式锁唯一且不同微服务的不同 (使用uuid区别) 自动续期 :不自动续期仍会出现超卖现象 ,加了自动续期可能会一直死锁,但当执行结束解锁后就不会自动续期,自动续期时必须先判断锁是否存在,不存在时则直接退出 SETNX无法实现可重入性 ,因为无法对锁进行计数,故使用 HSET lock id count 来加锁并实现可重入性,实际上只是记录了一个当前锁的个数,仍要判断是否存在key时再加锁 ,当不存在锁或者存在锁且是当前锁时则可以加锁自研的分布式锁,其所有的锁都存在到Redis中,但由于Redis主从复制以及集群的异步性 ,就会出现不安全 ,如果某一个加锁后更新master但还未同步时,master宕机,此时另一个加锁,则新的master仍会同意,导致同时两个同时访问Redis ,造成不安全 Redlock红锁算法 :Redis提供的分布式锁实现算法,实现了更安全的DLM
Redis提供了一个规范的分布式锁实现算法 -Redlock红锁算法,实现了更安全的DLM(分布式锁管理器) 自研的分布式锁存在Redis缓存中,是不安全的 ,如果master宕机,就算有从机,但因为主从复制是异步的(Redis集群是AP的,不保证一致性),所以可能导致Redis分布式锁更新错误 ,一个获得锁后还未同步就宕机了,就会导致另一个也可以获得锁 ![[Pasted image 20241101202908.png]] Redlock算法设计理念
Redlock算法是Redis提供的分布式锁实现算法,实现了更安全的DLM(分布式锁管理器) ,为了解决自研分布式锁存放于master中由于主从复制的异步性造成的不安全 Redlock基于多个master实例的分布式锁 ,锁变量由多个master实例维护 ,类似于集群,但不是集群 ,因为各个master节点完全独立 ,不存在主从复制 ,各个master完全独立,不存在异步设计理念:直接用多个相互独立的master存放锁 ,当加锁时,对每个master上的分布式锁均获得锁 (且要设置超时时间 ,超时时间要小于失效时间,此时当一个master实例宕机时就可以快速去写一个master加锁 ),且必须大多数以上加锁成功才算成功 ,当加锁失败时,要对所有的master上的分布式锁进行解锁(防止某些master加上锁但客户端没有收到) ,就算没有获得锁也要进行解锁 ,以此来解决由于主从复制异步性导致的不安全 ,只有当大多数master实例上均获得了锁,并且获得锁的时间小于有效时间时才认为加锁成功 ![[Pasted image 20241101203846.png]] 解决方案:容错公式 N = 2X + 1 :其中X为容错机器数即最大可出错的机器数,N为最终部署机器数,即要确保不出错的机器数始终大于出错的机器数 ;如果希望X台机器出错后还能用,则**必须部署 N = 2X + 1 台机器 Redlock落地实现:Redisson
官网
Redisson是Redis提供的基于Redlock算法实现的更安全的DLM SpringBoot引入Redisson实现分布式锁
单机版
使用redisson分布式锁也会出现错误,因为有时候删除的不是自己的锁,故先判断是不是自己的锁再删除 ,使用redisson先判断是不是自己的锁再删除,会自动封装为原子操作 Redisson源码解析
必须实现Lock规范 ,有加锁、解锁、可重入 、续期功能 和自己实现的可重入分布式锁的源码类似,均实现了Lock接口规范,并重写相应的函数,加锁时先判断是否存在锁,不存在则直接加锁,如果存在则判断是不是自己的锁,如果是则计数+1实现可重入,使用lua脚本来实现原子性 ,加锁成功后开启一个看门狗来自动续期,默认超时时间是30s,当解锁时仍要先判断是不是自己的锁,如果不是则报错,只有是自己的锁时才会解锁,且在使用redisson分布式锁时,解锁之前也要使用redisson判断是不是存在锁并去是自己的锁,然后再解锁,当是自己的锁时,计数-1,然后判断计数是否为0,如果为0则要删除锁 ,不为0则重置过期时间后直接结束 看门狗是定时检查redis分布式锁是否还存在,并不断延长锁的过期时间 在redisson中当获得锁后会自动设置一个监控线程看门狗 ,会定期对锁进行监控,如果还未释放且快过期了则对锁续期,看门狗默认30s监控一次 守护进程来续命:定期检查锁是否存在,存在则重新设置过期时间,以防止过期了还未执行完业务,使用看门狗 ,当获得锁后会给锁加一个看门狗,会另起一个定时任务,不断查询锁是否存在并进行续期 加锁源码使用lua脚本实现原子性和自动续期看门狗 ![[Pasted image 20241102123637.png]]![[Pasted image 20241102123926.png]]![[Pasted image 20241102124112.png]]解锁源码 ,先判断是不是自己的锁,如果不是则返回nil,是自己的锁时先解一次锁,计数-1 ,然后判断计数是否为0,如果为0说明没有重入了,故要删除整个锁 ,如果不为0,则重置过期时间后返回但对于redisson可能高并发出现错误,故在使用时,解锁之前使用redisson先判断一下是不是存在锁且是自己的锁,然后再使用redisson解锁 小总结
redisson基于Redlock算法,防止出现单个redis故障造成的整个服务停止运行以及主从复制异步性造成互斥失效的问题 ![[Pasted image 20241102125357.png]] Redisdon多机实现
介绍
redisson基于RedLock算法来实现更安全的DLM,其基于多个相互独立的master实例来存放分布式锁 ,只有当大多数实例均加锁成功时,才算加锁成功,一次来避免单机master因为主从复制的异步性而造成错误,且加锁失败时必须所有的master均解锁,为了保证正确性,必须使得正确的master个数大于错误的master个数 去官网 实现
RedLock(已弃用)
通过redis通过的redisson分布式锁来实现多机,仍是先创建多个单机实例,然后使用redis通过的方法将多个RLock对象关联为一个RedLock ,此时就实现了基于多个master的分布式锁 将多个RLock实例关联为一个RedLock,此时使用RedLock对象进行加锁,是基于多个master实例的分布式锁,且加锁时可以指定过期时间,而且可以设置指定的重试时间来进行加锁 MultiLock多重锁
和RedLock一样,仍然是先将多个RLock(可能不是一个master下的RLock)关联为一个MultiLock多重锁 ,此时加锁时必须大多数锁都申请成功才可以加锁 ,且可以指定锁的时间,以及限制多少时间内去申请锁 ,解锁时对所有的master实例上的锁均要解锁 也就是创建多个相互独立的master实例 ,然后在Java客户端使用redisson先创建多个单机实例的Rlock分布式锁 ,然后根据这多个RLock创建一个MultiLock分布式锁 ,其基于多个master实例以防止单个节点故障以及主从复制异步性造成的问题 ,然后使用MultiLock就可以实现更加安全的DLM(分布式锁管理器) ,为了保证容错性更高,要遵循容错公式: N = 2X + 1 ,即实际部署的master实例个数 = 容忍最大故障树* 2 +1 ,保证正确的个数始终大于出错的个数 必须要在多个不同的master实例上获得RLock ,此时锁的名字可以相同 ,因为master实例不同,不可以同一个实例上获得三个不同的锁 ,因为此时仍是同一个master实例上,就无法解决单点故障问题 ,必须在多个master上获得RLock,再关联为一个MultiLock当创建好锁后,会自动生成一个监控锁的看门狗 ,超时时间默认是30s,其会定时1/3秒去查看一下锁的状态 ,如果未释放则对锁自动续期,然后继续监控 当使用MultiLock时,只有多个master实例均加锁成功后才会加锁成功,如果加锁失败,则所有的master实例都要进行解锁 小总结
RedLock算法就是为了实现更加安全的DLM,其基于多个相互独立的master实例来获得分布式锁,可以保证单点故障后仍正常时间加锁解锁 具体实现是使用redis提供的Redission来创建MultiLock锁,其根据多个独立master实例上创建的RLock来创建 Redis分布式锁用HSET实现,SETNX无法实现可重入性
2. Redis缓存过期淘汰策略
面试题
Redis的缓存过期淘汰策略 LRU是最近最久未使用 ,LFU最近最少使用 Redis内存满了怎么办
默认内存与配置
在redis.conf配置文件中对通过MAXMEMORY配置项设置最大内存,单位是BYTE字节 ,当未配置时默认最大内存为0(通过config get maxmemory得到配置项的值) ,不设置默认为0,表示不限制最大内存 ,在redis.conf配置文件中对maxmemory进行配置设置redis的最大内存,默认为0表示不进行限制,以BYTE为单位,推荐设置为3/4,且默认的缓存淘汰策略是NoEviction,即不清楚缓存,故当达到maxmemory时会报错OOM 一般最大内存推荐设置为物理最大内存的四分之三 ,当不配置默认为0表示不限制内存 ,默认为0表示不限制内存 可以通过修改redsi.conf配置文件来修改maxmemory配置项,此时会永久生效(要重启服务端才会生效) ,也可以通过命令config set maxmemory xx来设置 (可以多个参数),但只作用于本次客户端重启失效,单位是BYTE字节 通过 info memory 命令来查看redis内存占用情况 如果内存超过了设置的最大内存maxmemory时会报OOM错误 ,不允许超过设置的最大内存 ,因为默认的缓存淘汰策略是Noeviction不进行清除 ,当达到最大时就不允许添加,会报错 Redis中的数据是如何删除 Redis的键过期后不一定要立即删除,会占用大量cpu时间,可以使用惰性删除
立即删除(用时间CPU换空间内存)
当键过期时立即删除 ,保证了内存数据的新鲜度,使得内存立即释放,但对cpu非常不友好,因为要时时刻刻进行删除 ,会对CPU造成额外的压力 惰性删除(用空间内存换时间CPU)
当键过期时不做删除,当**下次访问时如果访问时过期了再删除键,如果没过期则返回数据 但可能会造成内存泄漏,因为如果一个键过期了但一直没有被访问 ,则会一直占用内存空间而不被删除 ,造成内存泄漏 在配置文件中对lazyfree-lazy-eviction(清除)设置为yes来开启惰性删除 惰性删除要在配置文件中进行配置开启 lazyfree配置项 定期删除:折中
定期抽查部分键判断是否过期 ,只对过期的键进行删除,不会判断所有的键是否过期,而是定期抽查一部分键 ,只删除定期随机抽查到且过期的键,仍会出现有的键过期后一直未被删除关键是确定删除操作执行的时长和频率 ,时长过长频率过高会退化为立即删除,时长过短频率过低会退化为惰性删除每隔一段时间随机抽查部分键,然后根据过期键的占比来执行过期键删除操作 当键过期时不会立即删除,也不会等下一次访问过期后再删除,而是隔一段时间后统一进行删除 ,不是全部检查,而是随机抽取key,看过期键的占比,如果过高则执行删除过期键操作 周期性的随机抽查存储空间,根据过期键占比来控制删除频度 漏洞:仍会内存泄漏 1. 在定期删除(定期随机抽查部分键,只删除过期的键)时,可能有的键从来没有被抽查过,导致永远不会被删除 2. 在惰性删除(过期后不会删除,而是下次访问时过期则删除)时,用空间换时间,但可能有的过期键永远不会被再次访问,则此时就不会被删除,造成内存泄漏 3. 立即删除会大量占用CPU时间 Redis缓存淘汰策略
配置文件
Redis.conf配置文件中的MEMORY MANAGEMENT配置项 当Redis内存超过设置的最大内存MAXMemory时 ,会根据设置的一种缓存淘汰策略 来释放内存,默认是Noeviction不进行缓存淘汰,此时当内存满了之后就会报错OOM 当Redis的缓存达到设置的最大内存后才会执行缓存淘汰 ,共8种,默认使用noeviction不淘汰 ,此时任何增加内存均会导致报错OOM ,因为超过了最大内存且不进行淘汰 共8种,分别是针对所有的键allkeys 和只针对过期了的键volatile ,使用LRU、LFU、RANDOM、NO和TTL LRU和LFU:均用到了局部性原理
LRU是最近最久未使用 :将最近的时间内最久未使用的键淘汰 ,可以用一个队列实现,当某个键访问时加入队列,最后从队尾删除即可 ;或者为每个键设置计数器 ,当某个键访问时,计数器置为0 ,其余的键的计数器全部+1 ,最后淘汰计数器最大的键 LFU是最近最少使用 :将最近一段时间内使用次数最少得键淘汰,可以为每个键设置计数器,当访问某个键则计数器+1,最后淘汰计数器最少的键 缓存淘汰策略(8种)
默认的缓存淘汰策略就是 noeviction (不淘汰,即不进行内存优化),所有当内存达到设置的最大内存时,任何增加内存命令都会OOM报错 ,因为默认设置的不对内存进行淘汰,达到最大后就不可以再增加 allkeys :表示对所有的key都生效 volatile :表示只对过期的键key生效 小总结
一共8中缓存淘汰策略,当缓存达到设置的最大内存时就会生效 2个维度:allkeys(对所有键生效) 、volatile(只对过期的键生效) 4个方面:lru、lfu、random 、No和ttl 工作中用哪种
不确定时推荐使用allkeys-lru(对所有的键根据最近最久未使用进行淘汰) ,如果所有key访问概率都差不多,使用RANDOM,如果所有的key都有过期时间,则推荐使用volatile-ttl删除马上要过期的key通过在redis.conf配置文件中修改memory-management配置项来永久生效,要重启服务器,或者使用 config set allkeys-lru yes来开启,只当前有效 建议:避免存储bigkey ,开启惰性删除lazyfree-lazy-eviction = yes