分布式缓存
由于服务器不可能为单体应用,大多为分布式,那么使用map作为缓存就会造成数据的弱一致性,这时候map就不适用于做缓存机制,我们需要引入中间件做缓存,其他的分布式机器全部在缓存中间件redis中进行读取缓存数据。
Spring Boot文档https://spring.io/projects/spring-boot/#overview
LEARN-> Reference Doc->Using Spring Boot->Starters->spring-boot-starter-data-redis寻找redis依赖
- 引入redis依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 配置信息
spring:
redis:
host:redis安装地址
port:6379 端口号固定
使用redis自带的类StringRedisTemplate进行获取数据,例如:
@Autowired
StringRedisTemplate StringRedisTemplate;
public void contextLoads() {
ValueOperations<String, String> ops = StringRedisTemplate.opsForValue();
String data = ops.get("xxxx");
}
- 此处redis中加入缓存逻辑,缓存中存放的数据为json,因为json跨语言跨平台可以使用。
//判断缓存数据是否为空
//为空先查数据库
//将查到的数据转换为json在放入缓存中
//最后再将数据逆转回来进行返回
注意:此处进行try{}catch{},防止redis出错,以后凡是进行远程访问或者访问数据库都可进行try{}catch{}防止出错
- 为了防止缓存击穿我们需要在查询数据库的代码块进行加锁
单例模式(双重校检)得到锁synchronized(){}之后,我们需要进行双重校检,再次查询缓存中是否有数据,没有才需要查询有就直接使用缓存。
分布式需要使用StringRedisTemplate.opsForValue().setIfAbsent(“xxx”,“xxx”);然后对此返回结果进行判断是否存在,如果存在,说明缓存中有数据,在缓存中查找,当占锁完成后需要删除锁。如果不存在,则说明缓存为空这个时候就需要自旋操作,将本身方法再次调用,直接返回此方法(休眠100ms重试)。
StringRedisTemplate.delete("xxx");//删除锁操作
为了防止死锁操作setIfAbsent(“xxx”,“xxx”)只传入一个锁不太满足所以需要在设置锁的时候就设置死亡时间,并且设置属于自己的独特标识
String uuid = UUID.randomUUID().toString();
Boolean lock = StringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
//使用脚本进行原子性删锁
String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return end";
Integer execute = StringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid); //Arrays转换是因为方法需要的是一个list Integer泛型是返回值为1和0成功1失败0
注意:脚本用lettuce客户端会报错,jedis不会。
为了确保可以在业务执行完成之后进行删锁我们可以使用
String uuid = UUID.randomUUID().toString();
Boolean lock = StringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
//判断缓存数据是否为空
if (lock){
try {
//此处查询数据库获取数据进行缓存
}finally {
//最后不甘失败还是成功都进行删锁操作
String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return end";
Integer execute = StringRedisTemplate.execute(new DefaultRedisScript<Integer>(script, Integer.class), Arrays.asList("lock"), uuid);
}
return 返回数据库中的数据;
}else {
try {
Thread.sleep(300);
}catch (Exception e){
}
return 返回当前方法;
}
注意:此处uuid可能被覆盖,为了程序的严谨性可以使用时间戳+uuid
注意:while比递归好用,递归容易内存溢出
整合redisson作为分布式等功能的框架
导入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.15.4</version>
</dependency>
配置redisson
https://github.com/redisson/redisson/wiki
第三方框架整合(分布式)(方法添加public)
https://github.com/redisson/redisson/wiki
注意地址为
"redis://127.0.0.1:7181"
可以用"rediss://"来启用SSL连接
第三方框架整合分布式可重入锁
lock
@Autowired
RedissonClient RedissonClient;
public String test(){
RLock lock = RedissonClient.getLock("锁名字");
//加锁,默认为阻塞式等待30s
//看门狗机制,锁的自动续期和自动删除锁
//lock.lock();
lock.lock(10, TimeUnit.SECONDS);
//自动过期时间,只要业务还在执行就还会占锁,只要业务没有结束时间就会每隔10秒自动蓄满默认为30s。常用且无需调用lock.unlock();手动解锁
try {
//业务执行代码
}catch (Exception e){
}finally {
//删除锁
lock.unlock();
}
return "";
}
分布式锁地址:https://github.com/redisson/redisson/wiki/8
读写锁保证一定能拿到最新的数据,保持了数据的一致性,写锁没释放读锁就必须等待
信号量可以用作分布式限流
使用spring的cache
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
配置
spring.cache.type=redis
添加注解
@cacheable("缓存名字")sync=true开启同步方法
注解内容:
@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.在级别共享缓存的多个配置
缓存存活时间
spring.cache.redis.tine.to-live=6000毫秒为单位
spring.cache.redis.cache-null-values=true是否缓存空值,可以防止缓存穿透
springboot使用redis,改变序列化方式
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
写一个序列化类
@EnableConfigurationProperties(CacheProperties.class)
@Configuration
@EnableCaching
public class RedisTest {
@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;
}
}
总结:常规数据,读多写少,一致性不高的数据可以使用spring-cache
特殊数据,特殊设计。