您好,登錄后才能下訂單哦!
這篇文章主要介紹了如何使用Redis解決高并發的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇如何使用Redis解決高并發文章都會有所收獲,下面我們一起來看看吧。
Not Only SQL的簡稱。NoSQL是解決傳統的RDBMS在應對某些問題時比較乏力而提出的。
即非關系型數據庫,它們不保證關系數據的ACID特性,數據之間一般沒有關聯,在擴展上就非常容易實現,并且擁有較高的性能。
redis是nosql的典型代表,也是目前互聯網公司的必用技術。
redis是鍵值(Key-Value)存儲數據庫,主要會使用到哈希表。大多數時候是直接以緩存的形式被使用,使得請求不直接訪問到磁盤,所以效率方面是很不錯的,完全能滿足中小型企業的使用需求。
常用數據類型
字符串string
散列hash
列表list
集合sets
有序集合sort set
使用頻率上string和hash會高一些,各個類型有各自的操作命令,無非增刪改查,具體的命令后面我會整理一份。
web應用在眾多請求同時發生時,可能會導致數據讀取、存儲上出現錯誤,即發生臟讀、臟數據生成。
在分布式項目下,會出現更多的問題。
并發時,本質其實就是多個請求同時進來了,沒辦法正確的去進行處理。
可以將所有的請求放在 一個隊列,讓請求們按照一個順序,挨個進來執行業務邏輯。目前成熟的解決方案就是使用消息隊列,下次我會整理一篇消息隊列處理高并發的;
還有一個方法是直接將并行轉為串行,Java提供了synchronized,即同步,不過這個在效率要求比較苛刻的地方 或者 分布式項目下還是不太合適的方案,這里就引出了使用redis來實現分布式鎖,從而解決并發問題。
在分布式項目中,使用一個唯一、通用、效率高的標識,來表示上鎖和解鎖。
redis實現起來很簡單,即對一個key是否存在來表示是否上鎖、是否解鎖。
以string類型舉例:
Integer stock = goodsMapper.getStock(); if (stock > 0) { stock =- 1; goodsMapper.updateStock(stock); }
以上是最簡單的秒殺偽代碼,我們嘗試用redis實現分布式鎖。
// 這里是錯誤代碼,只是一個思考過程,請耐心看完哦 String key = "REDIS_DISTRIBUTION_LOCKER"; // 分布式鎖名稱 String value = jedisUtils.get(key); if (value != null) { // 未上鎖 // wingzingliu jedisUtils.set(key, 1); // 上鎖 Integer stock = goodsMapper.getStock(); if (stock > 0) { stock =- 1; goodsMapper.updateStock(stock); jedisUtils.del(key); // 釋放鎖 } }
以上代碼可能會出現一個問題,就是當同時多個請求進來,某次多個請求都拿到value為空,線程A進入if 走到// wingzingliu這里的時候,還未上鎖,其他請求也進來了,這樣就會出現臟數據了。
這里的代碼問題就是出在沒有考慮原子性問題。
所以我們要使用到redis的一個setNx命令,本質也是設置值,但是這是一個原子操作,執行之后會返回是否設置成功。
redis> SETNX job "programmer" # job 設置成功 (integer) 1 redis> SETNX job "code-farmer" # 嘗試覆蓋 job ,失敗 (integer) 0 redis> GET job # 沒有被覆蓋 "programmer"
重點關注 當有值時,會失敗,返回0。所以我們的代碼會改造成以下這個樣子。
// 這里是錯誤代碼,只是一個思考過程,請耐心看完哦 String key = "REDIS_DISTRIBUTION_LOCKER"; // 分布式鎖名稱 Long result = jedisUtils.setNx(key, 1); if (result > 0) { // 上鎖成功,進入邏輯 // wingzingliu1 Integer stock = goodsMapper.getStock(); if (stock > 0) { stock =- 1; goodsMapper.updateStock(stock); System.out.println("購買成功!"); } else { System.out.println("沒有庫存了!"); } // wingzingliu2 jedisUtils.del(key); // 釋放鎖 }
以上我們就可以保證原子性,能正確的按照順序去處理。
可是還有一個隱藏的問題,就是當某個線程執行上鎖成功后,在wingzingliu1到wingzingliu2之間時,程序拋異常了,那么程序終止了,就無法釋放鎖,其他線程也都進不來了。
解決方案是加上try catch finally塊,在finally里面去釋放鎖。
可是那如果是宕機呢?上鎖之后宕機了,finally里面的依然不會執行,鎖沒有得到釋放,不手動處理的情況下,以后所有線程也無法進入。
所以引入了redis的過期時間,到了某個時間自動解鎖。
// 這里是不夠完善的代碼,請耐心看完哦 try { String key = "REDIS_DISTRIBUTION_LOCKER"; // 分布式鎖名稱 Long result = jedisUtils.setNx(key, 1, 30); // 假設處理邏輯需要20s左右,設置了30秒自動過期 if (result > 0) { // 上鎖成功,進入邏輯 Integer stock = goodsMapper.getStock(); if (stock > 0) { stock =- 1; goodsMapper.updateStock(stock); System.out.println("購買成功!"); } else { System.out.println("沒有庫存了!"); } } } catch (Exception e) { } finally { jedisUtils.del(key); // 釋放鎖 }
以上是比較完善的分布式鎖了,但是還有一個小瑕疵,就是假設某一次請求A處理的很慢,預計20s但是跑了35s,到了30s的時候鎖過期了,其他請求就自然進來了。
這不僅僅會導致一次并行,當請求A處理完時,依然會執行釋放鎖,這實際上是下一個線程上的鎖。以此類推,整個并發控制就亂了。
理論上可以設置一個更大的key過期時間,但是并不是最好的解決方案。這里就引出一個概念:鎖續命。
如其名,給鎖續命。實現就是 當鎖快過期的時候,去延長鎖的時間。假設一個30s的鎖,每個10s去檢測一下,鎖是否還在 如果在就重新延長至30s。這樣就避免掉了上面的這個可能出現的問題。
這里使用一個定時任務,周期性的調用即可。
剛剛對key設置的value是1,其實能使用請求ID來進行保存,這樣就能知道鎖是由哪個請求上的,在解鎖的時候 也可以避免解鎖了其他線程上的鎖。具體由前端傳遞,或者由服務端以某種規則生成都可以。
關于“如何使用Redis解決高并發”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“如何使用Redis解決高并發”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。