redis缓存优化

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
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
目录
相关文章
|
23天前
|
缓存 监控 前端开发
在资源加载优化中,如何利用浏览器缓存提升性能?
通过以上这些方法,可以有效地利用浏览器缓存来提升资源加载的性能,减少网络请求次数,提高用户体验和应用的响应速度。同时,需要根据具体的应用场景和资源特点进行灵活调整和优化,以达到最佳的效果。此外,随着技术的不断发展和变化,还需要持续关注和学习新的缓存优化方法和策略。
87 53
|
1月前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
1月前
|
存储 缓存 NoSQL
【赵渝强老师】基于Redis的旁路缓存架构
本文介绍了引入缓存后的系统架构,通过缓存可以提升访问性能、降低网络拥堵、减轻服务负载和增强可扩展性。文中提供了相关图片和视频讲解,并讨论了数据库读写分离、分库分表等方法来减轻数据库压力。同时,文章也指出了缓存可能带来的复杂度增加、成本提高和数据一致性问题。
【赵渝强老师】基于Redis的旁路缓存架构
|
26天前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
37 5
|
1月前
|
缓存 NoSQL 中间件
redis高并发缓存中间件总结!
本文档详细介绍了高并发缓存中间件Redis的原理、高级操作及其在电商架构中的应用。通过阿里云的角度,分析了Redis与架构的关系,并展示了无Redis和使用Redis缓存的架构图。文档还涵盖了Redis的基本特性、应用场景、安装部署步骤、配置文件详解、启动和关闭方法、systemctl管理脚本的生成以及日志警告处理等内容。适合初学者和有一定经验的技术人员参考学习。
179 7
|
1月前
|
缓存 监控 测试技术
如何利用浏览器的缓存来优化网站性能?
【10月更文挑战第23天】通过以上多种方法合理利用浏览器缓存,可以显著提高网站的性能,减少网络请求,加快资源加载速度,提升用户的访问体验。同时,要根据网站的具体情况和资源的特点,不断优化和调整缓存策略,以适应不断变化的业务需求和用户访问模式。
88 7
|
存储 缓存 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。
335 0
6.0Spring Boot 2.0实战 Redis 分布式缓存6.0|学习笔记
|
缓存 NoSQL Redis
首页数据显示-添加 redis 缓存(3)| 学习笔记
快速学习 首页数据显示-添加 redis 缓存(3)
154 0
首页数据显示-添加 redis 缓存(3)| 学习笔记
|
缓存 NoSQL Java
首页数据显示-添加 redis 缓存(1) | 学习笔记
快速学习 首页数据显示-添加 redis 缓存(1)
239 0
首页数据显示-添加 redis 缓存(1) | 学习笔记