分布式锁
分布式锁
1.只适用于单线程
@RestController
@RequestMapping("/stock")
public class StockController {
final StringRedisTemplate stringRedisTemplate;
public StockController(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
@GetMapping("/sell")
public String sell() {
String stockKey = "stock";
int stock = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("stock")));
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set(stockKey, realStock + "");
System.out.println("减库存成功,剩余库存:" + realStock);
} else {
System.out.println("库存不足!");
}
return "end";
}
}
一旦进入多个线程进入出现超卖问题
使用jmeter压力测试下,出现重复打印库存,即超卖
减库存成功,剩余库存:99
减库存成功,剩余库存:99
减库存成功,剩余库存:98
减库存成功,剩余库存:98
减库存成功,剩余库存:97
减库存成功,剩余库存:97
减库存成功,剩余库存:96
减库存成功,剩余库存:96
减库存成功,剩余库存:95
减库存成功,剩余库存:94
减库存成功,剩余库存:94
减库存成功,剩余库存:93
减库存成功,剩余库存:92
减库存成功,剩余库存:92
减库存成功,剩余库存:91
减库存成功,剩余库存:91
减库存成功,剩余库存:91
减库存成功,剩余库存:90
2.单机部署下
package top.wangjf.springbootredis.controller;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Objects;
@RestController
@RequestMapping("/stock")
public class StockController {
final StringRedisTemplate stringRedisTemplate;
public StockController(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
@GetMapping("/sell")
public String sell() {
synchronized (this) {
String stockKey = "stock";
int stock = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("stock")));
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set(stockKey, realStock + "");
System.out.println("减库存成功,剩余库存:" + realStock);
} else {
System.out.println("库存不足!");
}
return "end";
}
}
}
集群部署(两个服务server1, server2),使用nginx反向代理负载均衡,
upstream redislock {
server localhost:8080 weight=1;
server localhost:8090 weight=1;
}
server {
listen 80;
location / {
root html;
index index.html index.htm;
proxy_pass http://redislock; #访问nginx根目录,反向代理到redis_lock
}
}
使用jmeter压力测试下,出现重复打印库存,即超卖
| server1 | server2 |
|---|---|
| 减库存成功,剩余库存:99 减库存成功,剩余库存:98 减库存成功,剩余库存:97 减库存成功,剩余库存:95 减库存成功,剩余库存:94 减库存成功,剩余库存:93 减库存成功,剩余库存:91 减库存成功,剩余库存:90 |
减库存成功,剩余库存:97 减库存成功,剩余库存:96 减库存成功,剩余库存:94 减库存成功,剩余库存:93 减库存成功,剩余库存:92 减库存成功,剩余库存:91 |
3.redis分布式锁(setnx)
package top.wangjf.springbootredis.controller;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Objects;
@RestController
@RequestMapping("/stock")
public class StockController {
final StringRedisTemplate stringRedisTemplate;
public StockController(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
@GetMapping("/sell")
public String sell() {
String lockKey = "lockKey";
//redis实现分布式锁 类似 jedis.setnx(key,value);
final Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "lock");
if (!lock) { //未获得锁,但是库存还有
return "error_code";
}
String stockKey = "stock";
int stock = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("stock")));
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set(stockKey, realStock + "");
System.out.println("减库存成功,剩余库存:" + realStock);
} else {
System.out.println("库存不足!");
}
//redis实现分布式锁
stringRedisTemplate.delete(lockKey);
return "end";
}
}
3.1解决死锁问题
可能出现的问题:
- 死锁
- 运行中抛出异常导致最后的删除操作没有执行
- 解决方法:使用try{}catch(){}finall{} 在finally代码块中删除
- 运行中进程终止导致最后的删除操作没有执行
- 为分布式锁设置过期时间(可能存在原子性问题)
- 运行中抛出异常导致最后的删除操作没有执行
package top.wangjf.springbootredis.controller;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/stock")
public class StockController {
final StringRedisTemplate stringRedisTemplate;
public StockController(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
@GetMapping("/sell")
public String sell() {
String lockKey = "lockKey";
try {
//redis实现分布式锁 类似 jedis.setnx(key,value);
//final Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "lock");
//进程中止导致导致死锁优化
//stringRedisTemplate.expire(lockKey,10, TimeUnit.MILLISECONDS);
//进程中止导致导致死锁优化(解决原子性问题)
final Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "lock", 10, TimeUnit.MILLISECONDS);
if (!lock) { //未获得锁,但是库存还有
return "error_code";
}
String stockKey = "stock";
int stock = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("stock")));
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set(stockKey, realStock + "");
System.out.println("减库存成功,剩余库存:" + realStock);
} else {
System.out.println("库存不足!");
}
} finally {
//解决异常导致死锁问题
stringRedisTemplate.delete(lockKey);
}
return "end";
}
}
3.2解决高并发下锁失效问题
可能存在的问题:
自己的锁被别的线程释放:
假如秒杀功能执行需要100ms,但是分布式锁只有10ms的过期时间,
A线程在执行中分布式锁过期但是还未执行完,B进程获得分布式锁,
此时A进程执行结束释放锁,但是释放得是B进程的锁。导致分布式锁失效。
解决方案:
保证加的锁和释放的锁是同一个。
package top.wangjf.springbootredis.controller;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/stock")
public class StockController {
final StringRedisTemplate stringRedisTemplate;
public StockController(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
@GetMapping("/sell")
public String sell() {
String uuid = UUID.randomUUID().toString(); //解决高并发下分布式锁失效问题
String lockKey = "lockKey";
try {
//redis实现分布式锁 类似 jedis.setnx(key,value);
//final Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "lock");
//进程中止导致导致死锁优化
//stringRedisTemplate.expire(lockKey,10, TimeUnit.MILLISECONDS);
//进程中止导致导致死锁优化(解决原子性问题)
final Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, uuid, 10, TimeUnit.MILLISECONDS);
if (!lock) { //未获得锁,但是库存还有
return "error_code";
}
String stockKey = "stock";
int stock = Integer.parseInt(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("stock")));
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set(stockKey, realStock + "");
System.out.println("减库存成功,剩余库存:" + realStock);
} else {
System.out.println("库存不足!");
}
} finally {
//解决高并发下分布式锁失效问题
if (uuid.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
stringRedisTemplate.delete(lockKey);
}
}
return "end";
}
}
依然会存在超卖问题,在并发量不是非常非常高的情况下可以接受
优化方案:
每8ms判断以下是否仍然未释放锁,如果没有释放则,适当延长过期时间
4.分布式锁 redisson
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
this.internalLockLeaseTime = unit.toMillis(leaseTime);
return this.evalWriteAsync(this.getName(), LongCodec.INSTANCE, command,
"if (redis.call('exists', KEYS[1]) == 0) " +
"then redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; " +
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) " +
"then redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; " +
"return redis.call('pttl', KEYS[1]);",
Collections.singletonList(this.getName()), this.internalLockLeaseTime, this.getLockName(threadId));
}
private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
if (leaseTime != -1L) {
return this.tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
} else {
RFuture<Long> ttlRemainingFuture = this.tryLockInnerAsync(this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
if (e == null) {
if (ttlRemaining == null) {
this.scheduleExpirationRenewal(threadId); //定时任务
}
}
});
return ttlRemainingFuture;
}
}
private void scheduleExpirationRenewal(long threadId) {
RedissonLock.ExpirationEntry entry = new RedissonLock.ExpirationEntry();
RedissonLock.ExpirationEntry oldEntry = (RedissonLock.ExpirationEntry)EXPIRATION_RENEWAL_MAP.putIfAbsent(this.getEntryName(), entry);
if (oldEntry != null) {
oldEntry.addThreadId(threadId);
} else {
entry.addThreadId(threadId);
this.renewExpiration();
}
}
private void renewExpiration() {
RedissonLock.ExpirationEntry ee = (RedissonLock.ExpirationEntry)EXPIRATION_RENEWAL_MAP.get(this.getEntryName());
if (ee != null) {
Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
public void run(Timeout timeout) throws Exception {
RedissonLock.ExpirationEntry ent = (RedissonLock.ExpirationEntry)RedissonLock.EXPIRATION_RENEWAL_MAP.get(RedissonLock.this.getEntryName());
if (ent != null) {
Long threadId = ent.getFirstThreadId();
if (threadId != null) {
RFuture<Boolean> future = RedissonLock.this.renewExpirationAsync(threadId);
future.onComplete((res, e) -> {
if (e != null) {
RedissonLock.log.error("Can't update lock " + RedissonLock.this.getName() + " expiration", e);
} else {
if (res) {
RedissonLock.this.renewExpiration();
}
}
});
}
}
}
}, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS);
ee.setTimeout(task);
}
}
public RedissonLock(CommandAsyncExecutor commandExecutor, String name) {
super(commandExecutor, name);
this.commandExecutor = commandExecutor;
this.id = commandExecutor.getConnectionManager().getId();
this.internalLockLeaseTime = commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout();
this.entryName = this.id + ":" + name;
this.pubSub = commandExecutor.getConnectionManager().getSubscribeService().getLockPubSub();
}
public Config() {
this.transportMode = TransportMode.NIO;
this.lockWatchdogTimeout = 30000L; //默认是30s
this.keepPubSubOrder = true;
this.decodeInExecutor = false;
this.useScriptCache = false;
this.minCleanUpDelay = 5;
this.maxCleanUpDelay = 1800;
this.cleanUpKeysAmount = 100;
this.nettyHook = new DefaultNettyHook();
this.useThreadClassLoader = true;
this.addressResolverGroupFactory = new DnsAddressResolverGroupFactory();
}

浙公网安备 33010602011771号