畅购商城项目改造--redis分布式锁解决缓存击穿的问题

分布式锁主流的实现方案:

  1. 基于数据库实现分布式锁
  2. 基于缓存(Redis等)
  3. 基于Zookeeper

每一种分布式锁解决方案都有各自的优缺点:

  1. 性能:redis最高

  2. 可靠性:zookeeper最高
    但是畅购项目并没有使用zookeeper进行开发,功能由Eureka接替,我也没有在项目实现改造。下一篇文章再说利用模板设计模式实现分布式锁的问题。
    借助于redis中的命令setnx(key, value),key不存在就新增,存在就什么都不做。同时有多个客户端发送setnx命令,只有一个客户端可以成功,返回1(true);其他的客户端返回0(false)。
    在这里插入图片描述

  3. 多个客户端同时获取锁(setnx)

  4. 获取成功,执行业务逻辑,执行完成释放锁(del)

  5. 其他客户端等待重试
    基本思想就是这样改造,为了解决 缓存击穿 击穿是一个热点key失效,执行redis的setnx命令
    uuid是保证给自己的锁唯一标志,防止误删。
    同时string script是lua脚本的执行命令,获取值和对比是原子操作。
    核心在于加锁和解锁都是原子性的,加锁同时设置过期时间,保证数据一致性。

     String uuid = UUID.randomUUID().toString();
     Boolean aBoolean = this.redisTemplate.opsForValue().setIfAbsent("lock", uuid,10,TimeUnit.SECONDS);
    
     Content content=new Content();
     //判断是否拿到锁
     if(aBoolean){
         String jsonContent = redisTemplate.boundValueOps("content_" + id).get();
         if(jsonContent!=null){
             List<Content> contents;
             contents = JSON.parseObject(jsonContent, List.class);
             return contents;
         }
         content.setCategoryId(id);
         content.setStatus("1");
    
    
         // 2. 释放锁 del
         String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
         this.redisTemplate.execute(new DefaultRedisScript<>(script), Arrays.asList("lock"), Arrays.asList(uuid));
    
     }else {
         //其他请求尝试获取锁
         findByCategory(id);
     }
    
     return contentMapper.select(content);
    

备注:其实最好的方式还是redission加上AOP环绕通知,做成注解实现,具体查阅google
另外就是我们可以利用springCache进行改造,简化缓存开发。
第一步就是引入依赖
在这里插入图片描述
第二部自然就是配置
由于我们的中间件redis作为我们的缓存所以就要引RedisCacheConfiguration配置类对象,并且绑定我们的配置

@Configuration
@EnableCaching
@EnableConfigurationProperties(CacheProperties.class)
public class MyCacheConfig {
    /**
     * 配置文件没有用上ttl
     * 1 原来配置文件的配置类是这样
     *     @ConfigurationProperties(
     *     prefix = "spring.cache"
     * 2 让他生效  开启属性绑定生效
     * @return
     */
    RedisCacheConfiguration getRedisCacheConfiguration(CacheProperties cacheProperties){
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
        config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericFastJsonRedisSerializer()));
        //将配置文件所有配置生效
        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;
    }
}

这是properties的配置,缓存存活时间,前缀,是否使用空值做缓存在这里插入图片描述
第三就是几个注解的使用
@Cacheable 触发将数据保存到缓存
@CacheEvict 触发将数据从缓存删除
@CachePut 不影响方法执行更新缓存
@Cacheing 组合以上操作
@CacheConfig 在类级别共享缓存的相同配置

在这里插入图片描述
第一个注解value就是你的缓存分区,key就是(key,value)的key.
可以采用spel表达式,就是第一个注解,key值就是你的方法名字。
sync属性就能保证加锁,顺序去缓存,解决缓存问题。

@CacheEvict 清空缓存
因为我们更新数据之后,就要清除缓存保证数据一致性
这是清空该分区缓存的key
@CacheEvict(value = {“category”}, key = “‘Level1Categorys’”)
清空该分区的所有缓存
@CacheEvict(value = {“category”},allEntries = true)
清除多个缓存操作,使用它的evict的属性
@Caching(evict =
{@CacheEvict(value = {“category”}, key = “‘Level1Categorys’”),
@CacheEvict(value = {“category”}, key = “‘getCatelogJson’”)
})

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值