解決Redis超賣問題

第一種:使用synchronized(只適用於單個tomcat)

@Autowired
private StringRedisTemplate stringRedisTemplate;
@GetMapping("/deduct")
    public String toDeduct(){
        synchronized (this) {
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                Long stock1 = stringRedisTemplate.opsForValue().decrement("stock");
                System.out.println("扣減成功,剩餘庫存:" + stock1);
            }else{
                System.out.println("扣減失敗,庫存不足");
            }
            return "end";
        }
    }

第二種:使用SETNX命令(多臺tomcat)

 @GetMapping("/deduct_stock")
    public String deductStock() throws InterruptedException {
        String lockKey = "lockKey";
        //設計唯一key值防止鎖失效問題
        String clientId = UUID.randomUUID().toString();
        //設計鎖超時時間,防止服務器宕機時鎖沒有釋放掉(finally語句沒有執行)
        Boolean result = stringRedisTemplate.opsForValue().
                setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);//jedis.setnx(key,value);
        //若鍵 key 已經存在, 則 SETNX 命令不做任何動作。result==false
        if (!result) {
            return "正在排隊。。。";
        }
        try {
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                Long realStock = stringRedisTemplate.opsForValue().decrement("stock");
                System.out.println("扣減成功,剩餘庫存:" + realStock);
            }else{
                System.out.println("扣減失敗,庫存不足");
            }
        } finally {
            //防止鎖失效問題,在多線程的情況下,每個線程只釋放自己創建的鎖,線程之間互不干預。
            if(clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
                stringRedisTemplate.delete(lockKey);
            }
        }
        return "end";
    }

第三種:使用Redission框架(原理跟第二種一樣)

1、配置config類

@Configuration
public class RedissonConfig {

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private String port;

    @Value("${spring.redis.password}")
    private String password;

    @Bean
    public RedissonClient getRedisson(){
        Config config = new Config();
        config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(password);
        //添加主從配置
//      config.useMasterSlaveServers().setMasterAddress("").setPassword("").addSlaveAddress(new String[]{"",""});
        return Redisson.create(config);
    }
}
@GetMapping("/deduct_stock1")
    public String deductStock1(){
        String lockKey = "lockKey";
        RLock redissonLock = redisson.getLock(lockKey);
        try {
            redissonLock.lock();
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                Long realStock = stringRedisTemplate.opsForValue().decrement("stock");
                System.out.println("扣減成功,剩餘庫存:" + realStock);
            }else{
                System.out.println("扣減失敗,庫存不足");
            }
        } finally {
            redissonLock.unlock();
        }
        return "end";
    }

Redission原理:

Redission使用了Lua 腳本,實現原理跟原理跟第二種差不多。
當業務代碼處理時間很長,像mysql慢查詢等等,可以在try代碼塊中每隔10秒檢查是否還持有鎖,如果持有則延長鎖的時間,防止還沒執行完業務代碼“lockKey”就到了失效時間。
在這裏插入圖片描述


測試環境搭建

Redis命令大全

設置Redis密碼
在這裏插入圖片描述
pom:

		<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.7</version>
        </dependency>

application.properties:

server.port=8090
# redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=888888
spring.redis.jedis.pool.max-active=500
spring.redis.jedis.pool.max-idle=1000
spring.redis.jedis.pool.max-wait=6000ms
spring.redis.jedis.pool.min-idle=4

Nginx負載均衡:

 upstream redislock{
	server 127.0.0.1:8080 weight = 1;
	server 127.0.0.1:8090 weight = 1;
    }
    server {
        listen       80;
        server_name  localhost;
   		location / {
		root html;
		index index.html index.htm;
		proxy_pass http://redislock;
        }
    }

Jmeter壓力測試

在這裏插入圖片描述
發送http請求:
在這裏插入圖片描述
設置發送地址:
在這裏插入圖片描述
表示在0秒內發送200個請求,處理完後再發送200 * 3次,總計200 * 4次請求:
在這裏插入圖片描述
結果集:
在這裏插入圖片描述

測試結果:
在這裏插入圖片描述
在這裏插入圖片描述
沒有出現超賣問題

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