Cache雪崩效应

大概半年前,Guang.com曾发生一次由于首页部分cache失效,导致网站故障。

故障分析:

当时逛正在做推广,流量突然暴增,QPS达到5000+,当首页部分cache失效时,需要查询DB, 但由于这部分业务逻辑很复杂导致这SQL包含多表join、groupby、orderby等,执行需要1s,产生的大量临时表,in-memory都装不下,变成on-disk的临时表,但当时放临时表的disk分区容量只有20G,很快disk也爆了,结果显然网站打不开了。

总结为几点:

1、SQL语句优化不足

2、MYSQL tmp_table_size 配置太小

3、disk分区不合理/tmpdir路径配置不合理

4、部门间沟通不足,大型推广前没事先打招呼。

临时解决措施:

由于当时持续大量用户访问,查询DB一直hang住,导致cache一直无法set回去,首页那cache一直处于miss状态,恶性循环,雪崩了。

当时我们立马采取以下措施:

1、调整MYSQL tmp_table_size, 关于tmp_table_size 请看下面详细描述。

2、修改MYSQL临时表保存路径(tmpdir)到较大分区

3、简化业务逻辑,修改SQL,重新部署。

临时表使用内存(tmp_table_size):当我们进行一些特殊操作如需要使用临时表才能完成的join,Order By,Group By 等等,MySQL 可能需要使用到临时表。当我们的临时表较小(小于 tmp_table_size 参数所设置的大小)的时候,MySQL 会将临时表创建成内存临时表,只有当 tmp_table_size 所设置的大小无法装下整个临时表的时候,MySQL 才会将该表创建成 MyISAM 存储引擎的表存放在磁盘上。不过,当另一个系统参数 max_heap_table_size 的大小还小于 tmp_table_size 的时候,MySQL 将使用 max_heap_table_size 参数所设置大小作为最大的内存临时表大小,而忽略 tmp_table_size 所设置的值。而且 tmp_table_size 参数从 MySQL 5.1.2 才开始有,之前一直使用 max_heap_table_size.

长期解决措施:终于到本文的重点Cache Reload机制设计和实现

在讲Cache Reload机制设计和实现之前,先看看cache更新方式:

1、是缓存time out,让缓存失效,重查。(被动更新)

2、是由后端通知更新,一量后端发生变化,通知前端更新。(主动更新)

前者适合实时性不高,但更新频繁的;后者适合实时性要求高,更新不太频繁的应用。

Cache Reload mechanism 设计:

根据逛当时业务需求,选择被动更新方式,但这种方式的弊端是当cache失效那个点,刚好遇上高并发的话,就会发生上述的雪崩情况。

所以我在想这种使用率高的cache,就不用设置time out或time out设置足够大,然后按业务需求时间间隔定期reload/refresh cache data from DB,这cache就不会出现失效情况,也不出现雪崩现象。

下图是guang.com 关于Cache Reload的一小部分架构:


主要2个step:

1、将有需要reload cache 的wrapper保存到redis Hash.

2、部署在Daemon server上的CacheReloadJob,每分钟去redis拿需要reload的cache的hashmap,判断是否到时间refresh cache,如果到,通过Reflection call relevant method 重新reload data和reset 这个cache。

Cache Reload mechanism 实现:

set memcached with reload mechanism if necessary:

/**
 * <h2>set cache with reload mechanism </h2>
 * <h3>Example:</h3>
 * <p>
 * MethodInvocationWrapper wrapper = new MethodInvocationWrapper();<br>
 * wrapper.setMethodName("getProductList");<br>
 * wrapper.setObjectName("productService");<br>
 * wrapper.setArgs(new Object[] { null,0,1 });<br>
 * wrapper.setParameterTypes(new Class[] { Product.class,int.class,int.class});
 * </p>
 *
 * <h3>NOTE:</h3>
 * Make sure the Args have been Serializable and the service has been marked the name, like "@Service("productService")"
 *
 * @param key
 * @param expiredTime 过期时间,如果reloadable=true, 此时间建议为 24*60*60 一天.
 * @param value
 * @param reloadable 是否reload
 * @param durationTime reload 时间间距,单位 ms
 * @param wrapper
 * @return
 * @author Kenny Qi
 */
