spring-cache 雪崩

原创 2016年08月29日 18:38:01

spring-cache 基本原理是利用拦截器,先尝试读取缓存,未命中缓存,先读库在写入缓存,经过查看源码如果在并发量大的时候容易造成“雪崩”。原因是在更新缓存逻辑中没有做并发更新的处理。

原始代码

private Object execute(CacheOperationInvoker invoker, CacheOperationContexts contexts) {
        // Process any early evictions
        processCacheEvicts(contexts.get(CacheEvictOperation.class), true, ExpressionEvaluator.NO_RESULT);

        // Check if we have a cached item matching the conditions
        Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));

        // Collect puts from any @Cacheable miss, if no cached item is found
        List<CachePutRequest> cachePutRequests = new LinkedList<CachePutRequest>();
        if (cacheHit == null) {
            collectPutRequests(contexts.get(CacheableOperation.class), ExpressionEvaluator.NO_RESULT, cachePutRequests);
        }

        Cache.ValueWrapper result = null;

        // If there are no put requests, just use the cache hit
        if (cachePutRequests.isEmpty() && !hasCachePut(contexts)) {
            result = cacheHit;
        }

        // Invoke the method if don't have a cache hit
        if (result == null) {
            result = new SimpleValueWrapper(invokeOperation(invoker));
        }

        // Collect any explicit @CachePuts
        collectPutRequests(contexts.get(CachePutOperation.class), result.get(), cachePutRequests);

        // Process any collected put requests, either from @CachePut or a @Cacheable miss
        for (CachePutRequest cachePutRequest : cachePutRequests) {
            cachePutRequest.apply(result.get());
        }

        // Process any late evictions
        processCacheEvicts(contexts.get(CacheEvictOperation.class), false, result.get());

        return result.get();
    }

经过分析代码会发现在这里并没有任何锁来处理并发更新

// Invoke the method if don't have a cache hit
        if (result == null) {
            result = new SimpleValueWrapper(invokeOperation(invoker));
        }

测试代码

@Cacheable
public List<Regions> queryAllCity() {
    try {
        Thread.sleep(1000 * 30);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    log.info("getAllCity");
    return regionsMapper.getAllCity();
}

这里用jmeter简单的测试了一下(200个并发),效果如下

这里写图片描述

这里写图片描述

从日志中可以看到在并发更新缓存时,200次一次都没有命中,而是执行了200次读取数据方法(这里只是简单的用sleep模拟了读取数据的长时间操作)

改造

期望结果:并发更新缓存时,只会读取一次数据库,其他的请求都会命中缓存

改造后的代码样例:

Lock lock = new ReentrantLock();
    private Object execute(CacheOperationInvoker invoker, CacheOperationContexts contexts) {
        // Process any early evictions
        processCacheEvicts(contexts.get(CacheEvictOperation.class), true, ExpressionEvaluator.NO_RESULT);


        // Check if we have a cached item matching the conditions
        Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));

        // Collect puts from any @Cacheable miss, if no cached item is found
        List<CachePutRequest> cachePutRequests = new LinkedList<CachePutRequest>();
        if (cacheHit == null) {
            collectPutRequests(contexts.get(CacheableOperation.class), ExpressionEvaluator.NO_RESULT, cachePutRequests);
        }

        Cache.ValueWrapper result = null;

        // If there are no put requests, just use the cache hit
        if (cachePutRequests.isEmpty() && !hasCachePut(contexts)) {
            result = cacheHit;
        }

        // Invoke the method if don't have a cache hit
        if (result != null) {
            return result.get();
        }
        lock.lock();
        try{
            logger.info(" get lock");
            result = findCachedItem(contexts.get(CacheableOperation.class));
            if (result == null) {
                result = new SimpleValueWrapper(invokeOperation(invoker));
            } else {
                logger.info("hit cache");
            }

            // Collect any explicit @CachePuts
            collectPutRequests(contexts.get(CachePutOperation.class), result.get(), cachePutRequests);

            // Process any collected put requests, either from @CachePut or a @Cacheable miss
            for (CachePutRequest cachePutRequest : cachePutRequests) {
                cachePutRequest.apply(result.get());
            }

            // Process any late evictions
            processCacheEvicts(contexts.get(CacheEvictOperation.class), false, result.get());
            return result.get();
        } finally {
            lock.unlock();
        }

    }

相同的测试200个并发

这里写图片描述

这里写图片描述

这里写图片描述

从日志可以看到获取锁(get lock)一共200次,但是实际读取数据(getAllCity)只有1次,命中(hit cache)缓存199次,这正是我们所希望的结果

这里只是使用了Lock锁,如果要处理多实例的命中缓存,就需要一个分布式锁,可以用redis实现setIfAbsent。当然也可以引入锁的超时机制。这里有个简单基于redis的分布式锁: spring-distributelock

如果你的应用的实例只有十几个,其实只要保证每个实例只有1个线程在更新缓存就可以了(ReentrantLock),还免去了分布式的复杂和网络通信时间。

版权声明:本文为博主原创文章,未经博主允许不得转载。

spring redis cache使用思考

spring redis cache使用思考
  • zhaozhenzuo
  • zhaozhenzuo
  • 2015年06月17日 17:02
  • 2679

Spring Boot缓存实战 Redis 设置有效时间和自动刷新缓存,时间支持在配置文件中配置

问题描述 Spring Cache提供的@Cacheable注解不支持配置过期时间,还有缓存的自动刷新。 我们可以通过配置CacheManneg来配置默认的过期时间和针对每个缓存容器(value)单...
  • xiaolyuh123
  • xiaolyuh123
  • 2017年11月23日 23:36
  • 1206

Spring Cache之Ehcache和Memcached

spring框架从3.1版本开始提供了缓存支持:在spring-context.jar里的org.springframework.cache包,以及spring-context-support.jar...
  • m0_37929841
  • m0_37929841
  • 2017年03月30日 11:26
  • 515

从头认识Spring Cache

概述: Spring 3.1 引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如EHCache 或者 OSCache),而是一个对缓存...
  • u011659172
  • u011659172
  • 2016年03月10日 11:50
  • 1154

缓存穿透与缓存雪崩

缓存系统不得不考虑的另一个问题是缓存穿透与失效时的雪崩效应。缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个存在的数...
  • cxzhq2002
  • cxzhq2002
  • 2015年06月19日 18:59
  • 1473

缓存穿透,缓存击穿,缓存雪崩解决方案分析

前言 设计一个缓存系统,不得不要考虑的问题就是:缓存穿透、缓存击穿与失效时的雪崩效应。 缓存穿透 缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不...
  • zeb_perfect
  • zeb_perfect
  • 2017年01月06日 11:12
  • 11301

学习笔记:cache 和spring cache 技术---本地缓存-分布式缓存,缓存穿透,雪崩,和热点key的问题

title: 学习笔记:cache 和spring cache 技术—本地缓存-分布式缓存,缓存穿透,雪崩,和热点key的问题 author: Eric liu tags: [] categor...
  • ly_dut0627
  • ly_dut0627
  • 2017年12月08日 00:40
  • 73

Spring cache

  • 2016年04月05日 10:52
  • 140KB
  • 下载

SSM与memcached整合项目Spring Cache

  • 2016年10月31日 17:23
  • 29.06MB
  • 下载

spring-modules-cache

  • 2009年11月13日 15:47
  • 170KB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:spring-cache 雪崩
举报原因:
原因补充:

(最多只允许输入30个字)