Redis 高并发缓存架构实战与性能优化

前置知识

1、缓存击穿、缓存失效的基本概念
2、什么样的 数据 需要加分布式锁
3、课上代码

05-一线大厂Redis高并发缓存架构实战与性能优化

对于 公司 中 简单的增删改查 做 高性能处理 , 采用递进的方式一步步优化。

  1. 普通Redis用法: 新增、修改、删除设置缓存 , 查询是获取缓存 ,如果没获取到取数据库 。
  2. 实现简单的数据冷热分离: 采用 对 key 加 过期时间 、 查询时 做 缓存读延期 处理。( 这种 针对于 大规模 数据 , 如京东商品 可能 上亿, 但是 只有1% 的商品经常访问 。 )
  3. 对于数据的批量更新问题: 批量更新会导致大量数据的数据同时过期, ( 数据库压力可能增大,导致数据库抖动 这就是缓存击穿 )在设置过期时间时添加 随机过期时间 解决缓存击穿问题
  4. 解决缓存穿透问题: 如果数据已经删除,但是还有请求 到查询接口,数据库与Redis都查不到。 解决方案:设置空缓存,并设置一个相对较短的过期时间。( 即使有几十万 空数据缓存 , 那么 在 几分钟后都失效了,不会占用 缓存空间)
  5. 冷门数据缓存重建问题: DCL(double check look)机制实现缓存重建
  6. 使用分布式锁解决缓存数据库双写不一致
  7. 如果读多写少 使用读写锁 优化分布式锁,
  8. 优化DCL锁,使用tryLock(权衡使用与否)
  9. 对于单节点 redis 并发 差不多十万,如果是上百万并发同时访问redis 不一定能抗住,会导致缓存雪崩。需要本地缓存架构解决,如Guava、Ehcache。

代码

@Service
public class ProductService {

    @Autowired
    private ProductDao productDao;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private Redisson redisson;

    public static final Integer PRODUCT_CACHE_TIMEOUT = 60 * 60 * 24;
    public static final String EMPTY_CACHE = "{}";
    public static final String LOCK_PRODUCT_HOT_CACHE_PREFIX = "lock:product:hot_cache:";
    public static final String LOCK_PRODUCT_UPDATE_PREFIX = "lock:product:update:";
    public static Map<String, Product> productMap = new ConcurrentHashMap<>();

    @Transactional
    public Product create(Product product) {
        Product productResult = productDao.create(product);
        redisUtil.set(RedisKeyPrefixConst.PRODUCT_CACHE + productResult.getId(), JSON.toJSONString(productResult),
                genProductCacheTimeout(), TimeUnit.SECONDS);
        return productResult;
    }

    @Transactional
    public Product update(Product product) {
        Product productResult = null;
        //RLock updateProductLock = redisson.getLock(LOCK_PRODUCT_UPDATE_PREFIX + product.getId());
        RReadWriteLock readWriteLock = redisson.getReadWriteLock(LOCK_PRODUCT_UPDATE_PREFIX + product.getId());
        RLock writeLock = readWriteLock.writeLock();
        writeLock.lock();
        try {
            productResult = productDao.update(product);
            redisUtil.set(RedisKeyPrefixConst.PRODUCT_CACHE + productResult.getId(), JSON.toJSONString(productResult),
                    genProductCacheTimeout(), TimeUnit.SECONDS);
            productMap.put(RedisKeyPrefixConst.PRODUCT_CACHE + productResult.getId(), product);
        } finally {
            writeLock.unlock();
        }
        return productResult;
    }

    public Product get(Long productId) throws InterruptedException {
        Product product = null;
        String productCacheKey = RedisKeyPrefixConst.PRODUCT_CACHE + productId;

        product = getProductFromCache(productCacheKey);
        if (product != null) {
            return product;
        }
        //DCL
        RLock hotCacheLock = redisson.getLock(LOCK_PRODUCT_HOT_CACHE_PREFIX + productId);
        hotCacheLock.lock();
        //boolean result = hotCacheLock.tryLock(3, TimeUnit.SECONDS);
        try {
            product = getProductFromCache(productCacheKey);
            if (product != null) {
                return product;
            }

            //RLock updateProductLock = redisson.getLock(LOCK_PRODUCT_UPDATE_PREFIX + productId);
            RReadWriteLock readWriteLock = redisson.getReadWriteLock(LOCK_PRODUCT_UPDATE_PREFIX + productId);
            RLock rLock = readWriteLock.readLock();
            rLock.lock();
            try {
                product = productDao.get(productId);
                if (product != null) {
                    redisUtil.set(productCacheKey, JSON.toJSONString(product),
                            genProductCacheTimeout(), TimeUnit.SECONDS);
                    productMap.put(productCacheKey, product);
                } else {
                    redisUtil.set(productCacheKey, EMPTY_CACHE, genEmptyCacheTimeout(), TimeUnit.SECONDS);
                }
            } finally {
                rLock.unlock();
            }
        } finally {
            hotCacheLock.unlock();
        }
        return product;
    }


    private Integer genProductCacheTimeout() {
        return PRODUCT_CACHE_TIMEOUT + new Random().nextInt(5) * 60 * 60;
    }

    private Integer genEmptyCacheTimeout() {
        return 60 + new Random().nextInt(30);
    }

    private Product getProductFromCache(String productCacheKey) {
        Product product = productMap.get(productCacheKey);
        if (product != null) {
            return product;
        }

        String productStr = redisUtil.get(productCacheKey);
        if (!StringUtils.isEmpty(productStr)) {
            if (EMPTY_CACHE.equals(productStr)) {
                redisUtil.expire(productCacheKey, genEmptyCacheTimeout(), TimeUnit.SECONDS);
                return new Product();
            }
            product = JSON.parseObject(productStr, Product.class);
            redisUtil.expire(productCacheKey, genProductCacheTimeout(), TimeUnit.SECONDS); //读延期
        }
        return product;
    }

}
相关推荐
希忘auto1 小时前
详解Redis的常用命令
redis·1024程序员节
岁月变迁呀7 小时前
Redis梳理
数据库·redis·缓存
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭8 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
工业甲酰苯胺8 小时前
分布式系统架构:服务容错
数据库·架构
Code apprenticeship10 小时前
怎么利用Redis实现延时队列?
数据库·redis·缓存
百度智能云技术站10 小时前
广告投放系统成本降低 70%+,基于 Redis 容量型数据库 PegaDB 的方案设计和业务实践
数据库·redis·oracle
装不满的克莱因瓶10 小时前
【Redis经典面试题六】Redis的持久化机制是怎样的?
java·数据库·redis·持久化·aof·rdb
Java程序之猿10 小时前
微服务分布式(一、项目初始化)
分布式·微服务·架构
fpcc12 小时前
跟我学c++中级篇——C++中的缓存利用
c++·缓存