目录
目录
1、加锁实现关键:
Redis的setNX命令
2、分布式加锁实现方式:
1)先加锁,加所有执行程序,程序执行完毕通过finally模块关闭锁。弊端:如果程序在执行finally之前程序异常崩溃锁将不会被释放
2)先加锁,加锁成功后进行过时操作,当并发量达到一定程度那么程序很可能会变慢,如原设置加锁时长为30秒,如果程序运行超过30秒,那么锁将被提前释放, 下一个线程将可以和上个线程同时操作,并且并发量足够大这种情况将一直存在,此时相当于锁失效
3)同时加锁并设置加锁时间,当并发量达到一定程度那么程序很可能会变慢,如原设置加锁时长为30秒,如果程序运行超过30秒,那么锁将被提前释放,下一个线程将可以和上个线程同时操作,并且并发量足够大这种情况将一直存在,此时相当于锁失效
4)redisson加锁:加锁、锁时长配置为原子性同步执行,,并且如果遇见大并发量造成的程序运行超过指定加锁时长会对线程的锁进行弹性续时,如果程序正常运行时长没超过加锁时长将按照程序运行时长正常释放锁
一、pom.xml配置
spring-data-redis:redis基本连接,redisTempldate操作等依赖
redisson:分布式锁依赖
<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.0</version>
</dependency>
二、application.properties配置
## redis setting
spring.redis.host=127.0.0.1
spring.redis.password=123456
spring.redis.port=6379
## redis lettuce pool setting
spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-idle=10
spring.redis.lettuce.pool.min-idle=5
三、redisson的java配置类
package com.leadpms.qianlistandard.web.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;
/**
* Redisson 配置类
*
* @author Shaoyu Liu
* @date 2021/6/2 16:31
**/
@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);
}
}
package com.leadpms.qianlistandard.web.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;
/**
* Redis 配置类
*
* @author Shaoyu Liu
* @date 2021/5/28 15:50
**/
@Configuration
public class RedisConfig {
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setDefaultSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setConnectionFactory(redisConnectionFactory);
return template;
}
// @Bean
// public RedisConnectionFactory redisConnectionFactory(JedisPoolConfig jedisPool,
// RedisStandaloneConfiguration jedisConfig) {
// JedisConnectionFactory connectionFactory = new JedisConnectionFactory(jedisConfig);
// connectionFactory.setPoolConfig(jedisPool);
// return connectionFactory;
// }
//
// @Configuration
// public static class JedisPoolConf {
// @Value("${spring.redis.host:127.0.0.1}")
// private String host;
// @Value("${spring.redis.port:6379}")
// private Integer port;
// @Value("${spring.redis.password:}")
// private String password;
// @Value("${spring.redis.database:0}")
// private Integer database;
//
// @Value("${spring.redis.jedis.pool.max-active:8}")
// private Integer maxActive;
// @Value("${spring.redis.jedis.pool.max-idle:8}")
// private Integer maxIdle;
// @Value("${spring.redis.jedis.pool.max-wait:-1}")
// private Long maxWait;
// @Value("${spring.redis.jedis.pool.min-idle:0}")
// private Integer minIdle;
//
// @Bean
// public JedisPoolConfig jedisPool() {
// JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
// jedisPoolConfig.setMaxIdle(maxIdle);
// jedisPoolConfig.setMaxWaitMillis(maxWait);
// jedisPoolConfig.setMaxTotal(maxActive);
// jedisPoolConfig.setMinIdle(minIdle);
// return jedisPoolConfig;
// }
//
// @Bean
// public RedisStandaloneConfiguration jedisConfig() {
// RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
// config.setHostName(host);
// config.setPort(port);
// config.setDatabase(database);
// config.setPassword(RedisPassword.of(password));
// return config;
// }
// }
}
四、具体使用
1.redis定义商品个数为200件
set stock "200"
2.Java接口
@RestController
@RequestMapping("/address")
public class AddressController {
@Autowired
private RedissonClient redissonClient;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 商品总量
*/
public static int TICKET_PRODUCT = 100;
/**
* 锁超时时间(秒)
*/
public static long LOCK_TIMEOUT = 30;
/**
* 分布式锁前缀
*/
public static String LOCK_KEY_PREFIX = "lock_product";
@GetMapping("/testRedis")
public void testRedis() {
RLock rLock = redissonClient.getLock(LOCK_KEY_PREFIX);
try {
rLock.lock();
int stock = (Integer) redisTemplate.opsForValue().get("stock");
if (stock > 0) {
TICKET_PRODUCT = stock - 1;
redisTemplate.opsForValue().set("stock", TICKET_PRODUCT);
System.out.println("卖出1件商品,库存剩余:" + TICKET_PRODUCT);
} else {
System.out.println("售卖失败!库存不足");
}
} finally {
rLock.unlock();
}
}
}
五、jemeter并发测试
1.设置并发数,并发发起时间
2.设置接口地址端口号等
3.请求结果
其他使用
package com.leadpms.qianlistandard.web.rest;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.ObjectUtils;
import java.util.concurrent.TimeUnit;
/**
* @author Shaoyu Liu
* @date 2021/6/2 16:07
**/
public class Test {
@Autowired
private Redisson redisson;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 商品总量
*/
public static int TICKET_PRODUCT = 100;
/**
* 锁超时时间(秒)
*/
public static long LOCK_TIMEOUT = 30;
/**
* 商品锁前缀
*/
public static String LOCK_KEY_PREFIX = "lock_product";
public void RedisLock() {
// 1.先加锁,加所有执行程序,程序执行完毕通过finally模块关闭锁。弊端:如果程序在执行finally之前程序异常崩溃锁将不会被释放
Boolean lockResult = redisTemplate.opsForValue().setIfAbsent(LOCK_KEY_PREFIX, "distributed_lock");
try {
if (!ObjectUtils.isEmpty(lockResult)) {
if (lockResult) {
TICKET_PRODUCT--;
System.out.println("卖出1件商品,库存剩余:" + TICKET_PRODUCT);
}
if (!lockResult) {
System.out.println("售卖失败!库存不足");
}
}
} finally {
redisTemplate.delete(LOCK_KEY_PREFIX);
}
//2.先加锁,加锁成功后进行过时操作,当并发量达到一定程度那么程序很可能会变慢,如原设置加锁时长为30秒,如果程序运行超过30秒,那么锁将被提前释放,
//下一个线程将可以和上个线程同时操作,并且并发量足够大这种情况将一直存在,此时相当于锁失效
Boolean lockResult = redisTemplate.opsForValue().setIfAbsent(LOCK_KEY_PREFIX, "distributed_lock");
try {
if (!ObjectUtils.isEmpty(lockResult)) {
if (lockResult) {
redisTemplate.expire(LOCK_KEY_PREFIX, LOCK_TIMEOUT, TimeUnit.SECONDS);
TICKET_PRODUCT--;
System.out.println("卖出1件商品,库存剩余:" + TICKET_PRODUCT);
}
if (!lockResult) {
System.out.println("售卖失败!库存不足");
}
}
} finally {
redisTemplate.delete(LOCK_KEY_PREFIX);
}
/*
数据库双写不一致(写入缓存和写入数据库):加入uuid来确保在删除锁的时候不会删掉其他线程的锁。
在线程执行时间轴上,并不一定是先到的线程先执完毕的,也有可能先到的线程在执行过程中被晚到的线程先一步执行完毕。
可能第一个线程先到的线程拿到的库存为100,然后在30秒内没执行完毕锁被释放掉了,然后第二个后到线程拿到拿到的库存也是100卖出5件商品剩95件,
然后此时线程一执行完毕,在他之前拿到的100件上也减去1变为99件,此时发生超卖
*/
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
// 原子执行 setnx lock_product uuid && expire lock_product 30 ,到期后lock_product自动删除
Boolean result = redisTemplate.opsForValue().setIfAbsent("lock_product", uuid, 30, TimeUnit.SECONDS);
if (result) {
try {
int stock = (Integer) redisTemplate.opsForValue().get("stock");
if (stock > 0) {
PRODUCT_COUNT = stock - 1;
redisTemplate.opsForValue().set("stock", PRODUCT_COUNT);
System.out.println("卖出1件商品,商品还剩" + PRODUCT_COUNT);
} else {
System.out.println("售卖失败,库存不足");
}
} finally {
String lockValue = (String) redisTemplate.opsForValue().get("lock_product");
// 判断只删除属于自己线程的锁
if (StringUtils.equals(uuid, lockValue)) {
redisTemplate.delete("lock_product");
}
}
}
// 4.redisson加锁:加锁、锁时长配置为原子性同步执行,,并且如果遇见大并发量造成的程序运行超过指定加锁时长会对线程的锁进行弹性续时,
// 如果程序正常运行时长没超过加锁时长将按照程序运行时长正常释放锁
RLock redissonLock = redisson.getLock(LOCK_KEY_PREFIX);
try {
redissonLock.lock();
int stock = (Integer) redisTemplate.opsForValue().get("stock"); // jedis.get("key");
if (stock > 0) {
TICKET_PRODUCT = stock - 1;
redisTemplate.opsForValue().set("stock", TICKET_PRODUCT); // jedis.set("key","value");
System.out.println("卖出1件商品,库存剩余:" + TICKET_PRODUCT);
} else {
System.out.println("售卖失败!库存不足");
}
} finally {
redissonLock.unlock();
}
}
}