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;
    }

}
相关推荐
飞翔的佩奇8 小时前
Java项目:基于SSM框架实现的德云社票务管理系统【ssm+B/S架构+源码+数据库+开题报告+毕业论文】
java·数据库·spring·架构·maven·ssm框架·票务系统
cjay_fighting8 小时前
redis7新特性、源码解析
数据库·redis·分布式
鲁鲁51712 小时前
梧桐数据库:存算分离和存算一体架构的分布式数据库技术分析
数据库·分布式·架构·梧桐数据库
舒一笑12 小时前
supervised systemd -bash: supervised: 未找到命令如何解决?
开发语言·redis·bash
Catherines712 小时前
ssrf结合redis未授权getshell
redis·安全
向阳逐梦12 小时前
对回收站里的文件进行操作
算法·程序员·架构
wjq++12 小时前
LRU 缓存机制
java·spring·缓存
躺平攻城狮12 小时前
docker中实现多机redis主从集群
redis·docker·容器
MichaelxcT12 小时前
Redis哨兵和集群模式
linux·运维·redis
飞翔的佩奇13 小时前
Java项目:基于SSM框架实现的智慧城市实验室管理系统分前后台【ssm+B/S架构+源码+数据库+毕业论文】
java·数据库·架构·java-ee·maven·智慧城市·ssm框架