public boolean set(String key, int expiredTime, Object value,boolean reloadable, long durationTime, MethodInvocationWrapper wrapper) {
    if(reloadable){
        wrapper.setWriteTime(System.currentTimeMillis());
        wrapper.setDuration(durationTime);
        wrapper.setKey(key);
        wrapper.setExpiredTime(expiredTime);
        objectHashOperations.put(RedisKeyEnum.CACHE_RELOAD.getKey(), key, wrapper);
    }

    if(value==null) return false;
    try {
        return memcachedClient.set(key, expiredTime, value);
    } catch (Exception e) {
        logger.warn(e.getMessage(), e);
        return false;
    }
}

CacheReloadJob:

public class CacheReloadJob {

    private static Logger logger = LoggerFactory.getLogger(CacheReloadJob.class);
    @Autowired
    MyXMemcachedClient myXMemcachedClient;

    @Resource(name="objectHashOperations")
    private HashOperations<String, String, MethodInvocationWrapper> objectHashOperations;

    public void reloadCache(){
        logger.info("Try to reload cache");
        Map<String, MethodInvocationWrapper>  map = objectHashOperations.entries(RedisKeyEnum.CACHE_RELOAD.getKey());
        ThreadFactory tf = new NamedThreadFactory("CACHE_RELOAD_THREADPOOL");
        ExecutorService threadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), tf);
        for (String key: map.keySet()) {
            final MethodInvocationWrapper wrapper = map.get(key);
            if(wrapper.getWriteTime()+wrapper.getDuration()>System.currentTimeMillis()){//刷新时间大于当前时间
                threadPool.execute(new Runnable() {
                    @Override
                    public void run() {
                        refreshCache(wrapper);
                    }
                });
            }
        }

        logger.info("completed with reloaded cache");
    }

    private void refreshCache(MethodInvocationWrapper wrapper){
        Object object = ReflectionUtils.invokeMethod(SpringContextHolder.getBean(wrapper.getObjectName()), wrapper.getMethodName(), wrapper.getParameterTypes(), wrapper.getArgs());
        myXMemcachedClient.set(wrapper.getKey(), wrapper.getExpiredTime(), object);
        wrapper.setWriteTime(System.currentTimeMillis());
        objectHashOperations.put(RedisKeyEnum.CACHE_RELOAD.getKey(), wrapper.getKey(), wrapper);
    }

}

Redis 存储结构

redis> HSET cache:reload:memcached <memcache_key> <MethodInvocationWrapper>
OK
redis> HGETALL cache:reload:memcached

后记

如果要做的更人性化点,后续可以在网站后台管理系统 增加cache reloadable 的管理工具(删除、修改刷新间隔等)。

转载自:http://kenny7.com/2012/10/cache-reload-mechanism.html

时间: 2024-10-31 03:48:42

Cache雪崩效应的相关文章

架构设计之防止或缓解雪崩效应

熔断 当某个服务调用慢或者有大量超时现象(过载),系统停止后续针对该服务的调用而直接返回,直至情况好转才恢复调用.这通常是为防止造成整个系统故障而采取的一种保护措施,也称过载保护.很多时候刚开始,可能只是出现了局部小规模系统故障,但后来故障影响的范围越来越大,最终导致了全局性的后果. 限流 对某个服务调用设置最高QPS阈值,高于阈值的请求放弃调用直接返回.这种模式不能解决服务依赖的问题,只能解决系统整体资源分配问题,因为没有被限流的请求依然有可能造成雪崩效应. 限流处理方案: 限制最大并发数:

【架构】分布式系统雪崩效应处理方案

分布式系统雪崩效应处理方案 异步 雪崩_百度搜索 如何应对并发(2) - 请求合并及异步处理 防雪崩利器:熔断器 Hystrix 的原理与使用 - 编程随笔 - SegmentFault 两种常见雪崩的原理及其避免方法_公园里de石头_新浪博客 [问底]徐汉彬:Web系统大规模并发--电商秒杀与抢购-CSDN.NET 漫谈雪崩 - mikeszhang的专栏 - 博客频道 - CSDN.NET 漫谈雪崩 - 系统其他栏目 - 红黑联盟 前言 分布式系统中经常会出现某个基础服务不可用造成整个系统不

