基于数据库的分布式锁
使用场景:
某大型网站部署是分布式的,订单系统有三台服务器响应用户请求,生成订单后统一存放到order_info表;order_info表要求订单id(order_id)必须是唯一的,那么三台服务器怎么协同工作来确认order_id的唯一性呢?这时候就要用到分布式锁了。
分布式锁的要求:
在了解了使用场景之后,再看一下我们需要的分布式锁应该是怎样的(以方法锁为例)
这把锁要可重入(防止死锁)
这把锁最好是一个阻塞锁(根据业务考虑是否需要这条)
有高可用的获取锁跟释放锁的功能
获取锁跟释放锁的性能要好
实现方式:
分布式锁的实现分为3种,基于数据库的,基于缓存的跟基于zookeeper的。接下来我们对这三种方式进行实现。
基于数据库的分布式锁:
大概原理:直接创建一张锁表,当要锁住某个方法或者资源时,就在该表中增加一条记录,想要释放的时候就删除这条记录。
1 2 3 4 5 6 7 8 | CREATE TABLE `methodLock` ( `id` int (11) NOT NULL AUTO_INCREMENT COMMENT '主键' , `method_name` varchar (64) NOT NULL DEFAULT '' COMMENT '锁定的方法名' , `mydesc` varchar (1024) NOT NULL COMMENT '备注信息' , `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP , PRIMARY KEY (`id`), UNIQUE KEY `uidx_method_name` (`method_name`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
当我们想要锁住某个方法时,执行以下sql:
1 | insert into methodLock(method_name, desc ) values ( '具体方法名' , '描述' ); |
以上的简单实现有几个问题:
1、这把锁依赖数据库的可用性,如果数据库是一个单点,一旦挂掉,会导致业务系统不可用;
2、这把锁没有失效时间,一旦解锁操作失败,会导致锁一直存留在数据库中,其它线程无法获得锁;
3、这把锁只能是非阻塞的,因为数据的insert操作一旦插入失败就直接报错,没有获得锁的线程不会进入排队队列,想要再次获得锁就要再次触发获得锁的操作;
4、这把锁是非重入的,同一线程在没有释放锁之前无法再次获得该锁,因为表中数据已经存在了。
当然上面的问题也是可以解决的:
1、单点问题,两个数据库,双向同步,一旦挂掉切换到另一个上;
2、失效时间,做一个定时任务,每隔多长时间清理超时数据;
3、非阻塞问题,程序写for循环多次尝试,直至获取到锁为止;
4、非重入,增加一个字段,记录获取所的ip跟线程信息,下次查询的时候如果有,则直接给锁;
示例代码:
获取锁:
1 2 3 4 5 6 7 8 9 10 11 12 | public boolean lock(){ int result = 0 ; try { result = jdbcTemplate.update( "insert into methodLock(method_name,mydesc)values(?,?)" , new Object[]{ "com.wzy.home.study.distributedlock.MyResources.getNextId()" , "获取orderId" }); } catch (Exception e){ //todo nothing } if (result == 1 ){ return true ; } return false ; } |
释放锁:
1 2 3 4 5 6 7 8 | public boolean unLock(){ int rows = jdbcTemplate.update( "delete from methodLock where method_name = ?" , new Object[]{ "com.wzy.home.study.distributedlock.MyResources.getNextId()" }); if (rows == 1 ){ return true ; } else { return false ; } } |
两个线程,模拟两个客户端进行测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | public void testLock(){ Thread t1 = new Thread( new Runnable() { @Override public void run() { boolean flag = mysqlLock.lock(); if (flag){ //有锁 int orderId = MyResources.getInstance().getNextId(); System.out.println( "t1拿到锁,获取的订单id为:" +orderId); try { Thread.sleep( 5000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println( "t1释放锁了" ); mysqlLock.unLock(); } } }); Thread t2 = new Thread( new Runnable() { @Override public void run() { try { Thread.sleep( 500 ); } catch (InterruptedException e) { e.printStackTrace(); } tryLock(); } //多次尝试获取锁 private boolean tryLock(){ boolean flag = mysqlLock.lock(); if (flag){ int orderId = MyResources.getInstance().getNextId(); System.out.println( "t2拿到锁,获取订单id为:" +orderId); } else { System.out.println( "t2获取锁失败,再次尝试" ); try { Thread.sleep( 1000 ); } catch (InterruptedException e) { e.printStackTrace(); } tryLock(); mysqlLock.unLock(); } return flag; } }); t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } } |
输出结果:
t1拿到锁,获取的订单id为:1
t2获取锁失败,再次尝试
t2获取锁失败,再次尝试
t2获取锁失败,再次尝试
t2获取锁失败,再次尝试
t2获取锁失败,再次尝试
t1释放锁了
t2拿到锁,获取订单id为:2
可以看到,的确是t2等t1释放锁后才拿到了锁进行了业务操作。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 智能桌面机器人:用.NET IoT库控制舵机并多方法播放表情
· Linux glibc自带哈希表的用例及性能测试
· 深入理解 Mybatis 分库分表执行原理
· 如何打造一个高并发系统?
· .NET Core GC压缩(compact_phase)底层原理浅谈
· DeepSeek火爆全网,官网宕机?本地部署一个随便玩「LLM探索」
· 开发者新选择:用DeepSeek实现Cursor级智能编程的免费方案
· 【译】.NET 升级助手现在支持升级到集中式包管理
· 独立开发经验谈:如何通过 Docker 让潜在客户快速体验你的系统
· 并发编程 - 线程同步(二)