文章要点
- 自定义配置属性类
- 集成配置RedisTemplate
- 集成配置分布式锁Redisson
- 使用分布式锁简单实现超卖方案
1. 项目结构
2. 集成RedisTemplate和Redisson
添加依赖
依赖的版本与继承的spring-boot-starter-parent工程相对应,可写可不写
<!--spring data redis & cache-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- redis依赖commons-pool 这个依赖一定要添加 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!--redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.15.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
大概图解
如上图,先配置application.yml设置Redis的各项属性包括ip,端口,密码。再注入。
server:
port: 8080
# Redis配置
spring:
redis:
host: 192.168.200.131
port: 6379
password: root
// RedisConfigProperties.java
@Data
@ConfigurationProperties(prefix = "spring.redis")
public class RedisConfigProperties {
private String host;
private int port;
private String password;
}
// RedisTemplateConfig.java
@Configuration
public class RedisTemplateConfig {
@Autowired
RedisConfigProperties redisConfigProperties;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(
redisConfigProperties.getHost(), redisConfigProperties.getPort()
);
config.setPassword(redisConfigProperties.getPassword());
return new LettuceConnectionFactory(config);
}
@Bean
public RedisTemplate<String, Object> redisTemplate(){
RedisTemplate<String,Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory());
// 设置序列化器等其他配置
return template;
}
}
// RedissonConfig.java
@Configuration
@EnableConfigurationProperties({RedisConfigProperties.class})
public class RedissonConfig {
@Autowired
RedisConfigProperties redisConfigProperties;
@Bean
public RedissonClient redissonClient(){
// 1. 创建配置文件
Config config = new Config();
// 2. 设置单节点服务器配置
config.useSingleServer().setAddress(
"redis://"+redisConfigProperties.getHost()+":"+redisConfigProperties.getPort()
);
// 3. 如果Redis设置了密码,这里需要设置密码
config.useSingleServer().setPassword(redisConfigProperties.getPassword());
// 4. 创建RedissonClient实例
RedissonClient redisson = Redisson.create(config);
return redisson;
}
}
到这里,RedisTemplate和Redisson就已经集成好了,后续使用只需要注入就行。
例如
@RestController
@RequestMapping(value = "/order")
public class OrderController {
@Autowired
//private RedisTemplate redisTemplate;
private StringRedisTemplate redisTemplate; //通常用这个StringRedisTemplate
@Autowired
private RedissonClient redissonClient;
...
...
}
3. 模拟分布式场景下超卖现象
OrderController
/***
* 抢单
*/
@GetMapping(value = "/v1/{id}/{num}")
public String addv1(@PathVariable(value = "id")String id,@PathVariable("num")Long num) throws InterruptedException {
// 1.查询库存
int count = Integer.valueOf(redisTemplate.opsForValue().get(id));
System.out.println("剩余库存:"+count);
// 2.库存充足
if (count>=num){
//模拟操作
TimeUnit.SECONDS.sleep(5);
//递减库存
Long decrement = redisTemplate.opsForValue().decrement(id, num);
System.out.println("递减库存后剩余库存:"+decrement);
//其它操作 略
System.out.println("----添加订单了!");
return "下单成功";
}
//库存不足
else {
return "库存不足";
}
}
复制一个端口为8081的配置,模拟分布式服务
启动两个服务
在Redis中设置一个键值对water:2,模拟水的库存为2.
浏览器同时发送2个请求,模拟有2个用户同时每人买2瓶水
http://127.0.0.1:8080/order/v1/water/2
http://127.0.0.1:8081/order/v1/water/2
出现超卖现象
4. 利用Redisson分布式锁,防止超卖
关键代码
RLock lock = redissonClient.getLock("mylock_" + id); lock.lock(); //自旋获取锁 ... ... lock.unlock();
首先记得set water 2
OrderController
/***
* 抢单,使用分布式锁
*/
@GetMapping(value = "/{id}/{num}")
public String add(@PathVariable(value = "id")String id,@PathVariable("num")Long num) throws InterruptedException {
//对该商品加锁,加锁成功,则判断库存,避免多人同时判断某一个商品的库存
RLock lock = redissonClient.getLock("mylock_" + id);
lock.lock(); //自旋获取锁
System.out.println("获取了锁!"+lock.getName());
try {
// 1.查询库存
int count = Integer.valueOf(redisTemplate.opsForValue().get(id));
System.out.println("剩余库存:"+count);
// 2.库存充足
if (count>=num){
//模拟操作
TimeUnit.SECONDS.sleep(5);
//递减库存
Long decrement = redisTemplate.opsForValue().decrement(id, num);
System.out.println("递减库存后剩余库存:"+decrement);
//其它操作 略
System.out.println("----添加订单了!");
return "下单成功";
}
//库存不足
else {
return "库存不足";
}
} finally {
//释放锁
System.out.println("释放了锁!"+lock.getName());
lock.unlock();
}
}
启动2个服务,浏览器同时发送2个请求
http://127.0.0.1:8080/order/water/2
http://127.0.0.1:8081/order/water/2
防止了超卖现象