分布式缓存

分布式缓存

由于服务器不可能为单体应用,大多为分布式,那么使用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
特殊数据,特殊设计。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值