使用redis實(shí)現(xiàn)分布式鎖

在高并發(fā)場景下進(jìn)行減庫存氏身,該應(yīng)用程序是分布式部署,使用nginx做負(fù)載均衡梆造,使用redis做分布式鎖

?使用Spirngboot開發(fā)測試

  • 1.首先搭建環(huán)境
    pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.redis</groupId>
    <artifactId>test-redis-lock</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.5.0</version>
        </dependency>
    </dependencies>

</project>

項(xiàng)目目錄

項(xiàng)目目錄

application.yaml文件

server:
  port: 8080  #服務(wù)端口
spring:
  redis:
    host: 180.76.244.226  #redis服務(wù)器地址

啟動(dòng)器

@SpringBootApplication
public class TestRedisApplication {
    public static void main(String[] args) {
        SpringApplication.run(TestRedisApplication.class);
    }
  • 2.編寫減庫存代碼
package com.test.controller;

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author lipeng
 * 時(shí)間: 2020-10-13 10:00
 * 描述:
 */
@RestController
@RequestMapping("/api/demo")
public class Demo2Controller {


    @Autowired
    private StringRedisTemplate stringRedisTemplate;



    @RequestMapping("/deduct_lock")
    public String deductLock() {
        
        synchronized (this) {
            //加鎖
            Integer stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//根據(jù)key 獲取redis中的值
            if (stock > 0) {
                int realStock = stock - 1;
                //將值再存入redis中
                stringRedisTemplate.opsForValue().set("stock", realStock + "");
                System.out.println("減庫存成功,剩余庫存數(shù):" + realStock);
            } else {
                System.out.println("當(dāng)前庫存數(shù)不足");
            }
        }
        return "end";
    }
}

  • 3.分布式部署項(xiàng)目
    由于使用的是Springboot疑务,所以分布式部署還是很方便的暗甥,我們只需改變端口號宝与,并重新運(yùn)行一遍main函數(shù)即可


    分布式部署

nginx負(fù)載均衡配置

upstream redislock{
    
    server 192.168.110.99:8080 weight=1;
    server 192.168.110.99:8081 weight=1;
}

server{
    listen 9000;
    server_name localhsot;

    location / {
    root html;
    index index.html index.html;
    proxy_pass http://redislock;
    }

}
  • 4.使用壓力測試進(jìn)行jmter進(jìn)行測試
    jmter

    我這里配置了200的并發(fā)量
    http請求

    之后就會發(fā)起請求,我們發(fā)現(xiàn)出現(xiàn)很多超賣請求
    焚廊。冶匹。。咆瘟。嚼隘。。袒餐。飞蛹。要給redis中存一個(gè)值,stock 我存的50灸眼,這里就不截圖了
  • 5.使用reids實(shí)現(xiàn)分布式鎖卧檐,并總結(jié)遇到的坑
package com.test.controller;

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author lipeng
 * 時(shí)間: 2020-10-12 8:59
 * 描述:高并發(fā)下減庫存的實(shí)現(xiàn)
 */
@RestController
@RequestMapping("/api/demo")
public class DemoController {


    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private Redisson redisson;


