|
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框架来实现的话:
注意:要防止watchdog未生效问题
- redisson的自动续期,如果业务超长,运行期间自动续上30s,不用担心业务时间长,锁自动过期被删掉
- 加锁得业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30s后自动删除
- //lock.lock(10, TimeUnit.SECONDS);//10秒以后自动解锁,自动解锁时间一定要大于业务时间
- // lock.lock(10, TimeUnit.SECONDS)在锁时间到了以后,不会自动续期
- //1.如果我们传递了锁的超时时间,就发送给redis执行脚本,进行占锁,默认时间就是我我们传递的时间
- //2.如果我们未制定锁的超时时间,就使用lockWatchdogTimeout = 30 * 1000;看门狗默认时间
- //只要占锁成功,就会启动定时任务(重新给锁设置过期时间)新的时间就是看门狗的默认时间,每10秒,都会自动续期续成满时间
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);
//2。加锁
// lock.lock();
//1).redisson的自动续期,如果业务超长,运行期间自动续上30s,不用担心业务时间长,锁自动过期被删掉
//2).加锁得业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30s后自动删除
//lock.lock(10, TimeUnit.SECONDS);//10秒以后自动解锁,自动解锁时间一定要大于业务时间
// lock.lock(10, TimeUnit.SECONDS)在锁时间到了以后,不会自动续期
//1.如果我们传递了锁的超时时间,就发送给redis执行脚本,进行站锁,默认时间就是我我们传递的时间
//2.如果我们危制定锁的超时时间,就使用lockWatchdogTimeout = 30 * 1000;看门狗默认时间
//只要站锁成功,就会启动定时任务(重新给锁设置过期时间)新的时间就是看门狗的默认时间,每10秒,都会自动续期续成满时间
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压测软件,进行配置请求来测试会不会超卖等情况
④、查看控制台打印结果,发现两种情况都能控制住扣减库存的问题,而且也在扣减完库存后将锁删掉了~
|