一、分布式鎖介紹
由于傳統的鎖是基于Tomcat服務器的內部物件,搭建了集群之后,導致鎖失效,所以要使用分布式鎖來處理↓
| 分布式鎖介紹 |
|---|
![]() |
二、分布式鎖解決方案
3.1 搭建環境
創建SpringBoot工程,起名為秒殺second-kill,勾選web依賴↓
搶購的業務邏輯代碼↓
package com.ljh.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
public class SecondKillController {
//1. 準備商品的庫存
public static Map<String,Integer> itemStock = new HashMap<>();
//2. 準備商品的訂單
public static Map<String,Integer> itemOrder = new HashMap<>();
static{
itemStock.put("牙刷",10000);
itemOrder.put("牙刷",0);
}
@GetMapping("/kill")//item表示用戶訪問的商品名稱,等會瀏覽器地址欄我會輸入牙刷
public String kill(String item) throws InterruptedException {
//1. 減庫存
Integer stock = itemStock.get(item);
if(stock <= 0){
return "商品庫存數不足!!!";
}
Thread.sleep(100);//用睡眠來模擬消耗時間
itemStock.put(item,stock-1);
//2. 創建訂單
Thread.sleep(100);
itemOrder.put(item,itemOrder.get(item)+1);
//3. 回傳資訊
return "搶購成功!!!" + item + ": 剩余庫存數為" + itemStock.get(item) + ",訂單數為" + itemOrder.get(item);
}
}
用瀏覽器訪問我們的控制器方法,庫存和訂單確實加起來是10000,沒有超賣,但是我們別忘了還有其他用戶的訪問↓
http://localhost:8080/kill?item=牙刷

下載ab壓力測驗工具軟體,解壓軟體即可,找到bin目錄,cmd打開,輸入下面地址來模擬1000個請求和500并發↓
下載鏈接:https://pan.baidu.com/s/1yHeVgXN94kram3Jxj5V13w
提取碼:6qfhab -n 請求數 -c 并發數 訪問的路徑(復制瀏覽器地址過來,瀏覽器地址欄對牙刷進行了url編碼所以是這樣的↓) ab -n 1000 -c 500 http://localhost:8080/kill?item=%E7%89%99%E5%88%B7
enter回車,看到執行完了請求和并發,然后打開瀏覽器地址值,再訪問發現庫存和訂單加起來不是10000,超賣了↓
3.2 Zookeeper實作分布式鎖原理圖解
| Zookeeper實作分布式鎖原理圖解 |
|---|
![]() |
這里用臨時節點有個好處,斷開連接,鎖資源就會消失,不用設定過期時間,而下面用redis做分布式鎖需要過期時間
3.3 Zookeeper實作分布式鎖代碼實作
在上面寫的秒殺工程里面匯入依賴來解決問題
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.0</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
<exclusion>
<artifactId>slf4j-log4j12</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
<exclusion>
<artifactId>log4j</artifactId>
<groupId>log4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.7.1</version>
</dependency>
配置類
package com.ljh.config;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ZkConfig {
@Bean
public CuratorFramework cf(){
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000,2);//需要指定重試間隔和重試次數
CuratorFramework curatorFramework = CuratorFrameworkFactory.builder()
.connectString("10.20.159.39:2181,10.20.159.39:2182,10.20.159.39:2183")
.retryPolicy(retryPolicy)
.build();
curatorFramework.start();
return curatorFramework;
}
}
在業務代碼中添加分布式鎖
InterProcessMutex lock = new InterProcessMutex(cf,"/lock");
//加鎖
lock.acquire();
lock.acquire(1,TimeUnit.SECONDS); //指定排隊多久放棄獲取鎖資源
//----------------業務邏輯代碼------------------------
//釋放鎖
lock.release();
package com.ljh.controller;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@RestController
public class SecondKillController {
//1. 準備商品的庫存
public static Map<String,Integer> itemStock = new HashMap<>();
//2. 準備商品的訂單
public static Map<String,Integer> itemOrder = new HashMap<>();
static{
itemStock.put("牙刷",10000);
itemOrder.put("牙刷",0);
}
@Autowired
private CuratorFramework cf;
@GetMapping("/kill")//item表示用戶訪問的商品名稱,等會瀏覽器地址欄我會輸入牙刷
public String kill(String item) throws Exception {
InterProcessMutex lock = new InterProcessMutex(cf,"/lock");
//加鎖
lock.acquire();
//lock.acquire(1,TimeUnit.SECONDS); //指定排隊多久放棄獲取鎖資源
//----------------業務邏輯代碼↓------------------------
//1. 減庫存
Integer stock = itemStock.get(item);
if(stock <= 0){
return "商品庫存數不足!!!";
}
Thread.sleep(100);//用睡眠來模擬消耗時間
itemStock.put(item,stock-1);
//2. 創建訂單
Thread.sleep(100);
itemOrder.put(item,itemOrder.get(item)+1);
//----------------業務邏輯代碼↑------------------------
//釋放鎖
lock.release();
//3. 回傳資訊
return "搶購成功!!!" + item + ": 剩余庫存數為" + itemStock.get(item) + ",訂單數為" + itemOrder.get(item);
}
}
package com.ljh.controller;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@RestController
public class SecondKillController2 {
//1. 準備商品的庫存
public static Map<String,Integer> itemStock = new HashMap<>();
//2. 準備商品的訂單
public static Map<String,Integer> itemOrder = new HashMap<>();
static{
itemStock.put("牙刷",10000);
itemOrder.put("牙刷",0);
}
@Autowired
private CuratorFramework cf;
@RequestMapping(value = "/seckill")
public String seckill(String gname) throws Exception {
InterProcessMutex interProcessMutex = new InterProcessMutex(cf, "/seckill_lock");
//獲取鎖
//interProcessMutex.acquire();//沒有獲取到鎖就一直等待
if(interProcessMutex.acquire(1, TimeUnit.SECONDS)){//等待1s就放棄獲取鎖資源
//1.先減庫存
Integer stock = itemStock.get(gname);
if (stock <= 0) {
return "庫存不足,,,,";
}
itemStock.put(gname, stock - 1);
Thread.sleep(100);
//2.加訂單數量
Integer count = itemOrder.get(gname);
itemOrder.put(gname, count + 1);
Thread.sleep(100);
//釋放鎖
interProcessMutex.release();
return "搶購成功【" + gname + "】,庫存:" + itemStock.get(gname) + ",訂單:" + itemOrder.get(gname);
}else{
return "請稍后再試";
}
}
}
修改完代碼重啟秒殺工程,發現不管用瀏覽器訪問,還是用之前的ab壓測工具軟體訪問,庫存和訂單加起來都是10000
注意,如果啟動報下面這個錯,要重啟防火墻,然后關閉防火墻↓


