redis缓存应用设计与性能优化

一、分布式锁

redis的setnx被广泛应用在分布式锁中,使用setnx时,如果值存在,则设置失败,返回0,否则设置成功返回1,因为redis主要工作线程时单线程执行,所以把这个当作分布式锁来可使用。

简单的分布式锁实现如下(缺少锁续命步骤,可以使用成熟的分布式锁框架redisson):

@RestController
public class RedisLockTest {

    @Autowired
    JedisCluster jedisCluster;

    @RequestMapping("/getValue")
    public String getValue(){
        
        //释放锁时确保是当前线程加的锁
        String uuid = UUID.randomUUID().toString();

        //redis集群setnx方法,并且设置过期时间
        String lock = jedisCluster.set("lock", uuid, "nx", "PX", 30000);
        
        //返回非OK,则获取锁失败
        if(!"OK".equalsIgnoreCase(lock)){
            return "服务器繁忙,请稍后再试!";
        }

        int stock=0;
        try{
                stock = Integer.parseInt(jedisCluster.get("stock"));
                if(stock>0){
                    stock-=1;
                    jedisCluster.set("stock",stock+"");
                    System.out.println("减库存成功,剩余库存:"+stock);
                }else {
                    System.out.println("减库存失败,剩余库存不足!");
                }
        }finally {
            //释放锁
            if (uuid.equals(jedisCluster.get("lock"))){
                jedisCluster.del("lock");
            }
        }
        return "剩余库存:"+stock;

    }
}

二、缓存穿透

缓存穿透是指从缓存中查不到数据,再到数据库去查也查不到,这样如果频繁的查询会导致系统性能的下降,甚至遭到恶意攻击时系统瘫痪。

解决办法:

1、是即使从数据库查询的数据为空,也要将查询的KEY值缓存在redis中,但是要设置一个过期时间,在短时间内查询数据虽然不一致,但是确保最终是一致的。

2、使用布隆过滤器,对于恶意攻击,向服务器请求大量不存在的key,可以使用布隆过滤器有效防止缓存穿透。其原理是事先将所有数据hash后存在bitmap中,通过查询布隆过滤器,可以过滤掉确定不存在的值。布隆过滤器说数据存在实际不一定存在,说不存在则一定不存在。

布隆过滤器实现代码:

@RestController
public class RedissonBloomFilter {
    @Autowired
    JedisCluster jedisCluster;

    @Autowired
    Redisson redisson;

    @RequestMapping("/getBloomFileter/{value}")
    public boolean getBloomFileter(@PathVariable(value = "value") String value){

        RBloomFilter<Object> bloomFilter = redisson.getBloomFilter("bloomFilter");
        初始化布隆过滤器:预计元素为1000000000L,误差率为2%
        bloomFilter.tryInit(1000000000L,0.02);
        //预设值
        bloomFilter.add("bloom");
        //查询是否存在
        boolean contains = bloomFilter.contains(value);
        System.out.println("查缓存值:"+value+" 是否存在:"+contains);
        return contains;
    }
}

三、缓存失效(击穿)

由于大量缓存在同一时间过期失效,导致大量请求同时访问数据库,使系统压力瞬间增大甚至挂掉。解决办法是设置过期时间时不要设置一样的时间,而是随机产生一个大概的时间范围,避免发生缓存击穿的现象。

四、缓存雪崩

由于大量的请求访问导致缓存层扛不住而大面积挂掉时,所有的请求都打到存储层,以至于存储层也扛不住这么大的并发量,而使系统全面瘫痪的局面。

解决办法:

1、保证缓存层的高可用,使用redis sentinel或redis cluster

2、依赖隔离组件为后端限流降级,比如Hystrix限流降级组件,当流量过大时,限制访问后端,快速返回预定的结果。

3、提前做好高并发测试演练,在缓存层宕机后出现的问题提前预案。

五、缓存与数据库数据不一致问题

在并发情况下,同时操作缓存和数据库会有数据不一致性问题。正常情况下我们更新数据的操作顺序是,先删除缓存,然后更新数据库,等有查询请求的时候先从缓存查,查不到再去数据库查,然后再缓存到redis。在单线程情况或并发不高的时候是没有问题提的,但是在高并发情况下,如果不是串行执行,没办法控制执行的顺序,当有读写操作同时进行时,就会出现,写请求先删除缓存数据,然后读操作抢先更新缓存数据,写操作再更新数据库,这就会导致缓存和数据库数据不一致问题。

解决办法:

1、对于用户个人维度的数据,一般不会出现并发的问题,只要给缓存的数据加一个过期时间,在可以容忍数据在小时间段内的数据不一致,但最终数据是一致的。

2、在大部分情况下,只要能容忍数据在小时间段内不一致,可以使用延迟双删+缓存过期时间,就可以解决大部分的并发数据不一致的问题。延迟双删就是在写操作时,先删除缓存,然后更新数据库,更新完延迟一段时间再删除缓存。

3、如果没办法容忍任何时间段内的数据不一致,例如电商库存,可以使用分布式读写锁,读读不互斥,写读互斥,写完再更新缓存,这样就不会出现数据不一致的问题了。

分布式锁解决方案:

@RestController
public class CacheAndDB {

    @Autowired
    JedisPool jedisPool;

    @Autowired
    Redisson redisson;

    @RequestMapping("/getTotal")
    public String getTotal() throws InterruptedException {
        Jedis jedis = jedisPool.getResource();
        RReadWriteLock rwLock = redisson.getReadWriteLock("RWLock");

        RLock rLock = rwLock.readLock();
        rLock.lock();
        System.out.println("获取读锁成功!");
        String total = jedis.get("total");
        if (StringUtils.isEmpty(total)){

            //模拟查询数据
            total = jedis.get("totalDB");
            System.out.println("查询数据库total: "+total);
            //设置到缓存
            jedis.set("total",total);
        }
        rLock.unlock();
        return total;
    }

    @RequestMapping("/setTotal/{value}")
    public String setTotal(@PathVariable(value = "value") int value){
        Jedis jedis = jedisPool.getResource();
        RReadWriteLock rwLock = redisson.getReadWriteLock("RWLock");
        RLock rLock = rwLock.readLock();
        rLock.lock();

        jedis.del("total");
        System.out.println("获取写锁成功!");
        //模拟更新数据库
        String set = jedis.set("totalDB", String.valueOf(Integer.parseInt(jedis.get("totalDB")) - value));

        rLock.unlock();
        return set;
    }
}

六、redis对过期键删除策略

1、被动删除:当key的过期时间已经过了,redis不会立即删除,而是当有客户端访问这个key时才会被动去清除这个过期的key

2、主动删除:redis底层有会自动定期清除过期key的线程,主动去删除过期的key

3、当redis使用的内存超过设置的maxmemory最大限定,redis会触发主动清除策略

主动清除策略:

  • volatile-ttl: 清除过期时间最早的数据

  • volatile-random: 随机清除设置了过期时间的数据

  • volatile-lru: 清除设置了过期时间中最近最少使用的数据(最近使用时间排序)

  • volatile-lfu: 清除设置了过期时间中最不经常使用的数据(最近使用的次数排序)

  • allkeys-random: 从所有键中随机删除

  • allkeys-lru: 从所有键中清除最近最少使用的数据(最近使用时间排序)

  • allkeys-lfu: 从所有键中清除最不经常使用的数据(最近使用的次数排序)

  • noevitaion: 不会清除任何数据,读数据正常执行,写数据拒绝写入

 

 

 

 

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值