boolean before(){
lock(){//并发下单加分布式锁
//判断是否有下单资格 是否超过每日下单数
if(true){
return true;
}else{
return false;
}
}
};
validate(){};//参数校验之类的
after(){
redis.inrc();//统计每日下单数
}
//业务主线
if(before()){
validate();
after();
}
/**
* 获取分布式锁
* @param jedis
* @param lockKey 分布式锁Key
* @param expireTime 分布式锁过期时间
* @param timeoutTime 获取分布式锁最大时间
* @return 分布式锁持有者Key,未获取到锁为null
*/
public static String getDistributedLock(Jedis jedis, String lockKey, int expireTime,int timeoutTime) {
boolean result = false;
// 随机生成一个value
String requestId = UUID.randomUUID().toString();
// 获取锁的超时时间,超过这个时间则放弃获取锁
long end = System.currentTimeMillis() + timeoutTime;
while (!result && System.currentTimeMillis() < end) {
result = tryGetDistributedLock(jedis, lockKey, requestId, expireTime);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
return result ? requestId : null;
}
/**
* 尝试获取分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @param expireTime 超期时间
* @return 是否获取成功
*/
public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}
/**
* 释放分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @return 是否释放成功
*/
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
returnResource(jedis);
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}
/**
* 释放jedis资源
* @param jedis
*/
public static void returnResource(final Jedis jedis) {
if (jedis != null) {
jedis.close();
}
}
对于分布式锁,我们应该秉持 谁用谁释放的原则,不可以等到锁过期自动释放。
以上伪代码在before方法添加分布式锁后,什么时候释放呢?
按照正常的业务逻辑,应该是在before加锁,等到after执行完后释放,但是此时可能会释放掉其他的锁,造成业务混乱。如果等锁自动过期,并发时必定会消耗大量的系统资源,造成不可预知的问题。
如果在before里面在方法执行完后释放锁,在前一个请求执行validate() after()逻辑时,下一个订单可能早就进before来了,就失去了限量的目的。
最后怎么解决的呢?
- getDistributedLock(" “,” ",2*1000,60*1000)正常的业务逻辑一般2s内肯定可以执行完。
- 在before try catch finally 调用releaseDistributedLock释放锁时 使用线程延迟1s释放,给业务足够执行的时间。
String requestId = RedisUtil.getDistributedLock(jedis, lockKey, 2*1000, 60*1000);
if (null != requestId) {
try {
...
} catch (Exception e) {
log.error("系统下单限量管控error:"+e);
} finally {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
releaseDistributedLock(jedis, lockKey, requestId);
}
}
}).start();
}
}
后来踩坑发现这样处理是有问题的:
1.业务还没有执行完,锁都释放了,会报java.io.IOException: Connection reset by peer
2.分布式锁比较影响性能,不能在锁大面积业务
3.最后使用lua表达式 在after里完成业务判断
沿用同事的一句话: 我们现在使用的悲观锁的方式,每次拿数据就上锁,处理完后在释放锁,其实这样对于高并发是致命的,我们可以参考乐观锁的实现方式,每次获取数据不加锁,只是在最后判断数据有没有达到限制,达到就立马返回,立马释放锁,尽量把加群控制到最小范围,快速加锁快速释放