09蚂蚁-分布式缓存架构——12.缓存穿透与缓存雪崩

缓存穿透与缓存雪崩

缓存雪崩
缓存雪崩产生的原因

缓存雪崩通俗简单的理解就是:由于原有缓存失效(或者数据未加载到缓存中),新缓存未到期间(缓存正常从Redis中获取,如下图)所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机,造成系统的崩溃。
在这里插入图片描述

缓存失效的时候如下图:

在这里插入图片描述

缓存失效时的雪崩效应对底层系统的冲击非常可怕!那有什么办法来解决这个问题呢?基本解决思路如下:

第一,大多数系统设计者考虑用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,避免缓存失效时对数据库造成太大的压力,虽然能够在一定的程度上缓解了数据库的压力但是与此同时又降低了系统的吞吐量。

第二,分析用户的行为,尽量让缓存失效的时间均匀分布。

第三,如果是因为某台缓存服务器宕机,可以考虑做主备,比如:redis主备,但是双缓存涉及到更新事务的问题,update可能读到脏数据,需要好好解决。

解决方案

在这里插入图片描述
消息中间件方式逻辑
在这里插入图片描述
1:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。

@RequestMapping("/getUsers")
	public Users getByUsers(Long id) {
		// 1.先查询redis
		String key = this.getClass().getName() + "-" + Thread.currentThread().getStackTrace()[1].getMethodName()
				+ "-id:" + id;
		String userJson = redisService.getString(key);
		if (!StringUtils.isEmpty(userJson)) {
			Users users = JSONObject.parseObject(userJson, Users.class);
			return users;
		}
		Users user = null;
		try {
			lock.lock();
			// 查询db
			user = userMapper.getUser(id);
			redisService.setSet(key, JSONObject.toJSONString(user));
		} catch (Exception e) {

		} finally {
			lock.unlock(); // 释放锁
		}
		return user;
	}

注意:加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量。假设在高并发下,缓存重建期间key是锁着的,这是过来1000个请求999个都在阻塞的。同样会导致用户等待超时,这是个治标不治本的方法。

2:不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
3:做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期(此点为补充)

@Service
public class UserAvalanService {
	@Autowired
	private UserMapper userMapper;
	@Autowired
	private RedisService redisService;
	private Lock lock = new ReentrantLock();

	private String SIGN_KEY = "${NULL}";

	public Users getByUsers(Long id) {
		// 1.先查询redis
		String key = this.getClass().getName() + "-" + Thread.currentThread().getStackTrace()[1].getMethodName()
				+ "-id:" + id;
		String userJson = redisService.getString(key);
		if (!StringUtils.isEmpty(userJson)) {
			Users users = JSONObject.parseObject(userJson, Users.class);
			return users;
		}
		Users user = null;
		try {
			lock.lock();
			// 查询db
			user = userMapper.getUser(id);
			redisService.setSet(key, JSONObject.toJSONString(user));
			lock.unlock();
		} catch (Exception e) {

		} finally {
			lock.unlock(); // 释放锁
		}
		return user;
	}

	public String getByUsers2(Long id) {
		// 1.先查询redis
		String key = this.getClass().getName() + "-" + Thread.currentThread().getStackTrace()[1].getMethodName()
				+ "-id:" + id;
		String userName = redisService.getString(key);
		if (!StringUtils.isEmpty(userName)) {
			return userName;
		}
		System.out.println("######开始发送数据库DB请求########");
		Users user = userMapper.getUser(id);
		String value = null;
		if (user == null) {
			// 标识为null
			value = SIGN_KEY;
		} else {
			value = user.getName();
		}
		redisService.setString(key, value);
		return value;
	}
}
缓存穿透

在这里插入图片描述
 缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。
解决的办法就是:如果查询数据库也为空,直接设置一个默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴。

把空结果,也给缓存起来,这样下次同样的请求就可以直接返回空了,即可以避免当查询的值为空时引起的缓存穿透。同时也可以单独设置个缓存区域存储空值,对要查询的key进行预先校验,然后再放行给后面的正常缓存处理逻辑。

  public String getUser02(Long id){
        String resultUserName = null;
        String key = this.getClass().getName()+"-"+Thread.currentThread().getStackTrace()[1].getMethodName()
                +"-id:"+id;
        //限制id 规则 ngix
        //1.先查询redis
        System.out.println("查询redis缓存 key:"+key);
        String userName = redisServer.getString(key);
        if(!StringUtils.isEmpty(userName)){
            return userName;
        }

        //2.再查询数据库
        //如果数据中没有对应数据信息的时候
        System.out.println("查询数据库:id="+id);
        User user = userMapper.getUser(id);
        if (user == null) {
            resultUserName = "${null}";
        }else {
            resultUserName = user.getName();
        }
        System.out.println("写入redis缓存 key:"+key+",resultUserName:"+resultUserName);
        redisServer.setString(key, resultUserName);
        //3.直接返回
        return resultUserName;
    }

把空结果,也给缓存起来,这样下次同样的请求就可以直接返回空了,即可以避免当查询的值为空时引起的缓存穿透。同时也可以单独设置个缓存区域存储空值,对要查询的key进行预先校验,然后再放行给后面的正常缓存处理逻辑。

注意:再给对应的ip存放真值的时候,需要先清除对应的之前的空缓存。

热点key

热点key:某个key访问非常频繁,当key失效的时候有打量线程来构建缓存,导致负载增加,系统崩溃。

解决办法:
①使用锁,单机用synchronized,lock等,分布式用分布式锁。
②缓存过期时间不设置,而是设置在key对应的value里。如果检测到存的时间超过过期时间则异步更新缓存。
③在value设置一个比过期时间t0小的过期时间值t1,当t1过期的时候,延长t1并做更新缓存操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据您提供的引用内容,您遇到了一个关于npm的问题。报错信息显示,使用`npm -v`命令查询版本号时出现了`npm WARN config global --global, --local are deprecated. Use --location`的警告信息 [1 [2。 解决这个问题的方法是打开nodejs安装位置的文件夹,找到`npm.cmd`和`npm`这两个文件,然后将文件中的`prefix -g`替换为`prefix --location=global` [3。如果您没有权限修改这些文件,请以管理员身份运行命令提示符并进行授权。修改完成后,关闭之前的命令行窗口,并重新打开一个新的命令行窗口。然后再次运行`npm -v`命令,应该就可以成功查询到npm的版本号了。 至于您提到的`npm ERR! code ERESOLVE`错误,这个错误通常表示在解析npm包依赖关系时遇到了问题。您可以尝试运行`npm cache clean --force`命令清除npm缓存,并重新安装依赖包来解决这个问题。 希望以上解决方案对您有帮助!<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [npm WARN config global `--global`, `--local` are deprecated. Use `--location解决方案](https://blog.csdn.net/tnb_ml/article/details/125678413)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *2* [【亲测有效】npm WARN config global `--global`, `--local` are deprecated. Use `--location=global` ...](https://blog.csdn.net/baidu_30506559/article/details/125709063)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值