1.前言
缓存雪崩这种场景,缓存架构中非常重要的一个环节,应对缓存雪崩的解决方案,避免缓存雪崩的时候,造成整个系统崩溃,带来巨大的经济损失
- redis集群彻底崩溃或者大量key同时过期
- 缓存服务大量对redis的请求hang住,占用资源
- 缓存服务大量的请求打到源头服务去查询mysql,直接打死mysql
- 源头服务因为mysql被打死也崩溃,对源服务的请求也hang住,占用资源
- 缓存服务大量的资源全部耗费在访问redis和源服务无果,最后自己被拖死,无法提供服务
- nginx无法访问缓存服务,redis和源服务,只能基于本地缓存提供服务,但是缓存过期后,没有数据提供
2.解决方案
2.1 事前解决方案
- 发生缓存雪崩之前,事情之前,怎么去避免redis彻底挂掉
- redis本身的高可用性,复制,主从架构,操作主节点,读写,数据同步到从节点,一旦主节点挂掉,从节点跟上
- 双机房部署,一套redis cluster,部分机器在一个机房,另一部分机器在另外一个机房
还有一种部署方式,两套redis cluster,两套redis cluster之间做一个数据的同步,redis集群是可以搭建成树状的结构的。一旦说单个机房出了故障,至少说另外一个机房还能有些redis实例提供服务- redis key失效日期尽量不要一样
2.2 事中解决方案
redis cluster已经彻底崩溃了,已经开始大量的访问无法访问到redis了
- ehcache本地缓存
所做的多级缓存架构的作用上了,ehcache的缓存,应对零散的redis中数据被清除掉的现象,另外一个主要是预防redis彻底崩溃 - 对redis访问的资源隔离
- 对源服务访问的限流以及资源隔离
2.3 事后解决方案
- redis数据可以恢复,做了备份,redis数据备份和恢复,redis重新启动起来
- redis数据彻底丢失了,或者数据过旧,快速缓存预热,redis重新启动起来
3.实现
3.1 思路
- 查询redis时,将其封装到hystrix command中,做资源隔离、限流、熔断等
- 当发现redis出现大量错误(如time out),走fallback,fallback返回null
- 当返回商品返回null,查询二级缓存ehcache
- 当ehcache也返回null时,需要查询mysql
- 查询mysql,也需要将其封装到hystrix command中,避免流量洪峰打死mysql
- 当mysql查询走fallback时,fallback可以从大数据中查询相对旧的商品数据
3.2 代码
@RequestMapping("/getProductInfo")
@ResponseBody
public ProductInfo getProductInfo(Long productId) {
ProductInfo productInfo = null;
//调用的getProductInfoFromReidsCache方法,其实是使用hystrix command(见getProductInfoFromRedisCache方法)
productInfo = cacheService.getProductInfoFromReidsCache(productId);
if(productInfo != null) {
System.out.println("=================从redis中获取缓存,商品信息=" + productInfo);
}
//当command对redis的访问大量的报错和timeout超时,熔断(短路),
//fallback返回null(见GetProductInfoFromReidsCacheCommand ),以便走二级缓存ehcache
if(productInfo == null) {
productInfo = cacheService.getProductInfoFromLocalCache(productId);
if(productInfo != null) {
System.out.println("=================从ehcache中获取缓存,商品信息=" + productInfo);
}
}
if(productInfo == null) {
// 这里也需要增加保护,以避免流量洪峰打死MySQL
GetProductInfoCommand command = new GetProductInfoCommand(productId);
productInfo = command.execute();
// 将数据推送到一个内存队列中
RebuildCacheQueue rebuildCacheQueue = RebuildCacheQueue.getInstance();
rebuildCacheQueue.putProductInfo(productInfo);
}
return productInfo;
}
/**
* 从redis中获取商品信息
* @param productInfo
*/
public ProductInfo getProductInfoFromRedisCache(Long productId) {
GetProductInfoFromReidsCacheCommand command = new GetProductInfoFromReidsCacheCommand(productId);
return command.execute();
}
public class GetProductInfoFromReidsCacheCommand extends HystrixCommand<ProductInfo> {
private Long productId;
public GetProductInfoFromReidsCacheCommand(Long productId) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RedisGroup"))
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
.withExecutionTimeoutInMilliseconds(100)
.withCircuitBreakerRequestVolumeThreshold(1000)
.withCircuitBreakerErrorThresholdPercentage(70)
.withCircuitBreakerSleepWindowInMilliseconds(60 * 1000))
);
this.productId = productId;
}
@Override
protected ProductInfo run() throws Exception {
JedisCluster jedisCluster = (JedisCluster) SpringContext.getApplicationContext()
.getBean("JedisClusterFactory");
String key = "product_info_" + productId;
String json = jedisCluster.get(key);
if(json != null) {
return JSONObject.parseObject(json, ProductInfo.class);
}
return null;
}
@Override
protected ProductInfo getFallback() {
return null;
}
/**
* 从mysql中获取商品信息
* @param productInfo
*/
public class GetProductInfoCommand extends HystrixCommand<ProductInfo> {
private Long productId;
public GetProductInfoCommand(Long productId) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ProductService"))
.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
.withCoreSize(10)
.withMaximumSize(30)
.withAllowMaximumSizeToDivergeFromCoreSize(true)
.withKeepAliveTimeMinutes(1)
.withMaxQueueSize(50)
.withQueueSizeRejectionThreshold(100))
);
this.productId = productId;
}
@Override
protected ProductInfo run() throws Exception {
// 发送http或rpc接口调用,去调用商品服务的接口
String productInfoJSON = "{\"id\": " + productId + ", \"name\": \"iphone7手机\", \"price\": 5599, \"pictureList\":\"a.jpg,b.jpg\", \"specification\": \"iphone7的规格\", \"service\": \"iphone7的售后服务\", \"color\": \"红色,白色,黑色\", \"size\": \"5.5\", \"shopId\": 1, \"modifiedTime\": \"2017-01-01 12:01:00\"}";
return JSONObject.parseObject(productInfoJSON, ProductInfo.class);
}
@Override
protected ProductInfo getFallback() {
// mysql限流后,可以尝试从大数据中获取冷数据
HBaseColdDataCommand command = new HBaseColdDataCommand(productId);
return command.execute();
}
private class HBaseColdDataCommand extends HystrixCommand<ProductInfo> {
private Long productId;
public HBaseColdDataCommand(Long productId) {
super(HystrixCommandGroupKey.Factory.asKey("HBaseGroup"));
this.productId = productId;
}
@Override
protected ProductInfo run() throws Exception {
// 查询hbase
String productInfoJSON = "{\"id\": " + productId + ", \"name\": \"iphone7手机\", \"price\": 5599, \"pictureList\":\"a.jpg,b.jpg\", \"specification\": \"iphone7的规格\", \"service\": \"iphone7的售后服务\", \"color\": \"红色,白色,黑色\", \"size\": \"5.5\", \"shopId\": 1, \"modifiedTime\": \"2017-01-01 12:01:00\"}";
return JSONObject.parseObject(productInfoJSON, ProductInfo.class);
}
@Override
protected ProductInfo getFallback() {
ProductInfo productInfo = new ProductInfo();
productInfo.setId(productId);
// 从内存中找一些残缺的数据拼装进去
return productInfo;
}
}
}
}