在之前业务的一次活动中,我发现在数据库操作日志中,出现最多的一个异常就是Interrupted Exception了,几乎所有的异常都是来自一个校验订单幂等性的SQL。
因为校验订单幂等性是提交订单业务中第一个操作数据库的,所以幂等性校验也就承受了比较大的请求量,再加上我们还是基于一个数据库表来实现幂等性校验的,所以出现了一些请求事务超时,事务被中断的情况。其实基于数据库实现的幂等性校验就是一种分布式锁的实现。
那什么是分布式锁呢,它又是用来解决哪些问题的呢?
在JVM中,在多线程并发的情况下,我们可以使用同步锁或Lock锁,保证在同一时间内,只能有一个线程修改共享变量或执行代码块。但现在我们的服务基本都是基于分布式集群来实现部署的,对于一些共享资源,例如我们之前讨论过的库存,在分布式环境下使用Java锁的方式就失去作用了。
这时,我们就需要实现分布式锁来保证共享资源的原子性。除此之外,分布式锁也经常用来避免分布式中的不同节点执行重复性的工作,例如一个定时发短信的任务,在分布式集群中,我们只需要保证一个服务节点发送短信即可,一定要避免多个节点重复发送短信给同一个用户。
因为数据库实现一个分布式锁比较简单易懂,直接基于数据库实现就行了,不需要再引入第三方中间件,所以这是很多分布式业务实现分布式锁的首选。但是数据库实现的分布式锁在一定程度上,存在性能瓶颈。
首先,我们应该创建一个锁表,通过创建和查询数据来保证一个数据的原子性:
- CREATE TABLE `order` (
- `id` int(11) NOT NULL AUTO_INCREMENT,
- `order_no` int(11) DEFAULT NULL,
- `pay_money` decimal(10, 2) DEFAULT NULL,
- `status` int(4) DEFAULT NULL,
- `create_date` datetime(0) DEFAULT NULL,
- `delete_flag` int(4) DEFAULT NULL,
- PRIMARY KEY (`id`) USING BTREE,
- INDEX `idx_status`(`status`) USING BTREE,
- INDEX `idx_order`(`order_no`) USING BTREE
- ) ENGINE = InnoDB
- 复制代码
其次,如果是校验订单的幂等性,就要先查询该记录是否存在数据库中,查询的时候要防止幻读,如果不存在,就插入到数据库,否则,放弃操作。
- select id from `order` where `order_no`= 'xxxx' for update
- 复制代码
最后注意下,除了查询时防止幻读,我们还需要保证查询和插入是在同一个事务中,因此我们需要申明事务,具体的实现代码如下:
- @Transactional
- public int addOrderRecord(Order order) {
- if