redis缓存优化

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 采用获取一次缓存,如果为空的情况,获取分布式锁,让一个线程去重建缓存,另外的线程未获取到锁的情况,休眠短时间,然后再自旋获取缓存。

redis缓存优化

分段锁优化


关于一个商品在并发场景防止超卖,通常我们会用到分布式锁。


当我们把库存1000设置进redis,通常我们会对商品id进行加锁,如 lockKey = “product_101_stock”;这样所有的线程都会因为这个锁机制导致扣减库存缓慢。


如果将库存平均拆分成10份,每份100,利用十个lockKey,如product_101_stock_1,product_101_stock_2,…,product_101_stock_10,利用分段锁机制,可以有效提升加锁效率。


缓存击穿(缓存失效)


热点数据在失效的情况下,大量的高并发请求短时间内会绕过缓存,直接打到数据库,造成数据库压力非常大。


或者当热点数据大批量在同一时间都失效了,这时大量的请求都会打到数据库中查询,导致数据库压力过大。


解决方法:


1.热点数据永不过期


2.在设置缓存失效时间的时候,设置一个随机失效时间,这样在同一时间就不会存在大量的key过期。


缓存穿透


缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不会命中,通常出于容错的考虑,如果从存储层查不到数据则不写入缓

存层。


缓存穿透将导致不存在的数据每次请求都要到存储层去査询, 失去了缓存保护后端存储的意义。


造成缓存穿透的基本原因有两个:


第一、自身业务代码或者数据出现问题,


第二、一些恶意攻击.爬虫等造成大量空命中。


缓存穿透的解决方法:


1.对于不存在的数据设置一个短时效的空缓存。


2.利用布隆过滤器过滤不存在的数据请求


关于布隆过滤器


对于恶意攻击,向服务器请求大量不存在的数据造成的缓存穿透,还可以用布隆过滤器先做一次过滤,对于不存在的数据布隆过滤器一般都能够过滤掉,不让请求再往后端发送。当布隆过滤器说某个值存在时,这个值可能不存在;当它说不存在时,那就肯定不存在。


布隆过滤器原理



  1. 布隆过滤器内部是一个bit数组。


  1. 布隆过滤器可能有K个hash算法


  1. 利用K个hash算法对key的值进行hash运算,并对hash值进行取数组长度模操作,最后的index值就是bit数组的索引下标,最后将下标的bit位置1
 index = hash1(key) % bit位长度

 4.如果所有的hash值对应的下标都为1,则判定数据存在。


Redis的bitmap只支持2^32大小,对应到内存也就是512MB,误判率万分之一,可以放下2亿左右的数据,性能高,空间占用率及小,省去了大量无效的数据库连接。

redisson实现布隆过滤器
public class RedissonBloomFilter {

    public static void main(String[] args) {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.32.128:6379");
//        config.useSingleServer().setPassword("");
        // 构造Redisson
        RedissonClient redissonClient = Redisson.create(config);

        // 初始化布隆过滤器:预计元素为100000000L个,误差率为3%
        RBloomFilter<Object> bloomFilter = redissonClient.getBloomFilter("phoneList");
        bloomFilter.tryInit(100000000L, 0.03);

        // 将号码插入到布隆过滤器中
        bloomFilter.add("10086");

        // 判断下面的号码是否在布隆过滤器中
        System.out.println(bloomFilter.contains("10000"));
        System.out.println(bloomFilter.contains("10086"));

    }

}



注意: 布隆过滤器是不能删除数据的,如果要删除数据得重新初始化数据。


缓存雪崩

缓存雪崩指的是缓存层支撑不住或宕掉后,流量会像奔逃的野牛一样,打向后端存储层。


由于缓存层承载着大量请求,有效地保护了存储层,但是如果缓存层由于某些原因不能提供服务(比如超大并发过来,缓存层支撑不住,或者由于缓存设计不好,类似大量请求访问bigkey,导致缓存能支撑的并发急剧下降),于是大量请求都会打到存储层,存储层的调用量会暴增,造成存储层也会级联宕机的情况。


