分布式缓存
缓存使用场景
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());
// 模拟长业务5s
Thread.sleep(5000);
} catch (Exception e) {
}finally {
// 3 解锁
System.out.println("释放锁" + Thread.currentThread().getId());
lock.unlock();
}
return "hello";
}
读写锁
读锁(共享锁)会等待写锁(互斥锁,排他锁)释放,保证一定能读到最新数据
写锁也会等待读锁释放,保证写数据时不能读取数据
总结:读写互斥,不管是先读后写,还是先写后读,都会进行阻塞等待
@GetMapping("/write")
@ResponseBody
public 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")
@ResponseBody
public 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 {
@Bean
RedisCacheConfiguration 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注解
原理与不足
文章来源:https://www.toymoban.com/news/detail-493397.html
文章来源地址https://www.toymoban.com/news/detail-493397.html
到了这里,关于【系统开发】尚硅谷 - 谷粒商城项目笔记(五):分布式缓存的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!