redis分佈式鎖實現案例之redisson和StringRedisTemplate

redis實現分佈式鎖,單機情況下加synchronize關鍵字就ok了~,但是分佈式情況下就會出現問題,一個簡單的扣減庫存問題來做分佈式鎖的demo~~

1、先添加pom依賴,我這裏就將redis和redisson的依賴一起引入了

        <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.6.5</version>
        </dependency>

2、構建yml配置文件,添加redis配置,這只是爲了redis配置的,redisson框架不需要,後面需要用到改端口號,起兩個服務來做分佈式環境下測試~

server:
  port: 8081

spring:
  # Redis 配置
  redis:
    database: 0  #數據庫索引(默認爲0)
    host: 127.0.0.1
    port: 6379  #默認鏈接端口
    password:  #默認爲空
    jedis:
      pool:
        max-active: 8 #最大鏈接池
        max-wait: -1 #最大阻賽等待時間(使用負值沒有限制)默認爲-1
        max-idle: 8 #連接池中的最大空閒連接 默認 8
        min-idle: 0

3、構建主啓動類

package com.king;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * created by king on 2020/6/8 11:45 上午
 */
@SpringBootApplication
public class LockRedisMain6633 {
    public static void main(String[] args) {
            SpringApplication.run(LockRedisMain6633.class,args);
        }
}

4、下面先放StringRedisTemplate的案例代碼

package com.king.controller;

import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * created by king on 2020/6/8 8:01 下午
 */
@RestController
@Slf4j
public class RedisLockController {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 1、加setIfAbsent是爲了setnx來判斷能不能獲取鎖
     * 2、加expire時間是爲了防止宕機等情況造成死鎖
     * 3、加value的equals判斷是爲了防止誤刪鎖
     * 4、但是要是完全很好的實現分佈式鎖,還需要開啓守護線程去動態續航expire時間,防止過期,下面引入了redisson框架,
     *    框架包含一個watchdog看門狗,可以去開啓一個守護線程去動態續航
     * @return
     */
    @GetMapping("decreseStorage1/")
    public String decreaseStorage1() {
        String lockkey = "sku001";
        String value = Thread.currentThread().getName();
//        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockkey, value);
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockkey, value, 10, TimeUnit.SECONDS);
//        log.info("result:"+result);

        if (!result) {
            return "error";
        }
        try {
            int storage = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (storage > 0) {
                int still = storage - 1;
                stringRedisTemplate.opsForValue().set("stock", still + "");
                System.out.println("扣減庫存成功,目前庫存剩餘量爲:" + still);
            } else {
                System.out.println("扣減庫存失敗,庫存不足");
            }
        } finally {
            if (value.equals(stringRedisTemplate.opsForValue().get(lockkey))) {
                stringRedisTemplate.delete(lockkey);
            }
        }
        return "end";
    }


}

5、如果使用redisson框架來實現的話

package com.king.controller;

import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * created by king on 2020/6/8 8:01 下午
 */
@RestController
@Slf4j
public class RedisLockController {

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private Redisson redisson;

    /**
     * 1、redisson框架實現分佈式鎖,自帶watchdog看門狗
     *
     * @return
     */
    @GetMapping("decreseStorage/")
    public String decreaseStorage() {
        String lockkey = "sku001";
        RLock lock = redisson.getLock(lockkey);

        lock.lock(30,TimeUnit.SECONDS);
        try{
            int storage = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (storage > 0) {
                int still = storage - 1;
                stringRedisTemplate.opsForValue().set("stock", still + "");
                System.out.println("扣減庫存成功,目前庫存剩餘量爲:" + still);
            } else {
                System.out.println("扣減庫存失敗,庫存不足");
            }
        } finally {
            lock.unlock();
        }
        return "end";
    }

6、進行測試,先準備測試環境,

①、啓動8081和8082兩個端口,即修改端口啓動微服務

②、配置host ip redislock.com ,並配置nginx的配置文件設置反向代理微服務8081和8082,並實現負載均衡

upstream redis {   
         server 192.168.0.153:8081 weight=1 ;   
         server 192.168.0.153:8082 weight=1 ;
    }


    
     server{
        listen 80;
        charset utf-8;
        server_name redislock.com;

        location  ~* \.(php|jsp|cgi|asp|aspx)$
            {
                proxy_pass http://redis;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header REMOTE-HOST $remote_addr;
            }

        location /
            {
                proxy_pass http://redis;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header REMOTE-HOST $remote_addr;

                add_header X-Cache $upstream_cache_status;
                
                #Set Nginx Cache
                
                    add_header Cache-Control no-cache;
            }

     }

③、啓動jmeter壓測軟件,進行配置請求來測試會不會超賣等情況

④、查看控制檯打印結果,發現兩種情況都能控制住扣減庫存的問題,而且也在扣減完庫存後將鎖刪掉了~

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章