systemctl start firewalld
systemctl stop firewalld
3.4 Redis實作分布式鎖原理圖解
| Redis實作分布式鎖原理 |
|---|
![]() |
這里出現了例外不走后面釋放鎖資源的代碼怎么辦?簡單,為鎖資源設定過期時間來防止死鎖的發生
3.5 Redis實作分布式鎖代碼實作
上面的秒殺工程匯入redis依賴,添加組態檔
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
# yml組態檔,配置redis服務器的ip和埠↓
spring:
redis:
host: xx.xx.xx.xx #redis服務器的ip地址
port: xxxx #redis埠
工具類
package com.ljh.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class RedisLockUtil {
@Autowired
private StringRedisTemplate redisTemplate;
public boolean lock(String key,String value,int second){
return redisTemplate.opsForValue().setIfAbsent(key,value,second, TimeUnit.SECONDS);
}
public void unlock(String key){
redisTemplate.delete(key);
}
}
修改業務邏輯代碼
@Autowired
private RedisLockUtil lock;
@GetMapping("/redis/kill")
public String redisKill(String item) throws Exception {
//加鎖
if(lock.lock(item,System.currentTimeMillis() + "",1)){
//業務代碼...
//釋放鎖
lock.unlock(item);
}
}
package com.ljh.controller;
import com.ljh.utils.RedisLockUtil;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@RestController
public class SecondKillController {
//1. 準備商品的庫存
public static Map<String,Integer> itemStock = new HashMap<>();
//2. 準備商品的訂單
public static Map<String,Integer> itemOrder = new HashMap<>();
static{
itemStock.put("牙刷",10000);
itemOrder.put("牙刷",0);
}
@Autowired
private RedisLockUtil lock;
@GetMapping("/kill")//item表示用戶訪問的商品名稱,等會瀏覽器地址欄我會輸入牙刷
public String kill(String item) throws Exception {
//加鎖
if(lock.lock(item,System.currentTimeMillis() + "",1)){
//業務代碼
//----------------業務邏輯代碼↓------------------------
//1. 減庫存
Integer stock = itemStock.get(item);
if(stock <= 0){
return "商品庫存數不足!!!";
}
Thread.sleep(100);//用睡眠來模擬消耗時間
itemStock.put(item,stock-1);
//2. 創建訂單
Thread.sleep(100);
itemOrder.put(item,itemOrder.get(item)+1);
//----------------業務邏輯代碼↑------------------------
//釋放鎖
lock.unlock(item);
}
//3. 回傳資訊
return "搶購成功!!!" + item + ": 剩余庫存數為" + itemStock.get(item) + ",訂單數為" + itemOrder.get(item);
}
}
修改完代碼重啟秒殺工程,發現不管用瀏覽器訪問,還是用之前的ab壓測工具軟體訪問,庫存和訂單加起來都是10000
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/290027.html
標籤:其他
下一篇:Hive-安裝







