前提

  • 优化数据库io操作,一般分为两个层面,一是提高数据库sql本身的性能,二是尽量避免直接查询数据库。
    提高数据库本身的性能首先是优化sql,包括:使用索引,减少不必要的大表关联次数,控制查询字段的行数和列数。另外当数据量巨大是可以考虑分库分表,以减轻单点压力。
  • 尽量避免直接查询数据库重要的解决办法就是:缓存,缓存可以理解是数据库的一道保护伞,任何请求只要能在缓存中命中,都不会直接访问数据库。而缓存的处理性能是数据库10-100
    倍。
  • 而Redis就是作为缓存系统
  • 一般放入缓存中的大都是热点内容,并发可能也是相对高的

但是使用redis同时会出现以下几种情况:

  • 一:缓存雪崩:或者可以突然全部失效,此时如果系统并发量大的就会给数据库造成很大的压力,造成压力的高峰
  • 解决办法:将缓存中的key设置的过期时间不一致,不同,不要让它同一时间都失效,造成高峰
  • 二:缓存穿透:当用户查询一个不存在的数据
  • 解决办法:不管是不是null都放入redis中
  • 三:缓存击穿:当某一个key突然失效了,此时会导致大量的用户访问数据库,会造成缓存击穿的情况,导致数据库蹦:
  • 解决办法:加锁,限制,先让一个人去数据库查找数据,然后再放入redis缓存,此时其他线程都是被阻塞,等放入redis中,就重新在redis中查找

使用分布式锁来解决缓存击穿的问题

方式一:使用Redis命令

set sku:1:info “OK” NX PX 10000

  • EX second :设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。
  • PX millisecond :设置键的过期时间为 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX key millisecond value 。
  • NX :只在键不存在时,才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。
  • XX :只在键已经存在时,才对键进行设置操作。

简单来说就是第一个人设置了这个带NX参数的键,只在键不存在时,才对键进行设置操作。 所以其他人在设置也设置不了了,然后自旋锁,也就是被阻塞了,当第一个人设置完后进入DB数据库找完数据在放入缓存中,当这个键结束后,据可以自爱redis中查到第一个人放入的缓存

实现代码:

// 没有数据 ,需要加锁!取出完数据,还要放入缓存中,下次直接从缓存中取得即可!
        System.out.println("没有命中缓存");
        // 定义key user:userId:lock
        String skuLockKey=ManageConst.SKUKEY_PREFIX+skuId+ManageConst.SKULOCK_SUFFIX;
        // 生成锁
        String lockKey  = jedis.set(skuLockKey, "OK", "NX", "PX", ManageConst.SKULOCK_EXPIRE_PX);
        if ("OK".equals(lockKey)){
            System.out.println("获取锁!");
            // 从数据库中取得数据
            skuInfo = getSkuInfoDB(skuId);
            // 将是数据放入缓存
            // 将对象转换成字符串
            String skuRedisStr = JSON.toJSONString(skuInfo);
            jedis.setex(skuInfoKey,ManageConst.SKUKEY_TIMEOUT,skuRedisStr);
            jedis.close();
            return skuInfo;
        }else {
            System.out.println("等待!");
            // 等待
            Thread.sleep(1000);
            // 自旋
           return getSkuInfo(skuId);
        }
    }else{
        // 有数据
        skuInfo = JSON.parseObject(skuJson, SkuInfo.class);
        jedis.close();
        return skuInfo;
    }
方式二:使用redisson解决分布式锁
#1.导入依赖 service-util
<dependency>
   <groupId>org.redisson</groupId>
   <artifactId>redisson</artifactId>
   <version>3.11.1</version>
</dependency>

代码:

// 使用redisson 调用getLock
        RLock lock = redissonClient.getLock("yourLock");


        // 加锁
        lock.lock(10, TimeUnit.SECONDS);

//        try {
//            boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
//
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        // 放入业务逻辑代码
        SkuInfo skuInfo =null;
        Jedis jedis = null;
        // ctrl+alt+t
        try {
            jedis = redisUtil.getJedis();
            // 定义key: 见名之意: sku:skuId:info
            String skuKey = ManageConst.SKUKEY_PREFIX+skuId+ManageConst.SKUKEY_SUFFIX;
            // 判断缓存中是否有数据,如果有,从缓存中获取,没有从db获取并将数据放入缓存!
            // 判断redis 中是否有key
            if (jedis.exists(skuKey)){
                // 取得key 中的value
                String skuJson = jedis.get(skuKey);
                // 将字符串转换为对象
                skuInfo = JSON.parseObject(skuJson, SkuInfo.class);
//                jedis.close();
                return skuInfo;
            }else {
                skuInfo = getSkuInfoDB(skuId);
                // 放redis 并设置过期时间
                jedis.setex(skuKey,ManageConst.SKUKEY_TIMEOUT,JSON.toJSONString(skuInfo));
                return skuInfo;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedis!=null){
                jedis.close();
            }
            lock.unlock();
        }
        return getSkuInfoDB(skuId);

可以看到使用redisson的方式简单明了也方便