预防和解决缓存雪崩问题, 可以从以下三个方面进行着手。


保证缓存层服务高可用性,比如使用Redis Sentinel或Redis Cluster.


依赖隔离组件为后端限流熔断并降级。比如使用Sentinel或Hystrix限流降级组件。比如服务降级,我们可以针对不同的数据采取不同的处理方式。当业务应用访问的是非核心数据例如电商商品属性,用户信息等)时,暂时停止从缓存中查询这些数据,而是直接返回预定义的默认降级信息、空值或是错误提示信息;当业务应用访问的是核心数据时(例如电商商品库存)时,仍然允许查询缓存,如果缓存缺失,也可以继续通过数据库读取。


提前演练。在项目上线前,演练缓存层宕掉后,应用以及后端的负载情况以及可能出现的问题,在此基础上做一些预案设定。


冷热分离

如果一个电商网站有成千上亿个商品,而商品信息缓存到redis加快访问速度,而不可能所有的商品信息都缓存到redis,这样redis得需要多大才满足。


所以对于热点数据(经常访问的商品)需要常驻到redis,而冷门商品则不必要一直都放在redis中。所以有了冷热分离概念,代码示例如下:



@Service
public class ProductService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private ProductDao productDao;

    private final static String REDIS_KEY_PREFIX_PRODUCT_CACHE = "product:cache:";
    /**
     * 分布式锁key
     */
    private final static String REDIS_KEY_PREFIX_PRODUCT_CACHE_LOCK = "product:cache:lock:";

    private static final Long PRODUCT_CACHE_TIMEOUT = 60 * 60 * 24L;


    public Product create(Product product){
        Product productResult = productDao.create(product);
        //冷热分离::设置超时时间,如果是冷门商品,则过期时间到了之后,redis之中不会有缓存
        ThreadLocalRandom current = ThreadLocalRandom.current();
        //生成一个3600秒以内的随机数,防止同时请求导致缓存击穿
        long random = current.nextLong(60 * 60);
        stringRedisTemplate.opsForValue().set(REDIS_KEY_PREFIX_PRODUCT_CACHE + product.getProductId(), JSON.toJSONString(productResult),
                PRODUCT_CACHE_TIMEOUT + random , TimeUnit.SECONDS);
        return productResult;
    }


    public Product update(Product product){
        Product productResult = productDao.update(product);
        //冷热分离:设置超时时间,如果是冷门商品,则过期时间到了之后,redis之中不会有缓存
        ThreadLocalRandom current = ThreadLocalRandom.current();
        //生成一个3600秒以内的随机数,防止同时请求导致缓存击穿
        long random = current.nextLong(60 * 60);
        stringRedisTemplate.opsForValue().set(REDIS_KEY_PREFIX_PRODUCT_CACHE + product.getProductId(), JSON.toJSONString(productResult),
                PRODUCT_CACHE_TIMEOUT + random, TimeUnit.SECONDS);
        return productResult;
    }


    public Product getProduct(Long productId){
        Product product = null;
        String productKey = REDIS_KEY_PREFIX_PRODUCT_CACHE + productId;

        String productStr = stringRedisTemplate.opsForValue().get(productKey);

        if(!StringUtil.isBlank(productStr)){
            product = JSON.parseObject(productStr, Product.class);
            //冷热分离:读延期,热门数据永不过期,
            stringRedisTemplate.expire(productKey, PRODUCT_CACHE_TIMEOUT, TimeUnit.SECONDS);
        }

        product = productDao.get(productId);

        if(product != null){
            ThreadLocalRandom current = ThreadLocalRandom.current();
            //生成一个3600秒以内的随机数,防止同时请求导致缓存击穿
            long random = current.nextLong(60 * 60);
            stringRedisTemplate.opsForValue().set(productKey, JSON.toJSONString(product),
                    PRODUCT_CACHE_TIMEOUT + random, TimeUnit.SECONDS);
        }else{
            //设置一个短时效空缓存,防止缓存穿透
            stringRedisTemplate.opsForValue().set(productKey,"",5*60,TimeUnit.SECONDS);
        }

        return product;

    }

}

