本文共 13985 字,大约阅读时间需要 46 分钟。
#这里注意springboot版本,springboot稍微旧一点的版本不需要加上-data-字样org.springframework.boot spring-boot-starter-data-redis
@Override public Map> getCatalogJson2(){ //先去redis中查询缓存是否存在需要读的数据 String catalogJson = redisTemplate.opsForValue().get("catalogJson"); //如果读不到缓存,缓存不存在 if(StringUtils.isEmpty(catalogJson)){ //去db读取缓存 Map > catalogJson2FromDB = this.getCatalogJson2FromDB(); //将查询的数据序列化成json对象,设置缓存方便下次使用 String cacheCatalog = JSON.toJSONString(catalogJson2FromDB); redisTemplate.opsForValue().set("catalogJson",cacheCatalog); return catalogJson2FromDB; } //如果查到缓存,则读取缓存,将缓存的json转换成对象,使用匿名内部类将json转换成指定的类型。 Map > result = JSON.parseObject(catalogJson, new TypeReference
io.netty.util.internal.OutOfDirectMemoryError: failed to allocate 46137344 byte(s) of direct memory (used: 58720256, max: 100663296) at io.netty.util.internal.PlatformDependent.incrementMemoryCounter(PlatformDependent.java:725) ~[netty-common-4.1.39.Final.jar:4.1.39.Final] at io.netty.util.internal.PlatformDependent.allocateDirectNoCleaner(PlatformDependent.java:680) ~[netty-common-4.1.39.Final.jar:4.1.39.Final] at io.netty.buffer.PoolArena$DirectArena.allocateDirect(PoolArena.java:772) ~[netty-buffer-4.1.39.Final.jar:4.1.39.Final] at io.netty.buffer.PoolArena$DirectArena.newUnpooledChunk(PoolArena.java:762) ~[netty-buffer-4.1.39.Final.jar:4.1.39.Final] at io.netty.buffer.PoolArena.allocateHuge(PoolArena.java:260) ~[netty-buffer-4.1.39.Final.jar:4.1.39.Final] at io.netty.buffer.PoolArena.allocate(PoolArena.java:232) ~[netty-buffer-4.1.39.Final.jar:4.1.39.Final] at io.netty.buffer.PoolArena.reallocate(PoolArena.java:400) ~[netty-buffer-4.1.39.Final.jar:4.1.39.Final] at io.netty.buffer.PooledByteBuf.capacity(PooledByteBuf.java:119) ~[netty-buffer-4.1.39.Final.jar:4.1.39.Final] at io.netty.buffer.AbstractByteBuf.ensureWritable0(AbstractByteBuf.java:303) ~[netty-buffer-4.1.39.Final.jar:4.1.39.Final] at io.netty.buffer.AbstractByteBuf.ensureWritable(AbstractByteBuf.java:274) ~[netty-buffer-4.1.39.Final.jar:4.1.39.Final] at io.netty.buffer.AbstractByteBuf.writeBytes(AbstractByteBuf.java:1111) ~[netty-buffer-4.1.39.Final.jar:4.1.39.Final] at io.netty.buffer.AbstractByteBuf.writeBytes(AbstractByteBuf.java:1104) ~[netty-buffer-4.1.39.Final.jar:4.1.39.Final] at io.netty.buffer.AbstractByteBuf.writeBytes(AbstractByteBuf.java:1095) ~[netty-buffer-4.1.39.Final.jar:4.1.39.Final] at io.lettuce.core.protocol.CommandHandler.channelRead(CommandHandler.java:554) ~[lettuce-core-5.1.8.RELEASE.jar:na] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374) [netty-transport-4.1.39.Final.jar:4.1.39.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360) [netty-transport-4.1.39.Final.jar:4.1.39.Final] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352) [netty-transport-4.1.39.Final.jar:4.1.39.Final] at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1421) [netty-transport-4.1.39.Final.jar:4.1.39.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374) [netty-transport-4.1.39.Final.jar:4.1.39.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360) [netty-transport-4.1.39.Final.jar:4.1.39.Final] at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:930) [netty-transport-4.1.39.Final.jar:4.1.39.Final] at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163) [netty-transport-4.1.39.Final.jar:4.1.39.Final] at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:697) [netty-transport-4.1.39.Final.jar:4.1.39.Final] at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:632) [netty-transport-4.1.39.Final.jar:4.1.39.Final] at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:549) [netty-transport-4.1.39.Final.jar:4.1.39.Final] at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:511) [netty-transport-4.1.39.Final.jar:4.1.39.Final] at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:918) [netty-common-4.1.39.Final.jar:4.1.39.Final] at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) [netty-common-4.1.39.Final.jar:4.1.39.Final] at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) [netty-common-4.1.39.Final.jar:4.1.39.Final] at java.lang.Thread.run(Thread.java:748) [na:1.8.0_291]2021-06-18 10:54:17.263 WARN 9796 --- [ioEventLoop-4-2] 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)
@Override public Map> getCatalogJson2(){ //先去redis中查询缓存是否存在需要读的数据 String catalogJson = redisTemplate.opsForValue().get("catalogJson"); //如果读不到缓存,缓存不存在 if(StringUtils.isEmpty(catalogJson)){ System.out.println("缓存不命中,查询数据库"); //去db读取缓存 Map > catalogJson2FromDB = this.getCatalogJson2FromDB(); return catalogJson2FromDB; } System.out.println("缓存命中,没有查询数据库"); //如果查到缓存,则读取缓存,将缓存的json转换成对象,使用匿名内部类。在这里也可以通过自己配置的redisTemplate,在配置文件中统一对数据进行序列化与反序列化处理 Map > result = JSON.parseObject(catalogJson, new TypeReference
setnx
实现的。setnx的意思是不存在即创建,存在的话则不创建。即同一时刻只能设置成功一个。TODO
1、导入maven坐标
org.redisson redisson 3.13.4
2、编写配置文件
@Configurationpublic class MyRedisConfig { // destoryMethod为redisson被销毁时调用的方法。 @Bean(destroyMethod = "shutdown") public RedissonClient redisson() { Config config = new Config(); // 创建单例模式的配置 config.useSingleServer().setAddress("redis://" + YourIP + ":6379"); return Redisson.create(config); }}
3、测试
@ResponseBody @GetMapping("/hello") private String hello(){ //注意这里,这里只要调用的锁名字相同,那么就是同一把锁。两个商品服务都调用了这个锁,那么只有一个会被锁住。 //很好的解决了分布式下缓存不一致的问题 //RLock本质上其实是可重入锁 //阻塞等待机制(默认),可以自己设置等待时间以及上锁后自动解锁时间 RLock lock = redisson.getLock("ny_lock"); // 自动解锁,加锁以后10秒钟自动解锁,看门狗不续命,使用的话自动解锁时间必须大于业务时间。 //lock.lock(10, TimeUnit.SECONDS); //上锁 lock.lock(); try{ System.out.println(Thread.currentThread().getId()+":"+Thread.currentThread().getName()+"加锁成功"); Thread.sleep(10000); } catch (Exception e) { e.printStackTrace(); } finally { System.out.println(Thread.currentThread().getId()+":"+Thread.currentThread().getName()+"解锁成功"); lock.unlock(); } return "hello"; }#看门狗机制可以确保在redission在关闭之前,即该线程执行完之前,或异常退出之前,该线程一直占有该线程需要的锁,锁续期时间为30秒。很好的解决了如某线程占有锁期间服务宕机导致死锁的情况,以及某个线程业务过长,业务执行期间锁自动过期被删掉的情况。
#写锁@ResponseBody @GetMapping("/write") private String writeHi(){ String s = ""; RReadWriteLock lock = redisson.getReadWriteLock("rw-lock"); lock.writeLock().lock(); try { s = UUID.randomUUID().toString(); redisTemplate.opsForValue().set("write",s); System.out.println(Thread.currentThread().getId()+"写锁正在写数据"); Thread.sleep(10000); }catch (Exception e){ e.printStackTrace(); }finally { lock.writeLock().unlock(); } return s; }#读锁 @ResponseBody @GetMapping("/read") private String readHi(){ String s = ""; RReadWriteLock lock = redisson.getReadWriteLock("rw-lock"); lock.readLock().lock(); try { s = redisTemplate.opsForValue().get("write"); System.out.println(Thread.currentThread().getId()+"写锁正在读数据"); }catch (Exception e){ e.printStackTrace(); }finally { lock.readLock().unlock(); } return s; }
@ResponseBody @GetMapping("/park") private String park(){ //从redis中获取key为semaphore的信号量 RSemaphore semaphore = redisson.getSemaphore("semaphore"); //判断信号量是否还有剩余 boolean b = semaphore.tryAcquire(); if(b){ return "停车成功"; } return "暂无车位,请稍后再试"; } @ResponseBody @GetMapping("/go") private String go(){ RSemaphore semaphore = redisson.getSemaphore("semaphore"); //释放一个信号量 semaphore.release(); return "欢迎下次光临"; }
写数据的时候,同时写缓存
可能存在脏数据的问题,最终一致性存在误差 可以通过加锁的方式,保证写数据库与写缓存同一时间只能由一个线程执行,从而实现一致性。
写数据的时候,同时删除缓存。下一次查询的时候,再更新缓存。
还是存在一致性的问题。还是可以通过加读写锁进行解决。不管是读+写还是写+读的模式,都需要按照顺序执行完毕之后再进行锁内操作。
org.springframework.b oot spring-boot-starter-cache
spring: cache: # 指定缓存类型为redis type: redis redis: # 指定redis中的过期时间为1h,(不应统一设置缓存失效时间,存在缓存雪崩的风险) time-to-live: 3600000 # 缓存前缀 # 不指定前缀名,就让分区名作为前缀 key-prefix: cache_ # 开启缓存前缀 use-key-prefix: true # 防止缓存穿透,查询不到数据返回null cache-null-values: true
@EnableCaching
@Configuration@EnableCachingpublic class MyCacheConfig { @Bean public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) { CacheProperties.Redis redisProperties = cacheProperties.getRedis(); RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig(); //指定缓存序列化方式为json config = config.serializeValuesWith( RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); //设置配置文件中的各项配置,如过期时间 if (redisProperties.getTimeToLive() != null) { config = config.entryTtl(redisProperties.getTimeToLive()); } if (redisProperties.getKeyPrefix() != null) { config = config.prefixKeysWith(redisProperties.getKeyPrefix()); } if (!redisProperties.isCacheNullValues()) { config = config.disableCachingNullValues(); } if (!redisProperties.isUseKeyPrefix()) { config = config.disableKeyPrefix(); } return config; }}
//Cacheable的使用表示当前方法返回的结果需要放入缓存中。如果缓存有该结果,则该接口不调用直接从缓存拿数据,如果没有,则调用该接口并放入缓存//该注解里面的名字表示将该缓存的结果分到那个区 @Cacheable(value = "{category}", key = "#root.method.name") @Override public ListgetLevel1Categorys() { List categoryEntities = this.baseMapper.selectList(new QueryWrapper ().eq("parent_cid", 0)); return categoryEntities; }
SpringCache相关的默认配置:
序列化采用的是jdk自带的序列化,可读性差、兼容性差,应将数据保存为json格式— 过期时间为-1,永不过期—yml配置文件修改存活时间 key名字自动生成—可通过注解的key属性配置自己的key
失效策略
的使用,即在更新db的时候删除缓存,下次查询的时候再写入缓存。有两个关键的属性值为key和value,要删除哪一块的缓存以及,这两个值就得对应前面新增缓存@CacheAble中key,value的值。/** * 缓存失效下CacheEvict的使用 * */ @CacheEvict(value = "category", key = "'getLevel1Categorys'") @Override public void updateDetail(CategoryEntity category) { //先更新自己 this.updateById(category); //级联更新其他 categoryBrandRelationService.updateCategory(category.getCatId(),category.getName()); }
@Caching(evict = { @CacheEvict(value = "category", key = "'getLevel1Categorys'"), @CacheEvict(value = "category", key = "'getCatalogJson2'") })
#注意,使用该方式进行批量删除,一定要开启允许使用前缀。use-key-prefix: true#不然的话进行删除的时候会把所有的key全部删掉@CacheEvict(value = "category",allEntries = true)
- 读模式 缓存穿透:大量查询一个不存在的数据。解决方案:缓存空数据,可通过spring.cache.redis.cache-null-values=true返回null数据 缓存击穿:大量并发进来同时查询一个正好过期的数据。解决方案:CacheAble注解中添加sync=true加锁,解决缓存击穿问题。 缓存雪崩:大量的key在同一时间过期。解决:加随机时间。
- 写模式 如果对于最终一致性(弱一致性)要求不高,加缓存过期时间即可。如果一致性要求高,可以通过加读写锁的方式解决。
转载地址:http://ccqof.baihongyu.com/