高并发&Redis

本文探讨了缓存和队列在高并发场景下的应用策略,解析了缓存失效、缓存雪崩的问题及解决方案,介绍了如何使用锁机制避免数据库压力过大。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

读多写少用缓存,写多读少用队列。

查询缓存,查不到取数据库,放入缓存。

public String query(){
		
		// 从缓存中获取数据
		String key = "key";
		String value = redisService.get(key);//高并发场景下,所有线程进来查询缓存,缓存中没有数据时,来不及将数据库中数据放入缓存,全部去查询数据库
		if(value != null){
			System.out.println("从缓存中获取数据!");
			return value;
		}
		
		// 从数据库中获取数据
		value = dataService.get(key);
		System.out.println("从数据库中获取数据!");
		
		// 将数据插入缓存
		redisService.set(key, value, 5*60);
		return value;
	}

一、缓存失效

1、高峰期大面积缓存key失效 / 缓存挂掉

解决:设置不同key不同失效时间,根据业务场景设置,或采用随机数设置。避免相同失效时间,导致一时大量数据失效,全部查询数据库。

2、局部高峰期,热点缓存key失效

导致热点数据获取海量请求直击数据库。

二、缓存雪崩

因缓存服务挂掉或者热点缓存失效,导致大量请求查询数据库,数据库服务器压力过大,数据库连接不够用或者数据库处理不过来,导致整个系统不可用。依赖数据库的其他系统面临崩溃风险。

解决:

增加互斥锁,拿到锁的线程查询数据库,写入缓存,其他线程判断缓存是否存在,此时之前拿到锁的线程已经把数据写入了,缓存存在,不在查询数据库。

synchronized:同步关键字,导致线程一直等待锁,全部请求排队阻塞影响效率。

public String query(){
		
		// 从缓存中获取数据
		String key = "key";
		String value = redisService.get(key);
		if(value != null){
			System.out.println("从缓存中获取数据!");
			return value;
		}
		
		synchronized (this) {
			// 从数据库中获取数据
			value = dataService.get(key);
			System.out.println("从数据库中获取数据!");
			
			// 将数据插入缓存
			redisService.set(key, value, 5*60);
			return value;
		}
		
	}

lock:灵活获取锁,释放锁。

lock(); //获取锁,多个线程争抢锁,抢不到一直等待,阻塞,不可中断。
lockInterruptibly(); //获取锁,等待,阻塞,可中断。
tryLock(); //尝试获取锁,取不到返回false
tryLock(long l, TimeUnit timeunit); //指定时间内尝试获取锁,取不到返回false
unlock(); //释放锁
	Lock lock = new ReentrantLock();
	
	public String query(){
		
		// 1.从缓存中获取数据
		String key = "key";
		String value = redisService.get(key);
		if(value != null){
			System.out.println("从缓存中获取数据!");
			return value;
		}
		
		lock.lock();// 所有线程排队获取锁,只有1个能拿到
		try {
			value = redisService.get(key);// 二次校验
			if(value != null){
				System.out.println("从缓存中获取数据!");
				return value;
			}
			// 2.从数据库中获取数据
			value = dataService.get(key);
			System.out.println("从数据库中获取数据!");
			
			// 3.将数据插入缓存
			redisService.set(key, value, 5*60);
			return value;
		} finally{
			lock.unlock();
		}
	}

优点:简单有效,请求不会全部涌向数据库;适用范围广

缺点:锁会阻塞所有线程,排队获取锁,加大等待时长,用户体验差; 锁的颗粒度粗,不同的key被同一把锁阻塞(针对不同key单独加锁)

针对不同key单独加锁:

@Autowired
	RedisService redisService;
	
	@Autowired
	DataService dataService;
	
	ConcurrentHashMap<String, String> mapLock = new ConcurrentHashMap<String, String>();//map记录锁状态
	
	public String query(){
		
		// 1.从缓存中获取数据
		String key = "key";
		String value = redisService.get(key);
		if(value != null){
			System.out.println("从缓存中获取数据!");
			return value;
		}
		
		boolean lock = false;
		try {
			// putIfAbsent 等同于redis setnx,存在则返回,不存在则插入
			lock = mapLock.putIfAbsent(key, key) == null;//插入一条数据,判断如果不存在则插入并返回null,存在则获取旧值返回。如果返回null,意味着插入成功,获得锁
			if (lock) {//拿到锁
				value = redisService.get(key);// 二次校验
				if(value != null){
					System.out.println("从缓存中获取数据!");
					return value;
				}
				// 2.从数据库中获取数据
				value = dataService.get(key);
				System.out.println("从数据库中获取数据!");
				
				// 3.将数据插入缓存
				redisService.set(key, value, 5*60);
			}else {// 没拿到锁怎么办?--缓存降级
				// 1、重试,争抢锁,控制重试次数
				// 2、返回空
				// 3、备用缓存
				value = null;
				System.out.println("缓存降级!");
			}
			return value;
		} finally{
			if (lock) {
				mapLock.remove(key);//释放锁
			}
		}
	}

优点:减小锁的颗粒度

缺点:锁的颗粒度仍然大,同一个key,只有一个请求能获得锁,其余全部阻塞。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值