目錄
分布式鎖介紹
模擬一個并發場景
基于Redis實作分布式鎖
原理
案例優化(加入分布式鎖)
Redisson
Redisson簡介
Redisson功能特性
基于Redisson實作分布式鎖
分布式鎖介紹
在計算機系統中,鎖作為一種控制并發的機制無處不在,單機環境下,作業系統能夠在行程或執行緒之間通過本地的鎖來控制并發程式的行為,而在如今的大型復雜系統中,通常采用的是分布式架構提供服務,分布式環境下,基于本地單機的鎖無法控制分布式系統中分開部署客戶端的并發行為,此時分布式鎖就應運而生了,
一個可靠的分布式鎖應該具有以下特征:
①互斥鎖:作為鎖,需要保證任何時候只能有一個客戶端持有鎖,
②可重入:同一個客戶端在獲得鎖后,可以再次進行加鎖,
③高可用:獲取鎖和釋放鎖的效率較高,不會出現單點故障,
④自動重試機制:當客戶加鎖失敗時,能夠提供一種機制讓客戶端自動重試,
模擬一個并發場景
眾所周知,電商系統秒殺活動優惠非常之大,同時也給系統帶來了不可想象的并發壓力,如果不做并發處理,會出現什么情況呢?沒錯,就是會出現超賣的情況,
那么,什么是超賣呢?
比如,某米10紀念版要參與秒殺活動,2000元/臺,相比正常價格下優惠力度非常之大,所以參加活動當然也需要確定秒殺的庫存數,這邊我們假設待秒殺的庫存數為100臺,搶完100臺則活動結束,
案例:當我們系統中如果沒有加入分布式鎖,會出現如下問題:
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@GetMapping("deductStock")
public String deductStock(){
Integer stock = Integer.parseInt(this.stringRedisTemplate.opsForValue().get("stock"));
if (stock > 0){
Integer realStock = stock - 1;
this.stringRedisTemplate.opsForValue().set("stock", realStock + "");
System.out.println("扣減成功,剩余庫存:" + realStock);
}else {
System.out.println("扣減失敗,庫存不足!");
}
return "執行完畢";
}
}
代碼在一般情況下感覺沒有任何問題,但是當同一時間段進行秒殺操作,是有可能存在并發的,我們假設并發數為10,那么這10個并發會同一時間段去get("stock"),此時獲取的stock都是100,再同一時間進行減庫存設定庫存的操作,那么此時Redis中的stock值為99,正確應該為90,
基于Redis實作分布式鎖
原理
利用Redis的setnx命令,因為此命令是原子性操作,只有在key不存在的情況下,才能set成功,
案例優化(加入分布式鎖)
看看加了分布式鎖后的代碼
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@GetMapping("deductStock")
public String deductStock(){
// 鎖的key,實際業務需要拼接
String lockKey = "product_mi10";
// 隨機字符,保證釋放鎖時釋放的是自己的鎖
String clientId = UUID.randomUUID().toString();
try {
// Redis的setnx,加上過期時間,程式還沒到釋放鎖的步驟已經宕機
Boolean result = this.stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);
if (!result){// 保證只有獲得鎖的執行緒在執行后續操作
return "未獲取到鎖!";
}
// 查詢商品庫存數
Integer stock = Integer.parseInt(this.stringRedisTemplate.opsForValue().get("stock"));
if (stock > 0){
// 減庫存操作
Integer realStock = stock - 1;
this.stringRedisTemplate.opsForValue().set("stock", realStock + "");
System.out.println("扣減成功,剩余庫存:" + realStock);
}else {
System.out.println("扣減失敗,庫存不足!");
}
}finally {
if (clientId.equals(this.stringRedisTemplate.opsForValue().get(lockKey))){
// 釋放鎖
this.stringRedisTemplate.delete(lockKey);
}
}
return "執行完畢";
}
}
此時你們覺得程式沒有問題了嗎?
不一定,我們想象一個場景,如果我們程式執行到釋放鎖操作需要15秒,那么會發生什么?
那么程式還沒執行完,鎖就過期了,外部還有很多執行緒在等待著去搶鎖,
這問題其實很好解決,比如我們內部可以再啟一個子執行緒,定時器每隔一段時間(建議是過期時間的1/3)去重置當前key的失效時間,強行續命,也不用擔心消耗性等問題,因為只有一個執行緒在做檢查重置操作,
Redisson
Redisson簡介
Redisson是一個在Redis的基礎上實作的Java駐記憶體資料網格(In-Memory Data Grid),它不僅提供了一系列的分布式的Java常用物件,還提供了許多分布式服務,其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) Redisson提供了使用Redis的最簡單和最便捷的方法,Redisson的宗旨是促進使用者對Redis的關注分離(Separation of Concern),從而讓使用者能夠將精力更集中地放在處理業務邏輯上,
Redisson功能特性
①多種連接方式:同步、異步、異步流、管道流,
②資料序列化:多種資料序列化方式,
③集合資料分片:在集群模式下,Redisson為單個Redis集合型別提供了自動分片功能,單個集合被拆分后,均勻地分布在整個集群里,而不是擠在單一的一個節點里,
④分布式物件:Object Bucket(通用物件桶), Binary Stream(二進制流), Geospatial Bucket(地理空間物件), AtomicLong,AtomicDouble,Bloom Filter(布隆過濾器)...
⑤分布式集合:Map,Multimap,Set,SortedSet,List,Queue,雙端,堵塞,延遲佇列...
⑥分布式鎖:Reentrant Lock(可重入鎖),Fair Lock(公平鎖),ReadWrite Lock(讀寫鎖),CountDownLatch(閉鎖)),Semaphore(信號量)...
⑦分布式服務:Remote Service(分布式遠程服務),Scheduler Service(分布式調度任務服務),MapReduce,Executor Service...
基于Redisson實作分布式鎖
添加Redisson初始化配置類
@Configuration
public class RedissonConfig {
@Bean
public Redisson redisson(){
Config config = new Config();
// 單機模式
config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
return (Redisson) Redisson.create(config);
}
}
實作分布式鎖
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private Redisson redisson;
@GetMapping("deductStock")
public String deductStock(){
// 鎖的key,實際業務需要拼接
String lockKey = "product_mi10";
// 獲取鎖物件
RLock redissonLock = this.redisson.getLock(lockKey);
try {
// 加鎖
redissonLock.lock(10, TimeUnit.SECONDS);
// 查詢商品庫存數
Integer stock = Integer.parseInt(this.stringRedisTemplate.opsForValue().get("stock"));
if (stock > 0){
// 減庫存操作
Integer realStock = stock - 1;
this.stringRedisTemplate.opsForValue().set("stock", realStock + "");
System.out.println("扣減成功,剩余庫存:" + realStock);
}else {
System.out.println("扣減失敗,庫存不足!");
}
}finally {
// 釋放鎖
redissonLock.unlock();
}
return "執行完畢";
}
}
是不是感覺使用Redisson實作分布式鎖更加簡單,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/242359.html
標籤:其他
下一篇:蘇寧茅臺腳本
