Redis 高並發問題,及解決方案!


(一)redis技術的使用:

redis真的是一個很好的技術,它可以很好的在一定程度上解決網站一瞬間的並發量,例如商品搶購秒殺等活動。。。

redis之所以能解決高並發的原因是它可以直接訪問內存,而以往我們用的是數據庫(硬盤),提高了訪問效率,解決了數據庫服務器壓力。

為什么redis的地位越來越高,我們為何不選擇memcache,這是因為memcache只能存儲字符串,而redis存儲類型很豐富(例如有字符串、LIST、SET等),memcache每個值最大只能存儲1M,存儲資源非常有限,十分消耗內存資源,而redis可以存儲1G,最重要的是memcache它不如redis安全,當服務器發生故障或者意外關機等情況時,redsi會把內存中的數據備份到硬盤中,而memcache所存儲的東西全部丟失;這也說明了memcache不適合做數據庫來用,可以用來做緩存。

下面用redis解決瞬間秒殺活動來說明:

下面這個程序模擬了20w人一瞬間涌入這個頁面進行秒殺,能夠秒殺成功的只有500人,我們把先進來的用戶放入redis隊列中,當隊列中的用戶達到500時,后來用戶就轉到秒殺結束頁面。這里用隨機數來表示不同的用戶。

這里我們可以看到秒殺成功的第一個用戶的id是208522,秒殺成功的最后一個用戶是176260,參與秒殺人數總共是20w。(讓大家注意這些的原因是為了驗證下面的准確性)。

接下來我們依次從隊列中把秒殺成功的500個用戶取出來並觀察第一個用戶和最后一個用戶是否跟之前的記錄值一樣

我們可以看到從秒殺成功隊列中依次取出的第一個用戶id是208522,最后一個用戶是176260,可以看出結果是很准確的。

redis在解決高並發這方面的能力是真的挺不錯的。

(二)Redis高並發可能產生的問題,解決:

1、 如果redis宕機了,或者鏈接不上,怎么辦?

解決方法:

①配置主從復制,配置哨兵模式(相當於古代門派的長老級別可以選擇掌門人的權利),一旦發現主機宕機,讓下一個從機當做主機。

②如果最壞的情況,只能關閉Redis連接,去往數據庫連接。但由於數據量大,這樣SQL數據庫也會宕掉的。

2、 如果redis緩存在高峰期到期失效,在這個時刻請求會向雪崩一樣,直接訪問數據庫如何處理?

設置條件查詢判斷,判斷redis緩存里是否有數據,如果沒有,則去往數據庫連接。當然要加分布式鎖,利用redis的單線程+多路IO復用技術,原子性原理,讓其它的線程請求等待,假若第一個線程進去獲取到分布式鎖在查詢數據的途中宕掉了,不能讓其它線程一直等待,設置等待一定時間判斷是否取回數據,如果沒有,遞歸調用自己的方法讓第二個線程繼續拿分布式鎖查詢數據庫。當第二個鎖從數據庫拿到數據時,把數據值設置到redis數據庫緩存中,設置失效時間,避免占內存,方便使用提高效率。

如果用戶不停地查詢一條不存在的數據,緩存沒有,數據庫也沒有,那么會出現什么

如果數據不存在,緩存中沒有,數據庫也沒有,當然如果不設置判斷,會一直調用數據庫,使數據庫效率降低,訪問量大時甚至會宕機。

解決方案:從數據庫查詢,如果數據庫沒有,則返回值為Null,判斷數據庫返回的值,如果為Null,則自定義把標識的字段存到Redis中,用key,value的方法,jedis.setex(key,"empty"),設置失效時間跟具體情況而定,然后調用String json=jedis.get(key),判斷是否獲取的值"empty".equal(json),如果相等,則拋出自定義異常,給用戶提示,或者直接return null。這樣用戶再次查詢的時候由於先從reids緩存中查詢,redis會有對應的Key獲取之前設置的value值,這樣就不會再次調用數據庫,影響效率等問題。

具體代碼如下:
@Override
public SkuInfo getSkuInfo(String skuId) {
try {
Thread.sleep(3*1000);
//自定義從redis工具類中獲取jedis對象
Jedis jedis = redisUtil.getJedis();
//拼接字符串創建Redis里面的Key值
String skuInfoKey= JedisConst.SKU_PREFIX+skuId+JedisConst.SKU_SUFFIX;
//根據key值獲取value值
String skuInfoJson = jedis.get(skuInfoKey);
//如果返回為空,則調用本地數據庫連接
if (skuInfoJsonnull || skuInfoJson.length()0){
System.out.println(Thread.currentThread().getName()+"當前緩存中未找到數據");
//判斷是否有人去取鎖
String skuLockKey=JedisConst.SKU_PREFIX+skuId+JedisConst.SKULOCK_SUFFIX;
String result = jedis.set(skuLockKey, "OK", "NX", "PX", JedisConst.SKULOCK_EXPIRE_PX);
if ("OK".equals(result)){
System.out.println(Thread.currentThread().getName()+"獲得分布式鎖");
SkuInfo skuInfo= getSkuInfoDB(skuId);
//如果數據庫里面沒有值,別人惡意攻擊的話,直接設置到redis緩存中
if (skuInfo==null){
jedis.setex(skuInfoKey,JedisConst.TIME_OUT,"empty");
return null;
}
skuInfoJson = JSON.toJSONString(skuInfo);
jedis.setex(skuInfoKey, JedisConst.TIME_OUT, skuInfoJson);
jedis.close();
return skuInfo;

} else {
 
//假設之后有人取鎖后dang掉了,遞歸調用自己去尋找鑰匙。
System.out.println(Thread.currentThread().getName()+"未獲得分布式鎖,開啟自旋模式俗稱遞歸.");
//等待1秒鍾
Thread.sleep(1*1000);
jedis.close();
return getSkuInfo(skuId);
}
 
}else if (skuInfoJson.equals("empty")){
return null;
}
else {
System.out.println(Thread.currentThread().getName()+"緩存中已有數據正在查詢");
SkuInfo skuInfo = JSON.parseObject(skuInfoJson, SkuInfo.class);
jedis.close();
return skuInfo;
}
}catch (JedisConnectionException e){
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return getSkuInfoDB(skuId);

redis的缺點有哪些?

是數據庫容量受到物理內存的限制,不能用作海量數據的高性能讀寫,因此Redis適合的場景主要局限在較小數據量的高性能操作和運算上。
Redis較難支持在線擴容,在集群容量達到上限時在線擴容會變得很復雜。為避免這一問題,運維人員在系統上線時必須確保有足夠的空間,這對資源造成了很大的浪費。

作者:Java微服務
鏈接:https://www.jianshu.com/p/6ce40dcbf3fe
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM