参考地址(Redis的基本使用)
https://blog.csdn.net/suchahaerkang/article/details/108561366
在使用redis进行开发的,出现OutOfDirectMemoryError堆外内存溢出
2021-05-29 14:10:28.509 WARN 18020 — [ioEventLoop-4-1] io.lettuce.core.protocol.CommandHandler : null Unexpected exception during request: io.netty.util.internal.OutOfDirectMemoryError: failed to allocate 46137344 byte(s) of direct memory (used: 58720256, max: 100663296)
使用58720256,需要释放空间46137344 ,但是最大内存100663296 (46137344 +58720256 >100663296 )
产生堆外内存溢出OutOfDirectMemoryError:
1)、springboot2.0以后默认使用lettuce操作redis的客户端,它使用netty进行与redis的网络通信
2)、lettuce的bug导致netty堆外内存溢出 -Xmx300m; netty如果没有指定堆外内存,默认使用-Xmx300m 可设置:
-Dio.netty.maxDirectMemory
解决方案:不能直接使用-Dio.netty.maxDirectMemory去调大堆外内存
当你调大 -Xmx300m运行内存空间时,只是减缓了OutOfDirectMemoryError异常出现的时间
1)、升级lettuce客户端。(项目推荐使用,需要手动解决该问题。lettuce在使用netty进行通信时,没有及时释放空间导致内存溢出,但是netty吞吐量达,新,所以推荐使用)
2)、切换使用jedis(比较老的版本)
使用Redis缓存性能发生大的改变
高并发下缓存失效的问题
(读模式)缓存穿透, 缓存雪崩 缓存击穿
穿透:查询一个永不存在的数据
雪崩:缓存key同一时间大面积失效
击穿:某一个单热点被高频访问,在前一点突然失效。解决方案:
1、空结果缓存:解决缓存穿透
2、设置过期时间(加随机值),解决缓存雪崩
3、加锁:解决缓存击穿
解决缓存击穿,进行加锁
1. 本地锁
synchronized关键字:表明这是一个加锁的操作。
锁:其实就是一个对象。只要是同一把锁,就能锁住这个锁的所有线程换句话说就是只要是用同一个对象锁,那么调用这个锁对象的所有进程都能锁住。
本地锁只能是在单体应用可以使用,如果是高并发的情况下,假如10个机器,就有10把锁。
synchronized关键字修饰的锁和JUC(Lock)都是本地锁,只能锁住当前进程
锁时序问题
黑色框表示锁
2. 分布式锁
分布式的情况下,锁住所有的进程。
本地锁:当是单体应用的时候,使用的是本地锁,例如JUC,里面有各种锁,比如可重入锁等。
分布式锁:当是多个应用的时候,这个时候要锁住所有应用的进程,需要Redis提供的分布式锁,Redisson对各种锁进行封装,可以使用简单的代码进行加锁。
我们可以同时去一个地方“占坑”,如果占到,就执行逻辑。否则就必须等待,直到释放锁。占坑可以去Redis,也可以去数据库,可以去任何大家都可以访问到的地方。等待可以自旋的方式。
使用锁的过程中注意的方式:
1.加锁和设置时间保持原子性
2. 删除锁,保证删除自己的锁(1.去redis获取lock锁的值 2.将获取的值与当前uuid比较,相等则删除)
步骤1 + 步骤2 ==保持原子性 ,使用redis官方提供的脚本运行。
3.让锁能够自动续机:当业务执行代码时间过长,还没执行完业务代码,锁就到期了,需要自动续机。
解决方案:(1)设置锁的时间长一点,加上try(业务代码)finally{删锁},无论业务代码是否有问题,都要删锁。
(2) 。。。。
分布式锁-Redisson(锁框架,封装了各种分布式锁)
1.可重入锁
2.读写锁
3.信号量
4.分布式闭锁
写模式(缓存数据一致性问题)
双写模式和失效模式
Canal,阿里开源,缓存数据一致性解决方案
Canal是一个阿里开源的一个组件,用来解决缓存数据和数据库数据一致性问题而开发的
Spring Cache
spring cache提供了对缓存技术的crud操作,缓存技术可以使用Redis,Map等等
步骤
1、引入依赖
spring-boot-starter-cache,spring-boot-starter-data-redis
2、写配置
2.1 自动配置了那些 (CacheAutoConfiguration 会导入 RedisCacheConfiguration,自动配好了缓存管理器)
2.2 配置使用Redis作为缓存 spring.cache.type=redis
3、使用缓存
3.1 在启动类上@EnableCaching 注解 开启缓存
3.2 使用相关Cache注解实现缓存操作
SPEL操作文档
使用缓存注解
spring cache原理
如果容器中的一个组件,只有一个构造器,那么其构造器的参数都是有容器提供的
自动配置类CacheAutoConfiguration —》 导入RedisCacheConfiguration 配置 —》自动配置了RedisCacheManager —》初始化所有缓存 —》每个缓存决定使用什么配置—》如果有redisCacheConfiguration就用已有的,没有就用默认配置 —》想改缓存配置,就给容器中放一个RedisCacheConfiguration即可 —》就会应用到当前RedisCacheManager 管理的所有的缓存分区中。
/**
* 每一个需要缓存的数据我们都来指定要放到那个名字的缓存。【缓存的分区(按照业务类型分)】
* 代表当前方法的结果需要缓存,如果缓存中有,方法都不用调用,如果缓存中没有,会调用方法。最后将方法的结果放入缓存
* 默认行为
* 如果缓存中有,方法不再调用
* key是默认生成的: 缓存的名字::SimpleKey::[] (自动生成key值),配置文件指定前缀的话,则是:前缀SimpleKey::[]
* 缓存的value值,默认使用jdk序列化机制,将序列化的数据存到redis中
* 默认时间是 -1:永不过期
*
* 自定义操作:key的生成
* 指定生成缓存的key:key属性指定,接收一个Spel表达式
* 指定缓存的数据的存活时间:配置文档中修改存活时间
* 将数据保存为json格式:保证如果使用的是PHP语言等也能使用,自定义自己的配置文件MyCacheConfig
* 1.缓存的自动配置CacheAutoConfiguration
* 2.RedisCacheConfigration
*
*
* 4、Spring-Cache的不足之处:
* 1)、读模式
* 缓存穿透:查询一个null数据。解决方案:缓存空数据
* 缓存击穿:大量并发进来同时查询一个正好过期的数据。解决方案:加锁 ? 默认是无加锁的;使用sync = true来解决击穿问题
* 缓存雪崩:大量的key同时过期。解决:加随机时间。加上过期时间
* 2)、写模式:(缓存与数据库一致)
* 1)、读写加锁。
* 2)、引入Canal,感知到MySQL的更新去更新Redis
* 3)、读多写多,直接去数据库查询就行
*
* 总结:
* 常规数据(读多写少,即时性,一致性要求不高的数据,完全可以使用Spring-Cache):写模式(只要缓存的数据有过期时间就足够了)
* 特殊数据:特殊设计
*
* 原理:
* CacheManager(RedisCacheManager)->Cache(RedisCache)->Cache负责缓存的读写
*
*/
spring Cache 不足
4、Spring-Cache的不足之处:
* 1)、读模式
* 缓存穿透:查询一个null数据。解决方案:缓存空数据 ==》配置文件:spring.cache.redis.cache-null-values=true
* 缓存击穿:大量并发进来同时查询一个正好过期的数据。解决方案:加锁 ? 默认是无加锁的;使用sync = true来解决击穿问题
* 缓存雪崩:大量的key同时过期。解决:加随机时间。加上过期时间
* 2)、写模式:(缓存与数据库一致)
* 1)、读写加锁。
* 2)、引入Canal,感知到MySQL的更新去更新Redis
* 3)、读多写多,直接去数据库查询就行
*
* 总结:
* 常规数据(读多写少,即时性,一致性要求不高的数据,完全可以使用Spring-Cache):写模式(只要缓存的数据有过期时间就足够了)
* 特殊数据:特殊设计
*
* 原理:
* CacheManager(RedisCacheManager)->Cache(RedisCache)->Cache负责缓存的读写