分布式锁一般有三种实现方式:1. 数据库乐观锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁
本篇博客介绍的是第二种,基于Redis的分布式锁,实际上是使用Redisson这一款强大的框架来实现分布式锁的.
代码示例: https://gitee.com/zhang-xiao-xiang/zxx-redis-lock
代码结构预览

1:创建一个springboot的基本项目,写好一些基本接口
所需的基本pom依赖
<!--spring boot操作Redis的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!--java web接口-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--简化java代码插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--Redis实现分布式锁的基本框架-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.5.0</version>
</dependency>
application.properties文件配置(注意端口可以配置8081和8082,多实例启动,模拟多肽tomcat服务器,idea的多实例启动方式如下图)

server.port=8082
spring.redis.url=redis://localhost:6379
写一个RedissonConfig配置类 来配置你的redisson
package com.zhang.zxx.redis.lock.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* RedissonAutoConfiguration:写一个RedissonConfig配置类 来配置你的redisson
*
* @author zhangxiaoxiang
* @date 2020/09/10
*/
@Configuration
public class RedissonAutoConfiguration {
@Value("${spring.redis.url}")
private String addressUrl;
@Bean
public RedissonClient getRedisson() {
Config config = new Config();
config.useSingleServer()
.setAddress(addressUrl)
.setReconnectionTimeout(10000)
.setRetryInterval(5000)
.setTimeout(10000)
.setConnectTimeout(10000);
return Redisson.create(config);
}
}
编写基本接口(注释的是优化后,使用分布式锁的,为了演示效果,先注释,然后再对比)
package com.zhang.zxx.redis.lock.controller;
import com.zhang.zxx.redis.lock.config.RedissonAutoConfiguration;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Objects;
/**
* RedisLockController:测试分布式锁
*
* @author zhangxiaoxiang
* @date 2020/09/10
*/
@RestController
@RequestMapping("/order")
public class RedisLockController {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private RedissonAutoConfiguration redissonAutoConfiguration;
@Value("${server.port:}")
private String port;
/**
* 使用接口为redis添加个库存(手动添加也行)
* @return
*/
@RequestMapping("/setprodnum")
public String addProdNum() {
redisTemplate.opsForValue().set("stock","50");
System.out.println("缓存数量是: "+redisTemplate.opsForValue().get("stock"));
return redisTemplate.opsForValue().get("stock")+"";
}
/**
* 未使用分布式锁:秒杀商品,减库存接口
*
* @return
*/
@RequestMapping("/getcommodity")
public String getCommodity() {
//库存(这里翻译成了锁也是为了方便理解)的key
String lockKey = "prod_lock";
// 检查指定的对象引用是否不为空 Objects.requireNonNull,查出库存
int stock = Integer.parseInt(Objects.requireNonNull(redisTemplate.opsForValue().get("stock")));
if (stock>0){
int realStock=stock-1;
redisTemplate.opsForValue().set("stock",realStock+"");
System.out.println("进入减库存的接口,服务器端口:" + port+"扣减成功,库存剩余===>"+realStock);
return "扣减成功,库存剩余:"+realStock;
}else {
System.err.println("扣减失败,库存不足了呢");
return "扣减失败,库存不足了呢";
}
}
// /**
// * 秒杀商品,减库存接口,分布式锁解决方案
// *
// * @return
// */
// @RequestMapping("/getcommodity")
// public String getCommodity() {
// String lockKey = "prod_lock";
// RLock rLock = redissonAutoConfiguration.getRedisson().getLock(lockKey);
// try {
// rLock.lock();
// int stock = Integer.parseInt(Objects.requireNonNull(redisTemplate.opsForValue().get("stock")));
// if (stock>0){
// int realStock=stock-1;
// redisTemplate.opsForValue().set("stock",realStock+"");
// System.out.println("进入减库存的接口,服务器端口:" + port+"扣减成功,库存剩余===>"+realStock);
// return "扣减成功,库存剩余:"+realStock;
// }else {
// System.err.println("扣减失败,库存不足了呢");
// return "扣减失败,库存不足了呢";
// }
// } finally {
// //注意释放锁
// rLock.unlock();
// }
// }
}
此时可以启动两个实例,可以访问两个接口,都可以实现减少库存,如下图
http:://127.0.0.1:8081/order/getcommodity
http://127.0.0.1:8082/order/getcommodity

2:配置NGINX代理(模拟多台tomcat服务器同时访问)和Jmeter压测
在NGINX配置文件最后一个大括号内添加,下载地址(http://nginx.org/download/nginx-1.12.2.zip),这是Windows版本的
#自定义添加配置,注意是在配置文件的最后一个大括号内-----------------------------------------------------------------
#大家可以指定为服务的域名或者项目的代号
upstream upstream_name{
server 127.0.0.1:8081;
server 127.0.0.1:8082;
}
server {
listen 8080;
server_name localhost;
location / {
proxy_pass http://upstream_name;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
#自定义添加配置,注意是在配置文件的最后一个大括号内-----------------------------------------------------
#访问的时候访问http://127.0.0.1:8080/order/getcommodity即可达到访问
#http://127.0.0.1:8081/order/getcommodity
#http://127.0.0.1:8082/order/getcommodity
#这几个的目的,没有配置权重,就采用默认策略(负载均衡-轮询)
启动NGINX的方式是

是否启动成功,访问http://localhost 即可,如图即为正常启动后的NGINX默认页面.

配置Jmeter,配置模拟并发和访问接口,如下两张图(不太会配置的可以百度Jmeter的使用)

下面红色框就是http://127.0.0.1:8080/order/getcommodity 拆分了一下,目的是为了NGINX代理分发请求到http:://127.0.0.1:8081/order/getcommodity和http:://127.0.0.1:8082/order/getcommodity

3:启动项目,并行8081和8082两个demo项目
对比结果(显然乱套了,数据并发访问异常)

此时将控制层注释掉的接口恢复,把常规接口 getcommodity 的注释,重新对比(注意修改redis库存后再试)

重启项目,对比结果

此时已经完全呈现出正确的数据,完成.
用法是不是和ReentrantLock 十分相似呢(可以参考我的另一篇博客https://zhangxiaoxiang.blog.youkuaiyun.com/article/details/108046261)

3万+

被折叠的 条评论
为什么被折叠?