突发性热点缓存重建导致系统压力暴增


类似于缓存击穿,某个热点数据或者冷门数据突然被大量请求。这是缓存服务器中是没有该数据缓存,导致数据库压力过大。

利用双重检测锁(double check lock dcl)机制,代码如下:


synchronized版本
public Product getProduct(Long productId){
        Product product = null;
        String productKey = REDIS_KEY_PREFIX_PRODUCT_CACHE + productId;

        String productStr = stringRedisTemplate.opsForValue().get(productKey);

        if(!StringUtil.isBlank(productStr)){
            product = JSON.parseObject(productStr, Product.class);
            //读延期,热门数据永不过期,
            stringRedisTemplate.expire(productKey, PRODUCT_CACHE_TIMEOUT, TimeUnit.SECONDS);
            
            return product;
        }

        //双重检测锁机制,当缓存中不存在商品信息时,利用管程锁,单台jvm下,只有一个线程可以进去重建数据。(每台应用服务器都会去重建一次)
      //当数据重建完成后,缓存中有数据,其他线程可以在缓存中获取到数据信息。
      //这里为什么不用分布式锁加自旋的方式,一直在redis缓存中获取数据呢?
      //这里this肯定不行,因为冷门数据如果是101,如果锁this,则102商品本来是正常都会被锁住。
        synchronized (this){

            productStr = stringRedisTemplate.opsForValue().get(productKey);
            if(!StringUtil.isBlank(productStr)){
                product = JSON.parseObject(productStr, Product.class);
                //读延期,热门数据永不过期,
                stringRedisTemplate.expire(productKey, PRODUCT_CACHE_TIMEOUT, TimeUnit.SECONDS);
                return product;
            }

            product = productDao.get(productId);

            if(product != null){
                ThreadLocalRandom current = ThreadLocalRandom.current();
                //生成一个3600秒以内的随机数,防止同时请求导致缓存击穿
                long random = current.nextLong(60 * 60);
                stringRedisTemplate.opsForValue().set(productKey, JSON.toJSONString(product),
                        PRODUCT_CACHE_TIMEOUT + random, TimeUnit.SECONDS);
            }else{
                //设置一个空缓存,防止缓存穿透
                stringRedisTemplate.opsForValue().set(productKey,"",5*60,TimeUnit.SECONDS);
            }
        }

        return product;

    }
redisson版本
public Product getProductRedisson(Long productId){
        Product product = null;
        String productKey = REDIS_KEY_PREFIX_PRODUCT_CACHE + productId;

        String productStr = stringRedisTemplate.opsForValue().get(productKey);

        if(!StringUtil.isBlank(productStr)){
            product = JSON.parseObject(productStr, Product.class);
            //读延期,热门数据永不过期,
            stringRedisTemplate.expire(productKey, PRODUCT_CACHE_TIMEOUT, TimeUnit.SECONDS);

            return product;
        }

        //利用redisson的分布式锁实现热点数据重建
        RLock hotCreateCacheLock = redisson.getLock(REDIS_KEY_PREFIX_PRODUCT_CACHE_LOCK + productId);

        hotCreateCacheLock.lock();
        try{
            productStr = stringRedisTemplate.opsForValue().get(productKey);
            if(!StringUtil.isBlank(productStr)){
                product = JSON.parseObject(productStr, Product.class);
                //读延期,热门数据永不过期,
                stringRedisTemplate.expire(productKey, PRODUCT_CACHE_TIMEOUT, TimeUnit.SECONDS);
                return product;
            }

            //重建缓存逻辑
            product = productDao.get(productId);

            if(product != null){
                ThreadLocalRandom current = ThreadLocalRandom.current();
                //生成一个3600秒以内的随机数,防止同时请求导致缓存击穿
                long random = current.nextLong(60 * 60);
                stringRedisTemplate.opsForValue().set(productKey, JSON.toJSONString(product),
                        PRODUCT_CACHE_TIMEOUT + random, TimeUnit.SECONDS);
            }else{
                //设置一个空缓存,防止缓存穿透
                stringRedisTemplate.opsForValue().set(productKey,"",5*60,TimeUnit.SECONDS);
            }
            return product;
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }finally {
            hotCreateCacheLock.unlock();
        }

    }