    @RequestMapping("/deduct_lock")
    public String deductLock() {
        String lockKey = "lockKey";
           /* Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "lipeng"); //使用redis實(shí)現(xiàn)1.0分布式鎖
            stringRedisTemplate.expire(lockKey,10, TimeUnit.SECONDS); //10s超時(shí)*/
    /*    String clientId= UUID.randomUUID().toString(); //創(chuàng)建標(biāo)識,判斷是否是自己還持有鎖
        //底層為原子塊執(zhí)行
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);
        if (!result) {
            return "error";
        }*/
        /**
         * -------------------------------------redis實(shí)現(xiàn)1.0版本的分布式鎖 bug 總結(jié)--------------------------------*
         * 1.當(dāng)?shù)谝粋€(gè)線程進(jìn)入時(shí)幢炸,拿到了鎖泄隔,但是在執(zhí)行過程中拋出了一個(gè)異常,這個(gè)時(shí)候鎖并沒有釋放 產(chǎn)生死鎖現(xiàn)象.
         * ----解決方案:加上一個(gè) try{} finally{}  使程序不管是否拋出異常 鎖必須釋放
         *2.在應(yīng)用執(zhí)行過程中宛徊,服務(wù)宕機(jī)了,此時(shí)該應(yīng)用已經(jīng)拿到鎖逻澳,但是并沒有釋放闸天,之后別的機(jī)器上過來的請求依然會直接返回,再次產(chǎn)生死鎖狀態(tài)
         *----解決方案:給這個(gè)鎖的key加一個(gè)超時(shí)時(shí)間斜做,10s后如果該key存在苞氮,直接自動(dòng)銷毀 stringRedisTemplate.expire(lockKey,10, TimeUnit.SECONDS);
         * 3.在程序執(zhí)行到給key加超時(shí)時(shí)間時(shí) 程序宕機(jī)了,又會產(chǎn)生死鎖
         *----解決方案:StringRedisTemplate提供了一個(gè)api:stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "lipeng", 10, TimeUnit.SECONDS)瓤逼;
         * 4.在高并發(fā)場景下笼吟,由于程序執(zhí)行時(shí)間超出失效時(shí)間導(dǎo)致鎖失效問題
         *----解決方案:創(chuàng)建一個(gè)標(biāo)識,在釋放鎖的時(shí)候霸旗,判斷是不是自己設(shè)置的鎖贷帮,如果是就釋放
         * 5.在高并發(fā)場景下,鎖的超時(shí)時(shí)間解決
         *----解決方案:使用redisson來實(shí)現(xiàn)分布式鎖(在加鎖成功后诱告,在后臺開啟一個(gè)線程撵枢,實(shí)現(xiàn)定時(shí)任務(wù),每隔10秒檢查是否還持有鎖如果持有則延長鎖的時(shí)間)
         */
        //獲取鎖
        RLock redissonLock = redisson.getLock(lockKey);
        try {
            //加鎖
            redissonLock.lock();
            Integer stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//根據(jù)key 獲取redis中的值
            if (stock > 0) {
                int realStock = stock - 1;
                //將值再存入redis中
                stringRedisTemplate.opsForValue().set("stock", realStock + "");
                System.out.println("減庫存成功,剩余庫存數(shù):" + realStock);
            } else {
                System.out.println("當(dāng)前庫存數(shù)不足");
            }
        } finally {
            //釋放鎖
            redissonLock.unlock();
            /*if(clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))){ //判斷是否是自己加的鎖精居,如果是自己加的鎖就釋放
                stringRedisTemplate.delete(lockKey);
            }*/
        }
        return "end";
    }

}

而最終我們選擇使用redisson,該框架對redis進(jìn)行了再次封裝锄禽,它的使用場景也多為分布式。

redisson分布式鎖實(shí)現(xiàn)原理
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末靴姿,一起剝皮案震驚了整個(gè)濱河市沃但,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌佛吓,老刑警劉巖宵晚,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件垂攘,死亡現(xiàn)場離奇詭異,居然都是意外死亡坝疼,警方通過查閱死者的電腦和手機(jī)搜贤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钝凶,“玉大人仪芒,你說我怎么就攤上這事「荩” “怎么了掂名?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長哟沫。 經(jīng)常有香客問我饺蔑,道長,這世上最難降的妖魔是什么嗜诀? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任猾警,我火速辦了婚禮,結(jié)果婚禮上隆敢,老公的妹妹穿的比我還像新娘发皿。我一直安慰自己,他們只是感情好拂蝎,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布穴墅。 她就那樣靜靜地躺著,像睡著了一般温自。 火紅的嫁衣襯著肌膚如雪玄货。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天悼泌,我揣著相機(jī)與錄音松捉,去河邊找鬼。 笑死券躁,一個(gè)胖子當(dāng)著我的面吹牛惩坑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播也拜,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼以舒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了慢哈?” 一聲冷哼從身側(cè)響起蔓钟,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎卵贱,沒想到半個(gè)月后滥沫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體侣集,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年兰绣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了世分。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,981評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缀辩,死狀恐怖臭埋,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情臀玄,我是刑警寧澤瓢阴,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站健无,受9級特大地震影響荣恐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜累贤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一叠穆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧臼膏,春花似錦痹束、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽屎媳。三九已至夺溢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間烛谊,已是汗流浹背风响。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留丹禀,地道東北人状勤。 一個(gè)月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像双泪,于是被迫代替她去往敵國和親持搜。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評論 2 355