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

}
相关推荐
架构师沉默1 小时前
我用一个 Postgres 实现一整套后端架构!
java·spring boot·程序人生·架构·tdd
伽蓝_游戏2 小时前
UGUI源码剖析(3):布局的“原子”——RectTransform的核心数据模型与几何学
ui·unity·架构·c#·游戏引擎·游戏程序·几何学
Web极客码2 小时前
如何为WordPress启用LiteSpeed缓存
前端·缓存
会编程的林俊杰3 小时前
Redisson中的分布式锁
redis·分布式·redisson
泯泷3 小时前
Tiptap 深度教程(二):构建你的第一个编辑器
前端·架构·typescript
野犬寒鸦3 小时前
Pipeline功能实现Redis批处理(项目批量查询点赞情况的应用)
java·服务器·数据库·redis·后端·缓存
程序员JerrySUN3 小时前
当前主流GPU全景讲解:架构、功能与应用方向
数据库·人工智能·驱动开发·redis·缓存·架构
小醉你真好4 小时前
12、Docker Compose 安装 Redis
redis·docker·容器
慌ZHANG4 小时前
云原生安全挑战与治理策略:从架构思维到落地实践
大数据·云原生·架构
serve the people6 小时前
系统的缓存(buff/cache)是如何影响系统性能的?
缓存