双写一致性


关于双写不一致性



线程2在线程1查询商品信息后,修改了商品信息,并在某些情况下,线程2先修改了缓存中的信息,然后线程1再次更新缓存。这样就导致了数据库中的商品信息和缓存中的商品信息不一致。


解决方法 : 利用分布式锁解决双写不一致。


解决思路,将查询商品信息和更新缓存绑定到锁中,这样无论哪个线程在前,数据库中的信息和缓存中的信息都是一致的。


代码示例:


@Service
public class ProductService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private ProductDao productDao;

    @Resource
    private Redisson redisson;

    private final static String REDIS_KEY_PREFIX_PRODUCT_CACHE = "product:cache:";
    private final static String REDIS_KEY_PREFIX_PRODUCT_CACHE_LOCK = "product:cache:lock:";
    private final static String REDIS_KEY_PREFIX_PRODUCT_UPDATE_CACHE_LOCK = "product:cache:update:lock:";

    private static final Long PRODUCT_CACHE_TIMEOUT = 60 * 60 * 24L;


    public Product create(Product product){
        Product productResult = productDao.create(product);
        //设置超时时间,如果是冷门商品,则过期时间到了之后,redis之中不会有缓存
        ThreadLocalRandom current = ThreadLocalRandom.current();
        //生成一个3600秒以内的随机数,防止同时请求导致缓存击穿
        long random = current.nextLong(60 * 60);
        stringRedisTemplate.opsForValue().set(REDIS_KEY_PREFIX_PRODUCT_CACHE + product.getProductId(), JSON.toJSONString(productResult),
                PRODUCT_CACHE_TIMEOUT + random , TimeUnit.SECONDS);
        return productResult;
    }


    public Product update(Product product){
        RLock updateCacheLock = redisson.getLock(REDIS_KEY_PREFIX_PRODUCT_UPDATE_CACHE_LOCK + product.getProductId());
        //将查询数据库和更新到缓存利用分布式锁锁起来,防止双写不一致性
        updateCacheLock.lock();
        try{
            Product productResult = productDao.update(product);
            //设置超时时间,如果是冷门商品,则过期时间到了之后,redis之中不会有缓存
            ThreadLocalRandom current = ThreadLocalRandom.current();
            //生成一个3600秒以内的随机数,防止同时请求导致缓存击穿
            long random = current.nextLong(60 * 60);
            stringRedisTemplate.opsForValue().set(REDIS_KEY_PREFIX_PRODUCT_CACHE + product.getProductId(), JSON.toJSONString(productResult),
                    PRODUCT_CACHE_TIMEOUT + random, TimeUnit.SECONDS);
            return productResult;
        }finally {
            updateCacheLock.unlock();
        }
    }


    public Product getProduct(Long productId){
        Product product = null;
        String productKey = REDIS_KEY_PREFIX_PRODUCT_CACHE + productId;

        String productStr = stringRedisTemplate.opsForValue().get(productKey);

        if(!StringUtil.isBlank(productStr)){
            product = JSON.parseObject(productStr, Product.class);
            //读延期,热门数据永不过期,
            stringRedisTemplate.expire(productKey, PRODUCT_CACHE_TIMEOUT, TimeUnit.SECONDS);

            return product;
        }

        //双重检测锁机制,当缓存中不存在商品信息时,利用管程锁,单台jvm下,只有一个线程可以进去重建数据。(每台应用服务器都会去重建一次)
        //当数据重建完成后,缓存中有数据,其他线程可以在缓存中获取到数据信息。
        //这里为什么不用分布式锁加自旋的方式,一直在redis缓存中获取数据呢?
        //这里this肯定不行,因为冷门数据如果是101,如果锁this,则102商品本来是正常都会被锁住。
        synchronized (this){

            productStr = stringRedisTemplate.opsForValue().get(productKey);

            if(!StringUtil.isBlank(productStr)){
                product = JSON.parseObject(productStr, Product.class);
                //读延期,热门数据永不过期,
                stringRedisTemplate.expire(productKey, PRODUCT_CACHE_TIMEOUT, TimeUnit.SECONDS);
                return product;
            }

            product = productDao.get(productId);

            if(product != null){
                ThreadLocalRandom current = ThreadLocalRandom.current();
                //生成一个3600秒以内的随机数,防止同时请求导致缓存击穿
                long random = current.nextLong(60 * 60);
                stringRedisTemplate.opsForValue().set(productKey, JSON.toJSONString(product),
                        PRODUCT_CACHE_TIMEOUT + random, TimeUnit.SECONDS);
            }else{
                //设置一个空缓存,防止缓存穿透
                stringRedisTemplate.opsForValue().set(productKey,"",5*60,TimeUnit.SECONDS);
            }
        }

        return product;

    }


    public Product getProductRedisson(Long productId){
        Product product = null;
        String productKey = REDIS_KEY_PREFIX_PRODUCT_CACHE + productId;

        String productStr = stringRedisTemplate.opsForValue().get(productKey);

        if(!StringUtil.isBlank(productStr)){
            product = JSON.parseObject(productStr, Product.class);
            //读延期,热门数据永不过期,
            stringRedisTemplate.expire(productKey, PRODUCT_CACHE_TIMEOUT, TimeUnit.SECONDS);

            return product;
        }

        //利用redisson的分布式锁实现热点数据重建
        RLock hotCreateCacheLock = redisson.getLock(REDIS_KEY_PREFIX_PRODUCT_CACHE_LOCK + productId);

        hotCreateCacheLock.lock();
        try{
            productStr = stringRedisTemplate.opsForValue().get(productKey);
            if(!StringUtil.isBlank(productStr)){
                product = JSON.parseObject(productStr, Product.class);
                //读延期,热门数据永不过期,
                stringRedisTemplate.expire(productKey, PRODUCT_CACHE_TIMEOUT, TimeUnit.SECONDS);
                return product;
            }

            //将查询数据库和更新到缓存利用分布式锁锁起来,防止双写不一致性
            RLock updateCacheLock = redisson.getLock(REDIS_KEY_PREFIX_PRODUCT_UPDATE_CACHE_LOCK + productId);

            updateCacheLock.lock();
            try{
                product = productDao.get(productId);

                if(product != null){
                    ThreadLocalRandom current = ThreadLocalRandom.current();
                    //生成一个3600秒以内的随机数,防止同时请求导致缓存击穿
                    long random = current.nextLong(60 * 60);
                    stringRedisTemplate.opsForValue().set(productKey, JSON.toJSONString(product),
                            PRODUCT_CACHE_TIMEOUT + random, TimeUnit.SECONDS);
                }else{
                    //设置一个空缓存,防止缓存穿透
                    stringRedisTemplate.opsForValue().set(productKey,"",5*60,TimeUnit.SECONDS);
                }
                return product;
            }finally {
                updateCacheLock.unlock();
            }

        }catch (Exception e){
            e.printStackTrace();
            return null;
        }finally {
            hotCreateCacheLock.unlock();
        }



    }

}