DES的雪崩效应分析

明文固定,密钥改变一个字节 假定明文为11111111(00000001 00000001 00000001 00000001 00000001 00000001 00000001 00000001): 密钥为12345678(00000001 00000010 00000011 00000100 00000101 00000110 00000111 00001000). 密钥变动为10345678(00000001 00000000 00000011 00000100 00000101 000

Hystrix 解决服务雪崩效应

1.服务雪崩效应 默认情况下tomcat只有一个线程池去处理客户端发送的所有服务请求,这样的话在高并发情况下,如果客户端所有的请求堆积到同一个服务接口上, 就会产生tomcat的所有线程去处理该服务接口,可能会导致其他服务接口访问延迟: 2.Hystrix服务保护框架,在微服务中Hystrix为我们解决了哪些事情? Hystrix 别名"豪猪" 1)断路器 2)服务降级 3)服务熔断 4)服务隔离机制 5)服务雪崩效应 -->连环雪崩效应,如果比较严重的话,可能会导致整个微服务接

雪崩效应

微服务化产品线,每一个服务专心于自己的业务逻辑,并对外提供相应的接口,看上去似乎很明了,其实还有很多的东西需要考虑,比如:服务的自动扩充,熔断和限流等,随着业务的扩展,服务的数量也会随之增多,逻辑会更加复杂,一个服务的某个逻辑需要依赖多个其他服务才能完成.一但一个依赖不能提供服务很可能会产生雪崩效应,最后导致整个服务不可访问.微服务之间进行rpc或者http调用时,我们一般都会设置调用超时,失败重试等机制来确保服务的成功执行,看上去很美,如果不考虑服务的熔断和限流,就是雪崩的源头.假设我们有两个

漫谈雪崩

雪崩是指平时正常调用和被调用的A系统和B系统,突然A系统对B系统的訪问超出B系统的承受能力.造成B系统崩溃. 注意将雪崩效应与拒绝服务攻击相差别:两者都是由于B系统过载导致崩溃,但后者是人为的蓄意攻击. 注意将雪崩效应与訪问量激增差别:假设A系统直接面对用户.那么激增的用户将直接带来A系统和B系统的流量激增,只是这样的case能够通过预估而对A系统和B系统做扩容应对. 一种雪崩case 假设A系统的訪问量非常平稳,那么是什么造成B的訪问量激增呢?造成雪崩的原因非常多,本文给出一个比較典型的cas

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

本文转自:http://blog.csdn.net/zeb_perfect/article/details/54135506 前言 设计一个缓存系统,不得不要考虑的问题就是:缓存穿透.缓存击穿与失效时的雪崩效应. 缓存穿透 缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义.在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞

Redis的KEYS命令引起RDS数据库雪崩,RDS发生两次宕机,造成几百万的资金损失

最近的互联网线上事故发生比较频繁,20180919顺丰发生了一起线上删库事件,在这里就不介绍了. 在这里讲述一下最近发生在我公司的事故,以及如何避免,并且如何处理优化. 间接原因还有很多,技术跟不上业务的发展,由每日百万量到千万级是一个大的跨进,公司对于系统优化的处理优先级不高,技术开发人手的短缺 第一次宕机20180913某个点,公司某服务化项目的RDS实例连接飙升,CPU升到100%,拒绝了其他应用的所有请求服务整个过程如下: 监控报警,显示RDS的CPU使用率达到80%以上,DBA介入,准

缓存穿透,缓存击穿,缓存雪崩的原理及解决方案

前言 设计一个缓存系统,不得不要考虑的问题就是:缓存穿透.缓存击穿与失效时的雪崩效应 缓存穿透 缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义.在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞.举例:如发起为id为"-1"的数据或id为特别大不存在的数据.这时的用户很可能是攻击者,攻击会导致数据库压力过大.