亿级流量电商详情页系统实战-51.缓存雪崩解决方案

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;
		}
		
	}

}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值