双写不一致分布式锁优化(读写锁)

如果大量请求都是读操作,则可以利用redisson的读写锁来优化


关于并发问题, 读-读之间是没有并发问题,写-写,有并发问题,读-写 ,有并发问题


读写锁原理,


1.线程1读的时候,线程2读,不互斥


2.线程1写的时候,线程2读,互斥,必须要等到线程1写完成后,线程2才能获取到锁


3.线程1写的时候,线程2写,互斥,必须要等到线程1写完成后,线程2才能获取到写锁。


线程1 线程2 互斥情况
读锁 读锁 不互斥,可以共同获取到锁(锁可重入)
读锁 写锁 互斥,线程2必须等到线程1释放读锁后才能加写锁。
写锁 读锁 互斥,线程2必须等到线程1释放写锁后才能加读锁。
写锁 写锁 写写互斥,必须串行化


代码优化:


  public Product update(Product product){
//        RLock updateCacheLock = redisson.getLock(REDIS_KEY_PREFIX_PRODUCT_UPDATE_CACHE_LOCK + product.getProductId());
        //将查询数据库和更新到缓存利用分布式锁锁起来,防止双写不一致性
//        updateCacheLock.lock();
        RReadWriteLock updateCacheLock = redisson.getReadWriteLock(REDIS_KEY_PREFIX_PRODUCT_UPDATE_CACHE_LOCK + product.getProductId());
        RLock writeLock = updateCacheLock.writeLock();
        writeLock.lock();
        try{
            Product productResult = productDao.update(product);
            //设置超时时间,如果是冷门商品,则过期时间到了之后,redis之中不会有缓存
            ThreadLocalRandom current = ThreadLocalRandom.current();
            //生成一个3600秒以内的随机数,防止同时请求导致缓存击穿
            long random = current.nextLong(60 * 60);
            stringRedisTemplate.opsForValue().set(REDIS_KEY_PREFIX_PRODUCT_CACHE + product.getProductId(), JSON.toJSONString(productResult),
                    PRODUCT_CACHE_TIMEOUT + random, TimeUnit.SECONDS);
            return productResult;
        }finally {
//            updateCacheLock.unlock();
            writeLock.unlock();
        }
    }
    



    public Product getProductRedisson(Long productId){
        Product product = null;
        String productKey = REDIS_KEY_PREFIX_PRODUCT_CACHE + productId;

        String productStr = stringRedisTemplate.opsForValue().get(productKey);

        if(!StringUtil.isBlank(productStr)){
            product = JSON.parseObject(productStr, Product.class);
            //读延期,热门数据永不过期,
            stringRedisTemplate.expire(productKey, PRODUCT_CACHE_TIMEOUT, TimeUnit.SECONDS);

            return product;
        }

        //利用redisson的分布式锁实现热点数据重建
        RLock hotCreateCacheLock = redisson.getLock(REDIS_KEY_PREFIX_PRODUCT_CACHE_LOCK + productId);

        hotCreateCacheLock.lock();
        try{
            productStr = stringRedisTemplate.opsForValue().get(productKey);
            if(!StringUtil.isBlank(productStr)){
                product = JSON.parseObject(productStr, Product.class);
                //读延期,热门数据永不过期,
                stringRedisTemplate.expire(productKey, PRODUCT_CACHE_TIMEOUT, TimeUnit.SECONDS);
                return product;
            }

            //将查询数据库和更新到缓存利用分布式锁锁起来,防止双写不一致性
//            RLock updateCacheLock = redisson.getLock(REDIS_KEY_PREFIX_PRODUCT_UPDATE_CACHE_LOCK + productId);

//            updateCacheLock.lock();
            RReadWriteLock updateCacheLock = redisson.getReadWriteLock(REDIS_KEY_PREFIX_PRODUCT_UPDATE_CACHE_LOCK + productId);
            RLock readLock = updateCacheLock.readLock();
            //读锁。其他线程在没有写锁的情况,这里的锁是并行执行的,意味着,读锁在写锁没有锁住的情况下,可以并行执行
            //当有其他线程在执行update方法时,由于写锁的加入,这里必须等到写锁释放后,才能获取到读锁
            readLock.lock();
            try{
                product = productDao.get(productId);

                if(product != null){
                    ThreadLocalRandom current = ThreadLocalRandom.current();
                    //生成一个3600秒以内的随机数,防止同时请求导致缓存击穿
                    long random = current.nextLong(60 * 60);
                    stringRedisTemplate.opsForValue().set(productKey, JSON.toJSONString(product),
                            PRODUCT_CACHE_TIMEOUT + random, TimeUnit.SECONDS);
                }else{
                    //设置一个空缓存,防止缓存穿透
                    stringRedisTemplate.opsForValue().set(productKey,"",5*60,TimeUnit.SECONDS);
                }
                return product;
            }finally {
//                updateCacheLock.unlock();
                readLock.unlock();
            }

        }catch (Exception e){
            e.printStackTrace();
            return null;
        }finally {
            hotCreateCacheLock.unlock();
        }

    }


