Redis

## Redis ##### 1)、引入依赖 ```xml <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> ``` ##### 2)、yml配置 ```yml spring: redis: host: 192.168.56.10 port: 6379 ``` 出现的问题 1. 产生堆外内存溢出:OutOfDirectMemoryError //1)、springboot2.0以后默认使用lettuce作为操作redis的客户端。它使用netty进行网络通信。 //2)、lettuce的bug导致netty堆外内存溢出 -Xmx300m;netty如果没有指定堆外内存,默认使用-Xmx300m // 可以通过-Dio.netty.maxDirectMemory进行设置 //解决方案:不能使用-Dio.netty.maxDirectMemory只去调大堆外内存。 //1)、升级lettuce客户端。 2)、切换使用jedis // redisTemplate: // lettuce、jedis操作redis的底层客户端。Spring再次封装redisTemplate; ## 高并发下缓存失效问题----缓存穿透 ### 缓存穿透: 查询一个一定不存在的数据,缓存是不命中,将去查询数据库,数据库也无此记录。我们没有将这次查询的null写入缓存,这将导致这个不存在的数据每次请求都要到数据库查询,失去了缓存的意义。 ### 风险: 利用不存在的数据进行攻击,数据库瞬间压力增大,最终崩溃。 ### 解决: null结果缓存,并加入短暂过期时间。 ## 高并发下缓存失效问题----缓存雪崩 ### 缓存雪崩: 缓存雪崩是指在我们设置缓存时key采用相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,瞬时压力过重雪崩。 ### 解决: 原有的失效时间基础上增加一个随机值,比如1-5分钟随机,每一个缓存的过期时间的重复率就会降低,很难引发集体失效的事件。 ## 高并发下缓存失效问题----缓存击穿 ### 缓存击穿: 设置了过期时间的key,会在某些时间点被高并发地访问(热点数据)这个key在大量请求同时进来前正好失效,所有对这个key的数据查询都到了DB,我们称为缓存击穿 ### 解决: 加锁 大量并发只让一个人去查,其他人等待,查到后释放锁其他人获取锁,先查缓存有数据,不用去db。 ## redisson 分布式锁 ##### 1)、引入依赖 ```xml <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.12.0</version> </dependency> ``` ##### 2)、配置redisson ```java @Configuration public class MyRedissonConfig { /** * 所有对Redisson的使用都是通过RedissonClient对象 * @return * @throws IOException */ @Bean(destroyMethod="shutdown") public RedissonClient redisson(@Value("${spring.redis.host}") String url) throws IOException { //1、创建配置 //Redis url should start with redis:// or rediss:// Config config = new Config(); config.useSingleServer().setAddress("redis://"+url+":6379"); //2、根据Config创建出RedissonClient示例 RedissonClient redissonClient = Redisson.create(config); return redissonClient; } } ``` ##### 3)、lock ![image.png](https://cos.easydoc.net/79987554/files/l90qdf8f.png) ##### 4)、读写锁 # 一级标题 ```java //保证一定能读到最新数据,修改期间,写锁是一个排他锁(互斥锁、独享锁)。读锁是一个共享锁 //写锁没释放读就必须等待 // 读 + 读: 相当于无锁,并发读,只会在redis中记录好,所有当前的读锁。他们都会同时加锁成功 // 写 + 读: 等待写锁释放 // 写 + 写: 阻塞方式 // 读 + 写: 有读锁。写也需要等待。 // 只要有写的存在,都必须等待 @GetMapping("/write") @ResponseBody public String writeValue(){ RReadWriteLock lock = redisson.getReadWriteLock("rw-lock"); String s = ""; RLock rLock = lock.writeLock(); try { //1、改数据加写锁,读数据加读锁 rLock.lock(); System.out.println("写锁加锁成功..."+Thread.currentThread().getId()); s = UUID.randomUUID().toString(); Thread.sleep(30000); redisTemplate.opsForValue().set("writeValue",s); } catch (Exception e) { e.printStackTrace(); } finally { rLock.unlock(); System.out.println("写锁释放"+Thread.currentThread().getId()); } return s; } @GetMapping("/read") @ResponseBody public String readValue(){ RReadWriteLock lock = redisson.getReadWriteLock("rw-lock"); String s = ""; //加读锁 RLock rLock = lock.readLock(); rLock.lock(); try { System.out.println("读锁加锁成功"+Thread.currentThread().getId()); s = redisTemplate.opsForValue().get("writeValue"); Thread.sleep(30000); } catch (Exception e) { e.printStackTrace(); } finally { rLock.unlock(); System.out.println("读锁释放"+Thread.currentThread().getId()); } return s; } ``` ##### 5)、信号量限流 ```java /** * 车库停车, * 3车位 * 信号量也可以用作分布式限流; */ @GetMapping("/park") @ResponseBody public String park() throws InterruptedException { RSemaphore park = redisson.getSemaphore("park"); // park.acquire();//获取一个信号,获取一个值,占一个车位 boolean b = park.tryAcquire(); if(b){ //执行业务 }else { return "error"; } return "ok=>"+b; } @GetMapping("/go") @ResponseBody public String go() throws InterruptedException { RSemaphore park = redisson.getSemaphore("park"); park.release();//释放一个车位 return "ok"; } ``` # 缓存数据一致性 ## 1.双写模式 ![image.png](https://cos.easydoc.net/79987554/files/l90xo54e.png) ## 1.失效模式 ![image.png](https://cos.easydoc.net/79987554/files/l90xwwn4.png) 1.缓存所有数据都有过期时间,数据过期下一次查询触发主动更新 2.读写数据的时候,加上分布式读写锁 # Spring Cache ##### 1)、引入依赖 ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <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> ``` ##### 2)、properties配置 ```properties spring.cache.type=redis ``` ##### 3)、相关注解 ```java @Cacheable: Triggers cache population.:触发将数据保存到缓存的操作 @CacheEvict: Triggers cache eviction.:触发将数据从缓存删除的操作 @CachePut: Updates the cache without interfering with the method execution.:不影响方法执行更新缓存 @Caching: Regroups multiple cache operations to be applied on a method.:组合以上多个操作 @CacheConfig: Shares some common cache-related settings at class-level.:在类级别共享缓存的相同配置 ``` ##### 4)、缓存json格式,配置类 ```java @EnableConfigurationProperties(CacheProperties.class) @Configuration @EnableCaching public class MyCacheConfig { /** * 配置文件中的东西没有用上; * 1、原来和配置文件绑定的配置类是这样子的 * @ConfigurationProperties(prefix = "spring.cache") * public class CacheProperties * 2、要让他生效 * @EnableConfigurationProperties(CacheProperties.class) * @return */ @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; } } ```