文章目录
- 分布式缓存
- 缓存使用场景
- redis作缓存中间件
- 引入redis依赖
- 配置redis
- 堆外内存溢出
- 缓存失效问题
- 缓存穿透
- 缓存雪崩
- 缓存击穿
- Redisson分布式锁
- 导入依赖
- redisson配置类
- 可重入锁
- 读写锁
- 缓存一致性解决
- 缓存-SpringCache
- 简介
- @Cacheable
- 自定义缓存配置
- @CacheEvict
- @CachePut
- 原理与不足
分布式缓存
缓存使用场景
redis作缓存中间件
引入redis依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置redis
# ip地址
spring.redis.host=124.222.43.217
# 端口
spring.redis.port=6379
堆外内存溢出
<!--引入redis,排除lettuce,使用jedis-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><exclusions><exclusion><groupId>io.lettuce</groupId><artifactId>lettuce-core</artifactId></exclusion></exclusions>
</dependency>
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId>
</dependency>
缓存失效问题
缓存穿透
缓存雪崩
缓存击穿
Redisson分布式锁
导入依赖
<!-- 以后使用redisson作为所有分布式锁,分布式对象等功能框架 -->
<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.12.0</version>
</dependency>
redisson配置类
@Configuration
public class MyRedissonConfig {@Bean(destroyMethod="shutdown")RedissonClient redissonClient() throws IOException {Config config = new Config();// 这里必须以redis://开头,否则会报错config.useSingleServer().setAddress("redis://124.222.43.217:6379");return Redisson.create(config);}
}
可重入锁
可重入锁解释:无论是公平方式还是非公平方式,进门坐下来之后,你可以问医生问题一次,两次,无数次( 重入),只要你还坐着,你都可以问,但是一旦起身离开座位,你的位置就会被抢,除非没人排队,不然你失去了提问的资格。
@ResponseBody
@GetMapping("/hello")
public String hello() {//1 获取一把锁,只要锁的名字一样,就是同一把锁RLock lock = redisson.getLock("my-lock");// 2 加锁lock.lock(); // 阻塞式等待,默认加的锁都是30s// 锁的自动续期,如果业务超长,运行期间自动给锁续上新的30s。不用担心业务时间长,锁自动过期被删除// 加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30s以后自动删除lock.lock(10, TimeUnit.SECONDS); //10s自动解锁,自动解锁时间一定要大于业务的执行时间// lock.lock(10, TimeUnit.SECONDS); // 锁时间到了之后,不会自动续期try {System.out.println("加锁成功,执行业务" + Thread.currentThread().getId());// 模拟长业务5sThread.sleep(5000);} catch (Exception e) {}finally {// 3 解锁System.out.println("释放锁" + Thread.currentThread().getId());lock.unlock();}return "hello";
}
读写锁
读锁(共享锁)会等待写锁(互斥锁,排他锁)释放,保证一定能读到最新数据
写锁也会等待读锁释放,保证写数据时不能读取数据
总结:读写互斥,不管是先读后写,还是先写后读,都会进行阻塞等待
@GetMapping("/write")@ResponseBodypublic String writeValue() {RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");String s = "";// 改数据,加写锁RLock rLock = lock.writeLock();try {rLock.lock();System.out.println("nihao");s = UUID.randomUUID().toString();Thread.sleep(30000);redisTemplate.opsForValue().set("writeValue", s);} catch (Exception e) {e.printStackTrace();}finally {rLock.unlock();}return s;}
@GetMapping("/read")@ResponseBodypublic String readValue() {RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");String s = "";// 读数据,加读锁RLock rLock = lock.readLock();try {rLock.lock();s = redisTemplate.opsForValue().get("writeValue");} catch (Exception e) {e.printStackTrace();}finally {rLock.unlock();}return s;}
缓存一致性解决
双写模式:写操作后,同时修改缓存
失效模式:写操作后,删除缓存
缓存-SpringCache
简介
@Cacheable
引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId>
</dependency>
properties配置
# 使用redis作为缓存
spring.cache.type=redis
# 设置有效时间,毫秒为单位,3600*1000
spring.cache.redis.time-to-live=3600000
# 是否使用缓存前缀
spring.cache.redis.use-key-prefix=true
# 指定缓存前缀,如果不指定,则默认使用注解中value指向的值(分组名),如果value没有指向任何值,则无缓存前缀
# spring.cache.redis.key-prefix=WSKH_CACHE_
# 是否缓存空值,防止缓存穿透
spring.cache.redis.cache-null-values=true
spring.session.store-type=redis
启动类上加注解,开启缓存
@EnableCaching
在要开启缓存的方法上加上@Cacheable注解,示例如下所示:
// 每一个需要缓存的数据我们都要指定放到哪个名字的缓存。【缓存的分区】
// 当前方法的结果需要缓存,如果缓存中有,方法不调用,如果缓存中没有,会调用方法,最后将方法的结果放入缓存
// value = {"category"} 表示:属于哪个缓存分区(分组),当没有指定缓存前缀的时候,就会使用这个分组名作为前缀
// key = "#root.method.name" 表示:将方法名作为存入redis中的键
// sync = true 表示:是否为同步代码块
@Cacheable(value = {"category"},key = "#root.method.name",sync = true)
@Override
public List<CategoryEntity> getLevel1Categorys() {long l = System.currentTimeMillis();List<CategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));System.out.println("消耗时间," + (System.currentTimeMillis() - 1));return categoryEntities;
}
自定义缓存配置
默认缓存数据是保存JDK序列化后的数据,一般业务上需要使用JSON格式进行缓存的存储(JSON具有跨语言,跨平台的高兼容性)
@EnableConfigurationProperties(CacheProperties.class)
@Configuration
@EnableCaching
public class MyCacheConfig {@BeanRedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));CacheProperties.Redis redisProperties = cacheProperties.getRedis();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;}
}
@CacheEvict
- value = “category”:指定分组名,即缓存默认前缀
- allEntries = true:指定删除value指向的分组下的所有缓存数据
- key = " ‘myKey’ ":指定缓存的key值,如果是普通字符串,则需要在双引号中加单引号,将其包裹
@CacheEvict(value = "category",allEntries = true) // 失效模式
@CachePut // 双写模式
@Transactional
@Override
public void updateCasecade(CategoryEntity category) {this.updateById(category);categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
}
同时清除多个缓存
@CachePut
双写模式,修改完数据库后,将返回的结果更新到缓存中,如果方法返回修饰符为void,那么就不能使用@CachePut注解