分佈式鎖會在高併發的業務被使用到:
一、 分佈式鎖的處理一般可以有兩種處理方式:
1.利用zookepeer的數據結構以及特性來處理分佈式鎖。
zookeeper可以創建臨時有序的數據節點,同時每個數據節點可以對比其小的數據節點進行監控。只有當比自己點 排序更小的數據節點被刪除之後纔會,zk纔會處理下一個節點。利用此特性,將高併發的訪問寫入到zk上的不同的有序 的節點。這時候各個請求就會有序的執行,只有在序列號比自己小的請求執行完畢之後纔會執行,可以實現一個分鎖。
2.利用redis的可以對每一個key可以執行一個設置鎖的操作,以及該鎖的過期時間可以實現。但是在實現過程中會出現一些問題,今天我就整理自己的一些見解以及使用代碼來完成使用redis處理分佈式鎖。
二、使用reids處理分佈式鎖的實戰
1.原生api處理分佈式鎖(效果不好,適用於併發訪問低的網站,這裏不再演示)
關鍵字:setNx expire
2.使用redisTemplate模板操作(效果較好,適用訪問量較大的公司)
<1>.第一步:pom文件中引入相關依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
<2>.第二步:applicaiton.properties中配置redis的相關鏈接配置
redis.hostName=127.0.0.1 redis.port=6379 redis.password=https://blog.hhui.top # 連接超時時間 redis.timeout=10000 #最大空閒數 redis.maxIdle=300 #控制一個pool可分配多少個jedis實例,用來替換上面的redis.maxActive,如果是jedis 2.4以後用該屬性 redis.maxTotal=1000 #最大建立連接等待時間。如果超過此時間將接到異常。設爲-1表示無限制。 redis.maxWaitMillis=1000 #連接的最小空閒時間 默認1800000毫秒(30分鐘) redis.minEvictableIdleTimeMillis=300000 #每次釋放連接的最大數目,默認3 redis.numTestsPerEvictionRun=1024 #逐出掃描的時間間隔(毫秒) 如果爲負數,則不運行逐出線程, 默認-1 redis.timeBetweenEvictionRunsMillis=30000 #是否在從池中取出連接前進行檢驗,如果檢驗失敗,則從池中去除連接並嘗試取出另一個 redis.testOnBorrow=true #在空閒時檢查有效性, 默認false redis.testWhileIdle=true
<3>.第三步:分佈式鎖代碼實現
package com.example.demo.redis; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.UUID; import java.util.concurrent.TimeUnit; /** * 商品操作類 */ @RestController @RequestMapping("/redis") public class ProductController { @Autowired private StringRedisTemplate redisTemplate; @RequestMapping("/product_count") public String getProduct(){ /** * 定義秒殺商品的redis中的key 以及 一個UUID 作爲鎖的value */ String productName = "productId"; String locKeyName = "lockKey"; String value = UUID.randomUUID().toString(); try{ /** * 設置鎖,以及鎖的過期時間,最好使用四個參數的setifAbsent保證原子性 */ Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(locKeyName,value,30, TimeUnit.SECONDS); if(!aBoolean){ return "此時鎖被別人佔領"; } /** * 減庫存操作 */ int product_count = Integer.valueOf(redisTemplate.opsForValue().get(productName)); if(product_count > 0){ int count = product_count - 1; redisTemplate.opsForValue().set(productName,String.valueOf(count)); System.out.println("扣除庫存成功!當前庫存爲" + count); }else{ System.out.println("商品已售空!"); } }finally { /** * 操作完畢,釋放當前線程的鎖 */ String concurrentLock = redisTemplate.opsForValue().get(locKeyName); if(value.equals(concurrentLock)){ redisTemplate.delete("product_001"); } } return "mission completed"; } }
總結:使用 redisTemplate做分佈式鎖的注意事項。
注意事項:
1.在使用redisTemplate的setIfAbsent API 的時候需要注意的是一定要使用key、value、long、timeUnit這參數 都有的方法。因爲這樣才能夠保證get鎖和設置鎖的過期時間保持原子性。否則在這兩個操作之間可能會出現錯誤。
2.在使用redisTemplate做分佈式鎖的時候需要使用try +finally 來釋放鎖,因爲在程序執行的過程中可能會出現一 些狀況,所以必須得保證每一個線程執行完畢之後釋放鎖。
3.在釋放鎖的時候必須加上判斷保證釋放的是自己的鎖,否則有可能會釋放到後面線程的鎖導致鎖的永久失效。
舉例說明:
4.任務執行的時間問題還是無法解決,需要在主線程中new一個新的線程來增加lockKey的存活時間。知道主線程完 成時纔將此線程關閉,才能夠保證鎖被別的線程獲取同時也能夠保證自己的任務執行完成。
有興趣的小夥伴可以知己動手嘗試着手寫一下。
三、使用reids + Redisson處理分佈式鎖的實戰
1.加入依賴:
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.7.4</version> </dependency>
2.將redisson注入spring容器:
@Bean // 創建好的redisson必須加上@Bean才能注入到spring容器中
public Redisson redisson(){
/**
* 使用redisson的config創建一個redisson的客戶端並連接
* 並使用@Bean註解將其初始化到spring容器
*/
Config config = new Config();
config.useSingleServer().setAddress("127.0.0.1:6379").setDatabase(0);
/**
* 注意類型轉換
*/
return (Redisson) Redisson.create(config);
}
注意:redisson的創建方式是使用redisson的config 來設置地址和數據庫。最後使用create()有參構造創建。
3.代碼實戰:
package com.example.demo.redis; import org.redisson.Redisson; import org.redisson.api.RLock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.concurrent.TimeUnit; @RestController @RequestMapping("/redisson") public class RedissonController { @Autowired private Redisson redisson; @Autowired private StringRedisTemplate redisTemplate; @RequestMapping("/product") public String getProduct(){ String productName = "productId"; String locKey = "lockKey"; /** * 使用redisson獲取鎖並判斷其是否獲取鎖 */ RLock lock = redisson.getLock(locKey); boolean locked = lock.isLocked(); if(locked){ return "此時鎖被別的線程使用!"; } /** * 設置鎖的過期時間: 鎖的默認過期時間爲30s * * 注:redission的lock方法的底層是一個rentreelock,同時它的底層也自動加上了一個線程 * 作爲定時器來增加鎖的過期時間,知道業務執行完畢之後纔會關閉調該線程。保證在業務執行完成再釋放鎖! * */ lock.lock(30, TimeUnit.SECONDS); // 解決了延長鎖的持有之間的問題 /** * 業務處理 */ Integer count = Integer.valueOf(redisTemplate.opsForValue().get(productName)); if(count > 0){ int x = count - 1; redisTemplate.opsForValue().set(productName,String.valueOf(x)); System.out.println("操作成功,庫存數量減一"); }else{ System.out.println("庫存不足,請稍後操作!"); } lock.unlock(); // 注意:lock必須手動釋放!!! return "mission completed"; } }
注意:Lock lock = redision.getLock("name"); 獲取到的是一個可重入鎖:renetreeLock
lock.lock(),方法中已經實現了加入定時增加的key的存活時間的線程,能夠保證資源不被浪費。保證在當前線程執行完畢之後才釋放鎖。
rentreelock必須釋放鎖!!!
這就已經用redis實現了分佈式鎖!