這可能是B站講的最好的Redis實(shí)戰(zhàn)教程(2023年最新版)


synchronized只能保證在單一tomcat容器內(nèi)線(xiàn)程安全,若是分布式微服務(wù)并發(fā)訪(fǎng)問(wèn)這個(gè)接口,還是會(huì)出現(xiàn)超賣(mài)的問(wèn)題
@RestController
public class Indexcontroller {
@Autowired
private Redisson redisson;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/deduct_stock")
public string deductstock(){
synchronized (this) {
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get("stock")
if (stock >0){
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock",realStock + "");//jedis.set(key , value)
System.out.println("扣減成功,剩余庫(kù)存:" + realStock);
}else {
System.out.println("扣減失敗,庫(kù)存不足");
}
}
return "end";
}
}
解決方案--分布式鎖

@RestController
public class Indexcontroller {
@Autowired
private Redisson redisson;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/deduct_stock")
public string deductstock(){
//分布式鎖的key值
String lockKey = "lock:product:101";
//分布式鎖
//設(shè)置key超時(shí)時(shí)間,避免系統(tǒng)執(zhí)行業(yè)務(wù)時(shí)宕機(jī)導(dǎo)致死鎖
boolean result= stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"zhuge",10,TimeUnit.SECONDS);
if(!result){
return "error_code";
}
try{
//獲取庫(kù)存
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get("stock")
if (stock >0){
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock",realStock + "");//jedis.set(key , value)
System.out.println("扣減成功,剩余庫(kù)存:" + realStock);
}else {
System.out.println("扣減失敗,庫(kù)存不足");
}
}finally{
//釋放分布式鎖
stringRedisTemplate.delete(lockKey);
}
return "end";
}
}
新問(wèn)題:1、在10s時(shí)間內(nèi),庫(kù)存操作還沒(méi)有完成,但分布式鎖已經(jīng)超時(shí)釋放,當(dāng)執(zhí)行釋放鎖的操作時(shí),會(huì)報(bào)空指針;
2、當(dāng)線(xiàn)程1在線(xiàn)程2執(zhí)行加鎖之后,將鎖釋放,會(huì)導(dǎo)致線(xiàn)程2的分布式鎖失效
解決方法如下:
public class Indexcontroller {
@Autowired
private Redisson redisson;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/deduct_stock")
public string deductstock(){
//分布式鎖的key值
String lockKey = "lock:product:101";
//區(qū)分是不同的請(qǐng)求來(lái)保證分布式鎖
String clientId = UUID.randomUUID().toString();
//分布式鎖
//設(shè)置key超時(shí)時(shí)間,避免系統(tǒng)執(zhí)行業(yè)務(wù)時(shí)宕機(jī)導(dǎo)致死鎖
boolean result= stringRedisTemplate.opsForValue().setIfAbsent(lockKey,clientId,10,TimeUnit.SECONDS);
if(!result){
return "error_code";
}
try{
//獲取庫(kù)存
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get("stock")
if (stock >0){
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock",realStock + "");//jedis.set(key , value)
System.out.println("扣減成功,剩余庫(kù)存:" + realStock);
}else {
System.out.println("扣減失敗,庫(kù)存不足");
}
}finally{
if(clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))){
//釋放分布式鎖
stringRedisTemplate.delete(lockKey);
}
}
return "end";
}
}
新問(wèn)題:如果當(dāng)線(xiàn)程1請(qǐng)求9.9s之后執(zhí)行到31行代碼,下一個(gè)請(qǐng)求在設(shè)置key的瞬間,線(xiàn)程1執(zhí)行33行代碼,導(dǎo)致下一次請(qǐng)求鎖失效;
所以釋放分布式鎖需保證原子性。以上所有問(wèn)題都是由key的失效時(shí)間導(dǎo)致,為解決這個(gè)問(wèn)題出現(xiàn)了鎖續(xù)命方法,在加鎖
之后開(kāi)啟一個(gè)子線(xiàn)程執(zhí)行每10s的時(shí)間間隔內(nèi)來(lái)判斷鎖是否存在,如果存在,將鎖超時(shí)時(shí)間設(shè)置為30s。
最強(qiáng)分布式鎖工具:Redisson
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.11.1</version>
</dependency>

public class Indexcontroller {
@Autowired
private Redisson redisson;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/deduct_stock")
public string deductstock(){
//分布式鎖的key值
String lockKey = "lock:product:101";
//Redisson鎖對(duì)象
RLock redissonLock = redisson.getLock(lockKey);
//加鎖
redissonLock.lock();
try{
//獲取庫(kù)存
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get("stock")
if (stock >0){
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock",realStock + "");//jedis.set(key , value)
System.out.println("扣減成功,剩余庫(kù)存:" + realStock);
}else {
System.out.println("扣減失敗,庫(kù)存不足");
}
}finally{
//釋放分布式鎖
redissonLock.lock();
}
return "end";
}
}
問(wèn)題:當(dāng)java程序加鎖之后,redis主節(jié)點(diǎn)直接宕機(jī),分布式鎖信息被沒(méi)有同步給從節(jié)點(diǎn),如何保證redis分布式鎖不失效?
RedLock可以解決鎖丟失問(wèn)題(zookeeper也可以實(shí)現(xiàn)分布式鎖)
RedLock實(shí)現(xiàn)原理

@RestController
public class IndexController {
@Resource
private Redisson redisson;
@Resource
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/deduct_stock")
public String deductStock(){
//分布式鎖的key值
String lockKey = "lock:product:101";
//Redisson鎖對(duì)象
RLock redissonLock = redisson.getLock(lockKey);
//加鎖
redissonLock.lock();
try{
//獲取庫(kù)存 //相當(dāng)于jedis.get("stock")
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock >0){
int realStock = stock - 1;
//相當(dāng)于jedis.set(key , value)
stringRedisTemplate.opsForValue().set("stock",realStock + "");
System.out.println("扣減成功,剩余庫(kù)存:" + realStock);
}else {
System.out.println("扣減失敗,庫(kù)存不足");
}
}finally{
//釋放分布式鎖
redissonLock.lock();
}
return "end";
}
@RequestMapping("/redlock")
public String redlock(){
//分布式鎖的key值
String lockKey = "lock:product:101";
//這里需要自己實(shí)例化不同redis實(shí)例的redisson客戶(hù)端連接,這里只是偽代碼用一個(gè)redisson客戶(hù)端簡(jiǎn)化了
RLock lock1 = redisson.getLock(lockKey);
RLock lock2 = redisson.getLock(lockKey);
RLock lock3 = redisson.getLock(lockKey);
//根據(jù)多個(gè) RLock 對(duì)象構(gòu)建 RedissonRedLock (最核心的差別就在這里)
RedissonRedLock redLock = new RedissonRedLock(lock1,lock2,lock3);
try {
/**
* waitTimeout 嘗試獲取鎖的最大等待時(shí)間,超過(guò)這個(gè)值,則認(rèn)為獲取鎖失敗
* LeaseTime鎖的持有時(shí)間,超過(guò)這個(gè)時(shí)間鎖會(huì)自動(dòng)失效(值應(yīng)設(shè)置為大于業(yè)務(wù)處理的時(shí)間,確保在鎖有效期內(nèi)業(yè)務(wù)能處理完)
*/
boolean res = redLock.tryLock(10, 30, TimeUnit.SECONDS);
if (res) {
//成功獲得鎖,在這里處理業(yè)務(wù)
}
} catch (Exception e) {
throw new RuntimeException("lock fail");
} finally {
//無(wú)論如何,最后都要解鎖
redLock.unlock();
}
return "end";
}
}