分布式锁产生的原因是:当多个客户端要同时并发操作数据库时,可能查出来的数据是相同的而后继续写的时候会出现事务方面的问题。如:商品只有一件而后被出售两次,造成数据幻读。
分布式锁的处理方案有:
使用redis操作,
使用zookeeper操作,
数据库方面操作(行锁)
以上所有的操作都是相当于在多个客户端之间放一把锁,类似于线程之间争夺锁的过程。
三种方案比较:
从理解的难易程度角度(从低到高)
数据库 > 缓存 > Zookeeper
从实现的复杂性角度(从低到高)
Zookeeper >= 缓存 > 数据库
从性能角度(从高到低)
缓存 > Zookeeper >= 数据库
从可靠性角度(从高到低)
Zookeeper > 缓存 > 数据库
具体的实现过程是:
1.用redis实现
可以使用redis的set(key,1,30,Nx)命令(在redis 2.6以上版本支持),其中的key可以使用这些客户端都要操作产品的id号去实现。
那么当解锁时需删除key,删除key要注意避免出现之前线程执行的时间很长导致删除key时删除了后面的客户端key。那么这是要注意设置key-value时一定要将value设置为对应线程的id号而后执行删除对应线程或者客户端的key。
2.用数据库实现
一.基于数据库表
最简单的方式可能就是直接创建一张锁表,当我们要锁住某个方法或资源时,我们就在该表中增加一条记录,想要释放锁的时候就删除这条记录。给某字段添加唯一性约束,如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,那么我们就可以认为操作成功的那个线程获得了该方法的锁,可以执行方法体内容。
缺点:会引入数据库单点、无失效时间、不阻塞、不可重入等问题
二.基于数据库的排他锁
如果使用的是MySql的InnoDB引擎,在查询语句后面增加 for update ,数据库会在查询过程中(须通过唯一索引查询)给数据库表增加排他锁,我们可以认为获得排它锁的线程即可获得分布式锁,而后可以通过 connection.commit() 操作来释放锁。
会引入数据库单点、不可重入、无法保证一定使用行锁(部分情况下MySQL自动使用表锁而不是行锁)、排他锁长时间不提交导致占用数据库连接等问题。
优点:
直接借助数据库,容易理解。
缺点:
会引入更多的问题,使整个方案变得越来越复杂
操作数据库需要一定的开销,有一定的性能问题
使用数据库的行级锁并不一定靠谱,尤其是当我们的锁表并不大的时候
3.使用zookeeper实现
借用大佬的博客:https://www.cnblogs.com/garfieldcgf/p/6380816.html