大概半年前,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

Cache雪崩效应的更多相关文章

  1. spring-cloud服务器雪崩效应

    在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以相互调用(RPC),在Spring Cloud可以用RestTemplate+Ribbon和Feign来调用.为了保证其高可用,单个服务 ...

  2. Spring Cloud之Hystrix雪崩效应解决方案

    基于Hystris解决雪崩效应: 1.服务降级:    防止用户一直等待,使用降级方式,调用FallBack(返回友好提示,不会去处理请求) 案例: 当前请求人数过多,请稍后重试 2.服务熔断:(和服 ...

  3. DES的雪崩效应分析

    明文固定,密钥改变一个字节 假定明文为11111111(00000001 00000001 00000001 00000001 00000001 00000001 00000001 00000001) ...

  4. Redis雪崩效应以及解决方案

    缓存雪崩产生的原因 缓存雪崩通俗简单的理解就是:由于原有缓存失效(或者数据未加载到缓存中),新缓存未到期间(缓存正常从Redis中获取,如下图)所有原本应该访问缓存的请求都去查询数据库了,而对数据库C ...

  5. hystrix 解决服务雪崩效应

    1.服务雪崩效应 默认情况下tomcat只有一个线程池去处理客户端发送的所有服务请求,这样的话在高并发情况下,如果客户端所有的请求堆积到同一个服务接口上, 就会产生tomcat的所有线程去处理该服务接 ...

  6. 谈谈redis缓存击穿透和缓存击穿的区别,雪崩效应

    面试经历 在很长的一段时间里,我以为缓存击穿和缓存穿透是一个东西,直到最近去腾讯面试,面试官问我缓存击穿和穿透的区别:我回答它俩是一样的,面试官马上抬起头用他那细长的单眼皮眼睛瞪着我说:"你 ...

  7. Redis 缓存穿透,缓存击穿,缓存雪崩的解决方案分析

    设计一个缓存系统,不得不要考虑的问题就是:缓存穿透.缓存击穿与失效时的雪崩效应. 一.什么样的数据适合缓存? 分析一个数据是否适合缓存,我们要从访问频率.读写比例.数据一致性等要求去分析.  二.什么 ...

  8. SpringCloud-使用熔断器防止服务雪崩-Ribbon和Feign方式(附代码下载)

    场景 SpringCloud-服务注册与实现-Eureka创建服务注册中心(附源码下载): https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/deta ...

  9. Redis缓存雪崩,缓存穿透,热点key解决方案和分析

    缓存穿透 缓存系统,按照KEY去查询VALUE,当KEY对应的VALUE一定不存在的时候并对KEY并发请求量很大的时候,就会对后端造成很大的压力. (查询一个必然不存在的数据.比如文章表,查询一个不存 ...

随机推荐

  1. SSL&HTTPS简单介绍

    这篇是最近看SSL和HTTPS的一个简单性总结,其中内容大部分都是参考网络上的内容,自己归纳整理了下. SSL介绍 HTTPS介绍 HTTP请求数据工作流程: l  用户在浏览器中输入网址,并告诉浏览 ...

  2. CRM 实体列名和标签名称

    CREATE VIEW v_entity_attribute AS SELECT EntityView.Name AS EntityName, LocalizedLabelView_1.Label A ...

  3. 2018 oppo校招前端面试题

    1.Es6 2.http请求过程 3.js事件执行流程(蒙蔽中) [默认冒泡,由内到外,] 4.css 样式选择器的优先级 (!important在类选择器和id选择器都可以使用,但不推荐使用) 5. ...

  4. MVP与MVC的区别

    MVP的主要思想就是解耦View和Model 先大致从图上看一下MVP和MVC又什么不同: MVC: M : Model 数据模型,就是对数据的封装和保存: V : View 视图界面,相当于布局文件 ...

  5. C#的两种类据类型:值类型和引用类型

    注:引用类型相等赋值是地址赋值,不是值赋值. 什么是值类型,什么是引用类型 概念:值类型直接存储其值,而引用类型存储对其值的引用.部署:托管堆上部署了所有引用类型. 引用类型:基类为Objcet 值类 ...

  6. echarts.js应用之map

    最近项目中用到了echarts.js中的map,我画了一个简版的案例,如下所示: 效果图: 主要代码如下: <!DOCTYPE html> <html lang="en&q ...

  7. 【Fiddler学习】Fiddler简介和Web抓包应用(转)

    一.Fiddler是什么? Fiddler是一个http协议调试代理工具,它能够记录并检查所有你的电脑和互联网之间的http通讯,设置断点,查看所有的进出Fiddler的数据. Fiddler 要比其 ...

  8. Docker的安装和启动

    2.Docker安装与启动 2.1安装环境说明 Docker官方建议在Ubuntu中安装,因为Docker是基于Ubuntu发布的,而且一般Docker出现的问题Ubuntu是最先更新或者打补丁的.在 ...

  9. 2690036 - SAP HANA 2.0 SPS 03 Database Revision 034

    Symptom This is the SAP Release Note for SAP HANA 2.0 Database Revision 034 (2.00.034.00) of the SAP ...

  10. 错误代码: 1231 - Variable 'sql_mode' can't be set to the value of 'NULL'

    错误代码: 1231 - Variable 'sql_mode' can't be set to the value of 'NULL' 错误代码: - Variable 'sql_mode' can ...