1、分布式锁服务端
单独启动一个应用,用来提供分布式锁,实现锁的关闭和开启。相当于redis的服务器
1.1、创建锁表
DROP TABLE IF EXISTS `qrtz_distribute_lock`;
CREATE TABLE `qrtz_distribute_lock` (
`id` varchar(32) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '主键',
`key` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '锁名称',
`val` tinyint(255) NULL DEFAULT 1 COMMENT '值',
`remark` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '描述',
`update` timestamp(0) NULL DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `KEY_INDEX`(`key`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Dynamic;
数据库表如下图所示:
其中key自行实现唯一性,数据库不做约束
1.2、锁的查询和删除
@Repository
public interface DistributeLockDao extends JpaRepository{
/** 根据key查询锁 **/
DistributeLock findByKey(String key);
/** 根据key删除锁 **/
void deleteByKey(String key);
}
1.3、加锁和解锁
@Service
public class DistributeLockService {
@Autowired
DistributeLockDao lockDao;
@Transactional
public synchronized Boolean lock(DistributeLock lock) {
DistributeLock locked = lockDao.findByKey(lock.getKey());
if(locked == null) { //没有锁,新增锁
lock.setUpdate(new Date());
lock.setVal(true);
lock = lockDao.saveAndFlush(lock);
return true;
}
if(locked.getVal()) { //有锁,且已锁,不可用
locked = new DistributeLock();
locked.setVal(false);
return false;
}
// 有锁,可用
locked.setVal(true);
locked.setUpdate(new Date());
locked = lockDao.saveAndFlush(locked);
return true;
}
/**
* 取消分布式锁
* @return
*/
@Transactional
public synchronized Boolean unLock(DistributeLock lock) {
lockDao.deleteByKey(lock.getKey());
lock.setVal(false);
return true;
}
}
1.4、提供取锁和解锁的接口
@RestController
public class DistributeLockController {
@Autowired
DistributeLockService lockService;
@RequestMapping("lock")
public Boolean lock(@Validated DistributeLock lock) {
return lockService.lock(lock);
}
@RequestMapping("unLock")
public Boolean unLock(@Validated DistributeLock lock) {
return lockService.unLock(lock);
}
}
2、分布式锁客户端
2.1、切点注解
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Lock {
/**
* 保证唯一性,默认是全限定类名 + 方法全称(含参数列表)
* @return
*/
String key() default "";
String describe() default "";
/**
* 获取锁超时时间
* @return
*/
int timeout() default 5000;
}
数据库未解锁前的数据:
2.1、实现分布式锁切面
@Aspect
@Component
public class DistributeLockAspect {
/** 分布式锁服务端的ip和端口, 服务端contextPath若不为‘distribute’,则需要修改 **/
public static final String DISTRIBUTE_SERVER_ADDRESS = "localhost:9876";
private static final String userAgent = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3409.0 Safari/537.36";
private static final CloseableHttpClient httpclient = HttpClients.createDefault();
/**
* 线程池
*/
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(20);
/**
* 存放key
*/
ThreadLocalkeys = new ThreadLocal<>();
// order = 1
@Before(value = "@annotation(Lock)")
public void doBefore(JoinPoint joinPoint) throws InterruptedException, ExecutionException, TimeoutException {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
String key = null, describe = null;
int timeout = 0;
if (method != null) {
Lock lock = method.getAnnotation(Lock.class);
describe = lock.describe();
timeout = lock.timeout();
key = lock.key();
}
keys.set(getLockKey(key, joinPoint));
String remark = describe;
String lockKey = keys.get();
while (true) {
Futurefuture = executor.submit(() -> {
// 在此处使用keys.get(), key可能会为null
String result = sendGet("http://" + DISTRIBUTE_SERVER_ADDRESS + "/distribute/lock?key=" + lockKey + "&remark=" + remark);;
if(StringUtils.equals(result, "true") || StringUtils.equals(result, "false")) {
return Boolean.parseBoolean(result);
}
return null;
});
Boolean flag = future.get(timeout, TimeUnit.MILLISECONDS);
if(flag == null) {
throw new IllegalArgumentException("network exception");
}
if(flag != null) {
if(future.get()) {
break;
} else {
continue;
}
}
}
}
// order = 2
@Around(value = "@annotation(Lock)")
public Object around(ProceedingJoinPoint proceedingJoinPoint){
try {
Object ret= proceedingJoinPoint.proceed();
return ret;
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return null;
}
// order = 3
@After(value = "@annotation(Lock)")
public void after(JoinPoint joinPoint){
}
// order = 4
@AfterReturning(pointcut = "@annotation(Lock)",returning = "ret")
public void doAfterReturning(Object ret) {
sendGet("http://" + DISTRIBUTE_SERVER_ADDRESS + "/distribute/unLock?key=" + keys.get());
keys.remove();
}
// order = 方法抛出异常时
@AfterThrowing(pointcut = "@annotation(Lock)",throwing = "ex")
public void AfterThrowing(JoinPoint joinPoint,Throwable ex){
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
sendGet("http://" + DISTRIBUTE_SERVER_ADDRESS + "/distribute/unLock?key=" + keys.get());
keys.remove();
}
/**
* 分布式锁的key, 默认由全限定类名 + 具体方法(含参数)
* @param key
* @param joinPoint
* @return
*/
private String getLockKey(String key, JoinPoint joinPoint) {
if(StringUtils.isNotBlank(key)) {
return key;
}
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
String[] names = StringUtils.split(method.toString(), " ");
for(String name : names) {
if(StringUtils.indexOf(name, "(") > -1 && StringUtils.indexOf(name, ")") > -1) {
return name;
}
}
return null;
}
private String sendGet(String url) {
String result = null;
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("User-Agent", userAgent);
try(CloseableHttpResponse response = httpclient.execute(httpGet)) {
HttpEntity entity = response.getEntity();
if (entity != null) {
result = EntityUtils.toString(entity);
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}