如何保證接口冪等性:
1,查詢與刪除操作是天然冪等操作。
2,新增時,根據某個字段做唯一性判斷,比如某個用戶只會有一條記錄這樣的類似判斷。(實用性差)
3,悲觀鎖,select from where 主鍵=’’ for update
4,樂觀鎖,update user set money = money + 1 where id=2 and money = 1;在字段自加一的時候,加上該字段的等值索引
3與4,如果沒有加主鍵id做索引,會鎖表,會影響該表其他功能。
【上訴兩種不好用,併發會使程序報錯,如同時要買掉一件商品,一件沒扣成功則進程失去反應】
5,分佈式鎖:使用Redis、Memcached、Zookeeper、Chubby等第三方程序
Redis:
①set(key, value, “NX”,“EX”,internalLockLeaseTime):使用set設置鍵值,以“id_後綴”等規範作爲key,以當前線程id作爲value。插入一條記錄,並設置鎖超時時間30秒。
如果插入成功,返回1,以此聲明成功獲得鎖,其他進程插入返回0說明該key已存在,獲取鎖失敗。成功則進入插入或者更新操作。
②釋放鎖,爲了避免誤刪其他線程的鎖,刪除時增加判斷if(threadId .equals(redisClient.get(key))){ del(key) }刪除鍵值對,del(id_後綴)
③守護線程,同線程只要不崩潰,會一直爲其增加有效期,如果崩了,則不會增加,redis到時間自動釋放
實操:
一個商品多人同時購買,同時觸發buyCommodity()方法,兩個進程併發,此時,上鎖對象爲商品(判斷清楚對象很重要,涉及到key的設計,處理併發),即,key值爲商品id+"_buy_lock",此時,A線程比B線程提前得到鎖(提前set好了),那麼B線程set失敗,進入循環獲取,持續時間內沒有獲取到,則視爲失敗。
本人封裝一個redis鎖工具,並展示測試類:
package com.fjhb;
import redis.clients.jedis.Jedis;
/**
* redis鎖
* Author:FangKunSen
* Time:2020-05-29,15:35
*/
public class JLock {
private String HOST;
Jedis jedis;
protected long internalLockLeaseTime = 30;//鎖過期時間
private long timeout = 20000; //獲取鎖的超時時間
private long SLEEP = 2000;//休息2秒重新獲取
public JLock(String HOST) {
this.HOST = HOST;
jedis = new Jedis(HOST);
}
/**
* 上鎖
* @param key 建議以上鎖對象id爲標識,加以識別後綴,例如 123456_commodityId_lock
* @param value 以線程id爲值,防止解鎖時解到其他線程的鎖
* @return
*/
public boolean lock(String key,String value){
Long start = System.currentTimeMillis();
try{
for(;;){
//SET命令返回OK ,則證明獲取鎖成功
String lock = jedis.set(key, value, "NX","EX",internalLockLeaseTime);
if("OK".equals(lock)){
return true;
}
System.out.println("未取到鎖,正在重新取鎖...");
//否則循環等待,在timeout時間內仍未獲取到鎖,則獲取失敗
long l = System.currentTimeMillis() - start;
if (l>=timeout) {
System.out.println("規定時間內未取到鎖,失敗");
return false;
}
try {
Thread.sleep(SLEEP);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}finally {
jedis.close();
}
}
/**
* 獲取鎖值,校驗用,目前值爲進程id
* @param key 建議以上鎖對象id爲標識,加以識別後綴,例如 123456_commodityId_lock
* @return
*/
public String get(String key){
return jedis.get(key);
}
/**
* 解指定線程id的鎖
* @param key 建議以上鎖對象id爲標識,加以識別後綴,例如 123456_commodityId_lock
* @param value 以線程id爲值,防止解鎖時解到其他線程的鎖
* @return
*/
public boolean unLock(String key, String value){
String threadId=get(key);
if(threadId.equals(value)){
jedis.del(key);
System.out.println("解鎖成功,下個繼續");
return true;
}
System.out.println("解鎖失敗");
return false;
}
}
@Test
public void test() {
JLock jLock = new JLock("127.0.0.1");
String key="10101010_commodityId";
String threadId = String.valueOf(Thread.currentThread().getId());
if(jLock.lock(key,threadId)){
System.out.println("取鎖成功,執行代碼,線程id:"+threadId);
for(int i = 0;i<10;i++){
System.out.println(i+1);
}
System.out.println("代碼執行完畢,正在關閉鎖");
jLock.unLock(key,threadId);
}else{
System.out.println("超時未取到");
}
}