Redis為什么性能高?
1贬蛙、Redis基于內(nèi)存的
2长搀、Redis基于單線程逼侦,較少線程上下文切換
3匿辩、Redis的基于NIO的多路復(fù)用機制
4、Redis底層多種數(shù)據(jù)結(jié)構(gòu)榛丢,得益于數(shù)據(jù)存儲結(jié)構(gòu)
使用redis原子性命令解決分布式鎖問題刨析
1铲球、保證加鎖LockKey唯一性
2、保證加鎖KEY和expire設(shè)置過期時間是一條原子性命令
3晰赞、finally {}語句塊中釋放鎖稼病,保證釋放是當前線程的Redis分布式鎖。在加鎖之前生成一個clientId
在最后再判斷當前clientId是否一致掖鱼,然后再釋放Redis分布式鎖
4然走、極限情況下,請查看如下代碼
finally {
// 在解鎖前添加一個uuid判斷當前刪除是否是當前請求的鎖
// (問題:判斷和刪除鎖不是原子性的戏挡,假設(shè)極限情況下key設(shè)置的過期時間是10秒)
if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
// 剛好執(zhí)行完這個判斷時候9.9秒芍瑞,突然線程阻塞或者垃圾回收,鎖過期了褐墅,這個時候其他線程并發(fā)仍然能加鎖成功
// 此時線程接著執(zhí)行刪除邏輯拆檬,釋放的是下一個線程的鎖,所有的問題都和鎖過期時間有關(guān)妥凳,所以需要鎖續(xù)命
// 5竟贯、釋放鎖(問題:如果異常系統(tǒng)宕機無法釋放鎖,所以在加鎖前要設(shè)置過期時間)
stringRedisTemplate.delete(lockKey);
}
如果在刪除鎖之前逝钥,Redis的Key剛好過期澄耍,也可能導(dǎo)致釋放其他線程鎖。
這里就需要給鎖設(shè)置續(xù)命規(guī)則
引入Watch Dog看門狗程序:
通過定時任務(wù),每隔10秒檢查鎖是否失效齐莲,對鎖進行續(xù)命
解決方案:Redisson框架的底層實現(xiàn)機制(下面代碼有示例)
總結(jié):Redis分布式鎖所有場景問題都和Key的過期時間有關(guān)聯(lián)
/**
* 使用redis原子性命令解決分布式鎖
*/
@PostMapping("/buyProduct1")
public String buyProduct1() {
// 1痢站、key代表商品唯一標識,保證永遠只會有一個請求成功獲取鎖
final String lockKey = "lockKey_product1";
final String clientId = UUID.randomUUID().toString();
// 2选酗、redis的setNx原子命令解決分布式鎖
// redis的setNx原子命令解決分布式鎖阵难,保證加鎖和過期時間設(shè)置原子性
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId);
// 給當前鎖設(shè)置過期時間,避免死鎖(問題:加鎖和設(shè)置過期時間不是原子操作)
stringRedisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);
if (Boolean.FALSE.equals(flag)) {
return null;
}
// 3芒填、返回 true代表獲取分布式鎖成功 (問題:假設(shè)過期時間設(shè)置過段呜叫,導(dǎo)致業(yè)務(wù)邏輯未執(zhí)行完畢,stringRedisTemplate.delete刪除別的請求線程鎖)
// 15s-->10s --> 5s / 8s-->5s --> 3s/ 6s-->5s --> 1s 假設(shè)多線程高并發(fā)請求殿衰,后續(xù)線程鎖會一直被釋放朱庆,導(dǎo)致鎖失效
// 問題:自己加的鎖,被別的請求刪掉了(解決方案闷祥,在解鎖前添加一個uuid判斷當前刪除是否是當前請求的鎖)
try {
// 從Redis獲取庫存
int stock = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("stock")));
if (stock <= 0) {
System.out.println("扣減庫存失敗娱颊,庫存不足");
return null;
}
// 4、扣減完畢直接更新庫存
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", String.valueOf(realStock));
System.out.println("扣減庫存成功凯砍,剩余庫存realStock = " + realStock);
} catch (NumberFormatException e) {
e.printStackTrace();
} finally {
// 在解鎖前添加一個uuid判斷當前刪除是否是當前請求的鎖
// (問題:判斷和刪除鎖不是原子性的箱硕,假設(shè)極限情況下key設(shè)置的過期時間是10秒)
if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
// 剛好執(zhí)行完這個判斷時候9.9秒,突然線程阻塞或者垃圾回收悟衩,鎖過期了剧罩,這個時候其他線程并發(fā)仍然能加鎖成功
// 此時線程接著執(zhí)行刪除邏輯,釋放的是下一個線程的鎖座泳,所有的問題都和鎖過期時間有關(guān)惠昔,所以需要鎖續(xù)命
// 5、釋放鎖(問題:如果異常系統(tǒng)宕機無法釋放鎖挑势,所以在加鎖前要設(shè)置過期時間)
stringRedisTemplate.delete(lockKey);
}
}
return null;
}
Redisson實現(xiàn)分布式鎖解決鎖續(xù)命問題
/**
* 使用redis原子性命令解決分布式鎖
*/
@PostMapping("/buyProduct1")
public String buyProduct1() {
// 1镇防、key代表商品唯一標識,保證永遠只會有一個請求成功獲取鎖
final String lockKey = "lockKey_product1";
// 2薛耻、獲取鎖對象
RLock redissonLock = redisson.getLock(lockKey);
redissonLock.lock();
try {
// 從Redis獲取庫存
int stock = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("stock")));
if (stock <= 0) {
System.out.println("扣減庫存失敗营罢,庫存不足");
return null;
}
// 扣減完畢直接更新庫存
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", String.valueOf(realStock));
System.out.println("扣減庫存成功赏陵,剩余庫存realStock = " + realStock);
} catch (NumberFormatException e) {
e.printStackTrace();
} finally {
// 3饼齿、釋放鎖
redissonLock.unlock();
}
return null;
}
Redisson實現(xiàn)分布式鎖原理
1、加鎖機制
線程去獲取鎖蝙搔,獲取成功: 執(zhí)行 lua腳本缕溉,保存數(shù)據(jù)到 redis數(shù)據(jù)庫。
2吃型、watch dog自動延期機制
在一個分布式環(huán)境下证鸥,假如一個線程獲得鎖后,突然服務(wù)器宕機了,那么這個時候在一定時間后這個鎖會自動釋放枉层,你也可以設(shè)置鎖的有效時間(不設(shè)置默認30秒)泉褐,這樣的目的主要是防止死鎖的發(fā)生。
但在實際開發(fā)中會有下面一種情況:
1 //設(shè)置鎖1秒過去
2 redissonLock.lock("redisson", 1);
3 /**
4 * 業(yè)務(wù)邏輯需要咨詢2秒
5 */
6 redissonLock.release("redisson");
7
8 /**
9 * 線程1 進來獲得鎖后鸟蜡,線程一切正常并沒有宕機膜赃,但它的業(yè)務(wù)邏輯需要執(zhí)行2秒,這就會有個問題揉忘,在 線程1 執(zhí)行1秒后跳座,這個鎖就自動過期了,
10 * 那么這個時候 線程2 進來了泣矛。那么就存在 線程1和線程2 同時在這段業(yè)務(wù)邏輯里執(zhí)行代碼疲眷,這當然是不合理的。
11 * 而且如果是這種情況您朽,那么在解鎖時系統(tǒng)會拋異常狂丝,因為解鎖和加鎖已經(jīng)不是同一線程了
12 */
所以這個時候看門狗就出現(xiàn)了,它的作用就是 線程1 業(yè)務(wù)還沒有執(zhí)行完虚倒,時間就過了美侦,線程1 還想持有鎖的話,就會啟動一個 watch dog后臺線程魂奥,不斷的延長鎖 key的生存時間菠剩。
注意:正常這個看門狗線程是不啟動的,還有就是這個看門狗啟動后對整體性能也會有一定影響耻煤,所以不建議開啟看門狗具壮。
Redis分布式鎖提升性能的方案
1、通過控制代碼層加鎖的粒度哈蝇,提升性能
2棺妓、通過分段鎖提升性能。