1つ目:同期を使用する(単一のtomcatにのみ適用可能)
@Autowired
private StringRedisTemplate stringRedisTemplate;
@GetMapping("/deduct")
public String toDeduct(){
synchronized (this) {
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock > 0) {
Long stock1 = stringRedisTemplate.opsForValue().decrement("stock");
System.out.println("扣减成功,剩余库存:" + stock1);
}else{
System.out.println("扣减失败,库存不足");
}
return "end";
}
}
2番目:SETNXコマンドを使用する(複数のtomcat)
@GetMapping("/deduct_stock")
public String deductStock() throws InterruptedException {
String lockKey = "lockKey";
//设计唯一key值防止锁失效问题
String clientId = UUID.randomUUID().toString();
//设计锁超时时间,防止服务器宕机时锁没有释放掉(finally语句没有执行)
Boolean result = stringRedisTemplate.opsForValue().
setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);//jedis.setnx(key,value);
//若键 key 已经存在, 则 SETNX 命令不做任何动作。result==false
if (!result) {
return "正在排队。。。";
}
try {
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock > 0) {
Long realStock = stringRedisTemplate.opsForValue().decrement("stock");
System.out.println("扣减成功,剩余库存:" + realStock);
}else{
System.out.println("扣减失败,库存不足");
}
} finally {
//防止锁失效问题,在多线程的情况下,每个线程只释放自己创建的锁,线程之间互不干预。
if(clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
stringRedisTemplate.delete(lockKey);
}
}
return "end";
}
3番目のタイプ:Redissionフレームワークを使用します(原則は2番目のタイプと同じです)
1.構成クラスを構成します
@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);
}
}
@GetMapping("/deduct_stock1")
public String deductStock1(){
String lockKey = "lockKey";
RLock redissonLock = redisson.getLock(lockKey);
try {
redissonLock.lock();
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if (stock > 0) {
Long realStock = stringRedisTemplate.opsForValue().decrement("stock");
System.out.println("扣减成功,剩余库存:" + realStock);
}else{
System.out.println("扣减失败,库存不足");
}
} finally {
redissonLock.unlock();
}
return "end";
}
再分配の原則:
RedissionはLuaスクリプトを使用し、実装の原則は2番目のスクリプトと同様です。
mysqlの低速クエリなどのようにビジネスコードの処理時間が非常に長い場合は、try codeブロックでロックが10秒ごとに保持されているかどうかを確認できます。保持している場合は、ロック時間を延長して、ビジネスコードが実行されないようにします。 「それは有効期限です。
テスト環境のセットアップ
Redisコマンドリスト
Redisパスワード
pomを設定します。
<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.7</version>
</dependency>
application.properties:
server.port=8090
# redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=888888
spring.redis.jedis.pool.max-active=500
spring.redis.jedis.pool.max-idle=1000
spring.redis.jedis.pool.max-wait=6000ms
spring.redis.jedis.pool.min-idle=4
Nginxロードバランシング:
upstream redislock{
server 127.0.0.1:8080 weight = 1;
server 127.0.0.1:8090 weight = 1;
}
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
proxy_pass http://redislock;
}
}
Jmeterストレステスト
httpリクエスト
の送信:送信アドレスの設定:
0秒以内に200リクエストを送信し、処理後に200 * 3回送信し、合計200 * 4リクエスト:
結果セット:
テスト結果:
売られ過ぎの問題なし