高併發&Redis

讀多寫少用緩存,寫多讀少用隊列。

查詢緩存,查不到取數據庫,放入緩存。

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,只有一個請求能獲得鎖,其餘全部阻塞。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章