关于双写不一致的其他解决方案


对于并发几率很小的数据(如个人维度的订单数据,用户数据等),这种几乎不用考虑双写一致性,很少发生缓存不一致,可以给缓存数据加上过期时间,每隔一段时间触发读的主动更新即可。


就算并发很高,如果业务上能容忍短时间的缓存数据不一致(如商品名称,商品分类菜单等),缓存加上过期时间依然可以解决大部分业务对缓存的要求。

也可以用阿里开源的canal通过监听数据库的binlog日志及时的去修改缓存,但是引入了新的中间件,增加了系统的复杂度。


双写一致总结


针对读多写少的情况加入缓存以提高性能,如果写多读多的情况又不能容忍双写不一致,那就没必要加缓存了,可以直接操作数据库。放入缓存的数据应该是实时性,一致性要求不是很高的数据。切记不要为了用缓存,同时又要保证绝对的一致性做大量的过度设计和控制,增加系统复杂度。


对于热点缓存初始化的逻辑


采用获取一次缓存,如果为空的情况,获取分布式锁,让一个线程去重建缓存,另外的线程未获取到锁的情况,休眠短时间,然后再自旋获取缓存。


伪代码逻辑


public String get(String key){
  String value = redis.get(key);
  if(value == null){
    //获取锁操作
    String lockKey = "lock:key";
    if(redis.set(lockKey,"1","ex 180","nx")){
            value = db.get(key);
          redis.setex(key,value,timeout);
          //删除锁操作
          redis.delete(lockKey);
        }else{
            Thread.sleep(50);
            //自旋
            return get(key);
        } 
  } 
    return value;
}
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
9天前
|
缓存 NoSQL 中间件
Redis,分布式缓存演化之路
本文介绍了基于Redis的分布式缓存演化,探讨了分布式锁和缓存一致性问题及其解决方案。首先分析了本地缓存和分布式缓存的区别与优劣,接着深入讲解了分布式远程缓存带来的并发、缓存失效(穿透、雪崩、击穿)等问题及应对策略。文章还详细描述了如何使用Redis实现分布式锁,确保高并发场景下的数据一致性和系统稳定性。最后,通过双写模式和失效模式讨论了缓存一致性问题,并提出了多种解决方案,如引入Canal中间件等。希望这些内容能为读者在设计分布式缓存系统时提供有价值的参考。感谢您的阅读!
Redis,分布式缓存演化之路
|
28天前
|
缓存 NoSQL JavaScript
Vue.js应用结合Redis数据库:实践与优化
将Vue.js应用与Redis结合,可以实现高效的数据管理和快速响应的用户体验。通过合理的实践步骤和优化策略,可以充分发挥两者的优势,提高应用的性能和可靠性。希望本文能为您在实际开发中提供有价值的参考。
55 11
|
1月前
|
存储 监控 NoSQL
NoSQL与Redis配置与优化
通过合理配置和优化Redis,可以显著提高其性能和可靠性。选择合适的数据结构、优化内存使用、合理设置持久化策略、使用Pipeline批量执行命令、以及采用分布式集群方案,都是提升Redis性能的重要手段。同时,定期监控和维护Redis实例,及时调整配置,能够确保系统的稳定运行。希望本文对您在Redis的配置与优化方面有所帮助。
70 23
|
2月前
|
存储 缓存 自然语言处理
SCOPE:面向大语言模型长序列生成的双阶段KV缓存优化框架
KV缓存是大语言模型(LLM)处理长文本的关键性能瓶颈,现有研究多聚焦于预填充阶段优化,忽视了解码阶段的重要性。本文提出SCOPE框架,通过分离预填充与解码阶段的KV缓存策略,实现高效管理。SCOPE保留预填充阶段的关键信息,并在解码阶段引入滑动窗口等策略,确保重要特征的有效选取。实验表明,SCOPE仅用35%原始内存即可达到接近完整缓存的性能水平,显著提升了长文本生成任务的效率和准确性。
172 3
SCOPE:面向大语言模型长序列生成的双阶段KV缓存优化框架
|
1月前
|
存储 缓存 NoSQL
云端问道21期方案教学-应对高并发,利用云数据库 Tair(兼容 Redis®*)缓存实现极速响应
云端问道21期方案教学-应对高并发,利用云数据库 Tair(兼容 Redis®*)缓存实现极速响应
|
1月前
|
缓存 NoSQL 关系型数据库
云端问道21期实操教学-应对高并发,利用云数据库 Tair(兼容 Redis®)缓存实现极速响应
本文介绍了如何通过云端问道21期实操教学,利用云数据库 Tair(兼容 Redis®)缓存实现高并发场景下的极速响应。主要内容分为四部分:方案概览、部署准备、一键部署和完成及清理。方案概览中,展示了如何使用 Redis 提升业务性能,降低响应时间;部署准备介绍了账号注册与充值步骤;一键部署详细讲解了创建 ECS、RDS 和 Redis 实例的过程;最后,通过对比测试验证了 Redis 缓存的有效性,并指导用户清理资源以避免额外费用。
|
1月前
|
存储 监控 NoSQL
NoSQL与Redis配置与优化
通过合理配置和优化Redis,可以显著提高其性能和可靠性。选择合适的数据结构、优化内存使用、合理设置持久化策略、使用Pipeline批量执行命令、以及采用分布式集群方案,都是提升Redis性能的重要手段。
49 7
|
2月前
|
缓存 监控 NoSQL
Redis经典问题:缓存穿透
本文详细探讨了分布式系统和缓存应用中的经典问题——缓存穿透。缓存穿透是指用户请求的数据在缓存和数据库中都不存在,导致大量请求直接落到数据库上,可能引发数据库崩溃或性能下降。文章介绍了几种有效的解决方案,包括接口层增加校验、缓存空值、使用布隆过滤器、优化数据库查询以及加强监控报警机制。通过这些方法,可以有效缓解缓存穿透对系统的影响,提升系统的稳定性和性能。
|
存储 缓存 NoSQL
Spring Boot2.5 实战 MongoDB 与高并发 Redis 缓存|学习笔记
快速学习 Spring Boot2.5 实战 MongoDB 与高并发 Redis 缓存
Spring Boot2.5 实战 MongoDB 与高并发 Redis 缓存|学习笔记
|
缓存 NoSQL 安全
6.0Spring Boot 2.0实战 Redis 分布式缓存6.0|学习笔记
快速学习6.0Spring Boot 2.0实战 Redis 分布式缓存6.0。
360 0
6.0Spring Boot 2.0实战 Redis 分布式缓存6.0|学习笔记