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壓測軟件,進行配置請求來測試會不會超賣等情況
④、查看控制檯打印結果,發現兩種情況都能控制住扣減庫存的問題,而且也在扣減完庫存後將鎖刪掉了~