您好,登錄后才能下訂單哦!
本篇內容介紹了“怎么使用redis分布式鎖”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
不僅可以用來緩存數據,在分布式應用開發中,經常被用來當作分布式鎖的使用,為什么要用到分布式鎖呢?
在分布式的開發中,以電商庫存的更新功能進行講解,在實際的應用中相同功能的消費者是有多個的,假如多個消費者同一時刻要去消費一條數據,假如業務邏輯處理邏輯是查詢出redis中的商品庫存,而如果第一個進來的消費的消費者獲取到庫存了,還沒進行減庫存操作,相對晚來的消費者就獲取了商品的庫存,這樣就導致數據會出錯,導致消費的數據變多了。
例如:消費者A和消費者B分別去消費生產者C1和生產者C2的數據,而生產者都是使用同一個redis的數據庫的,如果生產者C1接收到消費者A的消息后,先進行查詢庫存,然后當要進行減庫存的時候,因為生產者C2接收到消費者B的消息后,也去查詢庫存,而因為生產者C1還沒有進行庫存的更新,導致生產者C2獲取到的庫存數是臟數據,而不是生產者C1更新后的數據,導致業務出錯。
如果不是分布式的應用,可以使用synchronized進行防止庫存更新的問題的產生,但是synchronized只是基于JVM層面的,如果在不同的JVM中,就不能實現這樣的功能。
@GetMapping("getInt0") public String test() { synchronized (this) { //獲取當前商品的數量 int productNum = Integer.parseInt(stringRedisTemplate.opsForValue().get("product")); //然后對商品進行出庫操作,即進行減1 /* * a業務邏輯 * * */ if (productNum > 0) { stringRedisTemplate.opsForValue().set("product", String.valueOf(productNum - 1)); int productNumNow = productNum - 1; } else { return "product=0"; } int productNumNow = productNum - 1; return "success=" + productNumNow; } }
如果對redis熟悉的話,我們能夠想到redis中具有setnx的命令,該命令的功能宇set功能類似,但是setnx的命令在進行存數據前,會檢查redis中是否已經存在相同的key,如存在的話就返回false,反之則返回true,因此我們可以使用該命令的功能,設計一個分布式鎖。
在請求相同功能的接口時,使用redis的setnx命令,如果使用setnx命令后返回的是為true,說明此時沒有其他的調用這個接口,就相當于獲取到鎖了,然后就可以繼續執行接下來的業務邏輯了。當執行完業務邏輯后,在返回數據前,就把key刪除了,然后其他的請求就能獲取到鎖了。
如果使用setnx命令,返回的是false,說明此時有其他的消費者正在調用這個接口,因此需要等待其他消費者順利消費完成后,才能獲取到分布式的鎖。
代碼片段【1】
@GetMapping("getInt1") public String fubushisuo(){ //setIfAbsent的指令功能和redis命令中的setNx功能一樣,如果redis中已經存在相同的key,則返回false String lockkey = "yigehaimeirumengdechengxuyuan"; String lockvalue = "yigehaimeirumengdechengxuyuan"; boolean opsForSet = stringRedisTemplate.opsForValue().setIfAbsent(lockkey,lockvalue); //如果能夠成功的設置lockkey,這說明當前獲取到分布式鎖 if (!opsForSet){ return "false"; } //獲取當前商品的數量 int productNum = Integer.parseInt(stringRedisTemplate.opsForValue().get("product")); //然后對商品進行出庫操作,即進行減1 /* * a業務邏輯 * * */ if (productNum>0){ stringRedisTemplate.opsForValue().set("product", String.valueOf(productNum - 1)); int productNumNow = productNum - 1; }else { return "product=0"; } //然后進行釋放鎖 stringRedisTemplate.delete(lockkey); int productNumNow = productNum-1; return "success="+productNumNow; }
如果使用這種方式,會產生死鎖的方式:
死鎖發生的情況:
(1) 如果在a業務邏輯出現錯誤時,導致不能執行delete()操作,使得其他的請求不能獲取到分布式鎖,業務lockkey一直存在于reids中,導致setnx操作一直失敗,所以不能獲取到分布式鎖
(2) 解決方法,使用對業務代碼進行try…catch操作,如果出現錯誤,那么使用finally對key進行刪除
優化代碼【2】
@GetMapping("getInt2") public String fubushisuo2(){ //setIfAbsent的指令功能和redis命令中的setNx功能一樣,如果redis中已經存在相同的key,則返回false String lockkey = "yigehaimeirumengdechengxuyuan"; String lockvalue = "yigehaimeirumengdechengxuyuan"; boolean opsForSet = stringRedisTemplate.opsForValue().setIfAbsent(lockkey,lockvalue); int productNumNow = 0; //如果能夠成功的設置lockkey,這說明當前獲取到分布式鎖 if (!opsForSet){ return "false"; } try { //獲取當前商品的數量 int productNum = Integer.parseInt(stringRedisTemplate.opsForValue().get("product")); //然后對商品進行出庫操作,即進行減1 /* * b業務邏輯 * */ if (productNum>0){ stringRedisTemplate.opsForValue().set("product", String.valueOf(productNum - 1)); productNumNow = productNum-1; }else { return "product=0"; } }catch (Exception e){ System.out.println(e.getCause()); }finally { //然后進行釋放鎖 stringRedisTemplate.delete(lockkey); } return "success="+productNumNow; }
出現問題的情況:
如果這種情況也有會產生的情況,如果此時有多臺服務器都在運行該方法,
其中有一個方法獲取到了分布式鎖,而在運行下面的業務代碼時,此時該服務器突然宕機了,導致其他的不能獲取到分布式鎖,
解決方法:加上過期時間,但又服務宕機了,過了設置的時間后,redis會可以把key給刪除,這樣其他的的服務器就可以正常的進行上鎖了。
優化代碼【3】
@GetMapping("getInt3") public String fubushisuo3(){ //setIfAbsent的指令功能和redis命令中的setNx功能一樣,如果redis中已經存在相同的key,則返回false String lockkey = "yigehaimeirumengdechengxuyuan"; String lockvalue = "yigehaimeirumengdechengxuyuan"; //[01] boolean opsForSet = stringRedisTemplate.opsForValue().setIfAbsent(lockkey,lockvalue); //設置過期時間為10秒,但是如果使用該命令,沒有原子性,可能執行expire前宕機了,而不是設置過期時間, //[02] stringRedisTemplate.expire(lockkey, Duration.ofSeconds(10)); //使用setIfAbsent(lockkey,lockvalue,10,TimeUnit.SECONDS);代碼代替上面[01],[02]行代碼 Boolean opsForSet = stringRedisTemplate.opsForValue().setIfAbsent(lockkey, lockvalue, 10, TimeUnit.SECONDS); int productNumNow = 0; //如果能夠成功的設置lockkey,這說明當前獲取到分布式鎖 if (!opsForSet){ return "false"; } try { //獲取當前商品的數量 int productNum = Integer.parseInt(stringRedisTemplate.opsForValue().get("product")); //然后對商品進行出庫操作,即進行減1 /* * c業務邏輯 * */ if (productNum>0){ stringRedisTemplate.opsForValue().set("product", String.valueOf(productNum - 1)); productNumNow = productNum-1; }else { return "product=0"; } }catch (Exception e){ System.out.println(e.getCause()); }finally { //然后進行釋放鎖 stringRedisTemplate.delete(lockkey); } return "success="+productNumNow; }
出現問題的情況:
如果c業務邏輯持續超過了設置時間,導致redis中的lockkey過期了,
而其他的用戶此時訪問該方法時獲取到鎖了,而在此時,之前的的c業務邏輯也執行完成了,但是他會執行delete,把lcokkey刪除了。導致分布式鎖出錯。
例子:在12:01:55的時刻,有一個A來執行該getInt3方法,并且成功獲取到鎖,但是A執行了10秒后還不能完成業務邏輯,導致redis中的鎖過期了,而在11秒的時候有B來執行getint3方法,因為key被A刪除了,導致B能夠成功的獲取redis鎖,而在B獲取鎖后,A因為執行完成了,然后把reids中的key給刪除了,但是我們注意的是,A刪除的鎖是B加上去的,而A的鎖是因為過期了,才被redis自己刪除了,因此這導致了C如果此時來時也能獲取redis分布式鎖
解決方法:使用UUID,產生一個隨機數,當要進行delete(刪除)redis中key時,判斷是不是之前自己設置的UUID
代碼優化【4】
@GetMapping("getInt4") public String fubushisuo4(){ //setIfAbsent的指令功能和redis命令中的setNx功能一樣,如果redis中已經存在相同的key,則返回false String lockkey = "yigehaimeirumengdechengxuyuan"; //獲取UUID String lockvalue = UUID.randomUUID().toString(); Boolean opsForSet = stringRedisTemplate.opsForValue().setIfAbsent(lockkey, lockvalue, 10, TimeUnit.SECONDS); int productNumNow = 0; //如果能夠成功的設置lockkey,這說明當前獲取到分布式鎖 if (!opsForSet){ return "false"; } try { //獲取當前商品的數量 int productNum = Integer.parseInt(stringRedisTemplate.opsForValue().get("product")); //然后對商品進行出庫操作,即進行減1 /* * c業務邏輯 * */ if (productNum>0){ stringRedisTemplate.opsForValue().set("product", String.valueOf(productNum - 1)); productNumNow = productNum-1; }else { return "product=0"; } }catch (Exception e){ System.out.println(e.getCause()); }finally { //進行釋放鎖 if (lockvalue==stringRedisTemplate.opsForValue().get(lockkey)){ stringRedisTemplate.delete(lockkey); } } return "success="+productNumNow; }
出現問題的情況:
此時該方法是比較完美的,一般并發不是超級大的情況下都可以進行使用,但是關于key的過期時間需要根據業務執行的時間,進行設置,防止在業務還沒執行完時,key就過期了.
解決方法:目前有很多redis的分布式鎖的框架,其中redisson用的是比較多的
先添加redisson的maven依賴
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.11.1</version> </dependency>
redisson的bean配置
@Configuration public class RedissonConfigure { @Bean public Redisson redisson(){ Config config = new Config(); config.useSingleServer().setAddress("redis://27.196.106.42:6380").setDatabase(0); return (Redisson) Redisson.create(config); } }
實現分布式鎖代碼如下
@GetMapping("getInt5") public String fubushisuo5(){ //setIfAbsent的指令功能和redis命令中的setNx功能一樣,如果redis中已經存在相同的key,則返回false String lockkey = "yigehaimeirumengdechengxuyuan"; //獲取UUID RLock lock = redisson.getLock(lockkey); lock.lock(); int productNumNow = 0; //如果能夠成功的設置lockkey,這說明當前獲取到分布式鎖 try { //獲取當前商品的數量 int productNum = Integer.parseInt(stringRedisTemplate.opsForValue().get("product")); //然后對商品進行出庫操作,即進行減1 /* * c業務邏輯 * */ if (productNum>0){ stringRedisTemplate.opsForValue().set("product", String.valueOf(productNum - 1)); productNumNow = productNum-1; }else { return "product=0"; } }catch (Exception e){ System.out.println(e.getCause()); }finally { lock.unlock(); } //然后進行釋放鎖 return "success="+productNumNow; }
從面就能看到,redisson實現分布式鎖是非常簡單的,只要簡單的幾條命令就能實現分布式鎖的功能的。
redisson實現分布式鎖的只要原理如下:
redisson使用了Lua腳本語言使得命令既有原子性,redisson獲取鎖時,會給key設置30秒的過期是按,同時redisson會記錄當前請求的線程編號,然后定時的去檢查該線程的狀態,如果還處于執行狀態的話,而且key差不多要超期過時時,redisson會修改key的過期時間,一般增加10秒。這樣就可以動態的設置key的過期時間了,彌補了優化代碼【4】的片段
“怎么使用redis分布式鎖”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。