使用 Mysql 实现分布式锁

Mysql 实现分布式锁

背景

项目中经常需要分布式锁来处理一些并发或者避免重复的逻辑,如接口重复提交,消息重复消费,分布式任务调度等。实现分布式锁有很多解决方案,常见的有利用redis、zookeeper等中间价实现的分布式锁

但在实际生产中,有时会因为各种原因不能使用redis、zookeeper 等,如项目之前没有集成,为了某处的分布式锁而多引入中间件增加系统复杂读,或者干脆是处于成本考虑不给用…

大多数系统都集成了 mysql , 那么利用 mysql 做分布式锁自然成为一种选择。为了提高并发度,减小锁范围,主要利用 mysql 的行锁。在不增加额外表的情况下,可以利用insert 或者 update 或者select … for update 语句在某一行上加行锁,这三种有其各自的使用场景。无论使用哪种方案,都需要被加锁的逻辑代码在同一个事务中,可以通过在方法上加 @Transactional 注解实现,数据库表的关键字段上要有为一索引,比如任务 id , 消息 id 等。

insert

在被加锁逻辑代码中,先执行插入操作。在任务被多次执行时,只有第一次执行的代码可以成功插入数据,当第二次执行插入时,如果第一次执行的事务还未提交,则第二次执行的人任务会被阻塞。当第一次的执行任务提交后,第二次执行的任务会解除阻塞状态,并返回字段重复的异常,捕获异常并推出方法。如此,能够时任务只执行一次。

update

在被加锁逻辑代码中,先执行更新操作。例如更新状态的语句可以写成如下形式:

update task_table set state = 1 where task_id = "123" and state = 0;

在任务被多次执行时,只有第一次执行的代码可以成功更新数据,当第二次执行插入时,如果第一次执行的事务还未提交,则第二次执行的人任务会被阻塞。当第一次的执行任务提交后,第二次执行的任务会解除阻塞状态,并返回更新行数,如果任务已经被其它线程执行,那么返回的更新行数应该是 0,则可以判断本次不应执行任务,直接结束。

select for update

如果被加锁代码逻辑是需要先从库中查出数据,测可以使用 select for update 语句查询数据,会在数据行上加排它锁,当事务未提交而其它线程对同一行执行 select for update 语句时就会被阻塞。可以在被加锁逻的任务执行完后,改变数据行的状态字段,之后重复执行时,发现查到的数据状态已改变,则直接结束

实验方法

为验证利用 mysql 行锁实现分布式锁的可行性,可以做一下实验。以 select … for update 语句为例:
使用 Mysql 实现分布式锁_第1张图片

你可能感兴趣的:(mysql,分布式,数据库,java)