聊聊商城项目缓存的使用

做了一个商城的项目,虽然规模不大也使用到了缓存。

就结合代码聊下如何使用缓存,避免常见的问题。问题的解释都是copy其他大佬的。

说的很详细,很清楚。之前也有过一篇详细讲的(我只是搬运工。感谢大佬的付出)

  • 缓存穿透(查询不存在的数据,疯狂loadDb)

缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。

  • 缓存击穿(同时失效,高并发疯狂load一个key的db)

对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。

缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后

  • 缓存雪崩(所有key,同时失效,即时高并发疯狂盘数据库)

缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。

获取商品信息

@Override
public String getGoodsRedisInfo(Long spuId) {
    String spuKey = MallCacheKeys.getSpuKey(spuId);
    return (String) getRedisCache(spuKey, new IMallDataCache<String>() {
        @Override
        public String get(String key) {
            return jedisCluster.get(key);
        }

        @Override
        public String push(String key) {
            List<GoodsRedisInfo> spuList = goodsSpuMapper.selectRedisInfoByPrimaryKey(spuId,null);
            if (spuList.isEmpty()) {
                // 预防缓存穿透,放入一个默认值
                return addDefaultSpuCache(key);
            }
            /*selectIntegral*/
            List<GoodsSku> skuList = goodsSkuMapper.selectByspuId(spuId);
            if(skuList!=null){
                GoodsSku goodsSku = skuList.get(0);
                if (goodsSku!=null){
                    spuList.get(0).setIntegral(goodsSku.getIntegral());
                    spuList.get(0).setIntegralPrice(goodsSku.getIntegralPrice());
                }
            }
            return addSpuCache(spuList.get(0));
        }
    });
}

获取缓存信息

/**
 * 获取Redis缓存数据
 *
 * @param key           KEY
 * @param mallDataCache 商店数据缓存接口
 * @return 缓存信息
 */
private Object getRedisCache(String key, IMallDataCache<?> mallDataCache) {
    Object cache = mallDataCache.get(key);
    if (cache == null) { //代表缓存值过期
        //设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db
        String mutexKey = key + MallCacheKeys.tmpSuffix;
        //设置互斥锁,防止缓存击穿
        if (jedisCluster.setnx(mutexKey, "1") == 1) {  //代表设置成功
            jedisCluster.expire(mutexKey, preventCacheThroughTime);
            cache = mallDataCache.push(key);
            jedisCluster.del(mutexKey);
        } else {  //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可
            int i = 0;
            int retry = 5;
            while (i++ < retry) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    LOGGER.error("读取缓存线程意外终止!", e);
                    throw new MallException("等待超时,请稍后重试!");
                }
                cache = mallDataCache.get(key);
                if (cache != null) {
                    return cache;
                }
            }
            throw new MallException("等待超时,请稍后重试!");
        }
    }
    return cache;
}