基于数据库实现分布式锁

分布式锁用于在分布式系统中控制共享资源的访问,防止超卖等并发问题。文中提到了基于数据库的实现,包括使用悲观锁的`select...forupdate`和乐观锁的CAS(CheckandSet)策略。悲观锁通过事务确保资源独占,而乐观锁则在更新时检查并发冲突。两种方法各有适用场景,悲观锁适合高并发,乐观锁适合并发较低的情况。
摘要由CSDN通过智能技术生成

分布式锁概述

前言

我们的系统都是分布式部署的,日常开发中,秒杀下单、抢购商品等等业务场景,为了防⽌库存超卖,都需要用到分布式锁。

分布式锁其实就是,控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性。

业界流行的分布式锁实现,一般有这3种方式:

  • 基于数据库实现的分布式锁。
  • 基于Redis实现的分布式锁。
  • 基于Zookeeper实现的分布式锁。

那下面我们就来聊聊基于数据库实现的分布式锁。

基于数据库的分布式锁

数据库悲观锁实现的分布式锁

可以使用select … for update 来实现分布式锁。我们自己的项目,分布式定时任务,就使用类似的实现方案,我给大家来展示个简单版的哈!

表结构如下:

CREATE TABLE `t_resource_lock` (
  `key_resource` varchar(45) COLLATE utf8_bin NOT NULL DEFAULT '资源主键',
  `status` char(1) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT 'S,F,P',
  `lock_flag` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '1是已经锁 0是未锁',
  `begin_time` datetime DEFAULT NULL COMMENT '开始时间',
  `end_time` datetime DEFAULT NULL COMMENT '结束时间',
  `client_ip` varchar(45) COLLATE utf8_bin NOT NULL DEFAULT '抢到锁的IP',
  `time` int(10) unsigned NOT NULL DEFAULT '60' COMMENT '方法生命周期内只允许一个结点获取一次锁,单位:分钟',
  PRIMARY KEY (`key_resource`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin

加锁lock方法的伪代码如下:

@Transcational //一定要加事务
public boolean lock(String keyResource,int time){
   resourceLock = 'select * from t_resource_lock where key_resource ='#{keySource}' for update';
   
   try{
    if(resourceLock==null){
      //插入锁的数据
      resourceLock = new ResourceLock();
      resourceLock.setTime(time);
      resourceLock.setLockFlag(1);  //上锁
      resourceLock.setStatus(P); //处理中
      resourceLock.setBeginTime(new Date());
      int count = "insert into resourceLock"; 
      if(count==1){
         //获取锁成功
         return true;
      }
      return false;
   }
   }catch(Exception x){
      return false;
   }
   
   //没上锁并且锁已经超时,即可以获取锁成功
   if(resourceLock.getLockFlag=='0'&&'S'.equals(resourceLock.getstatus)
    && new Date()>=resourceLock.addDateTime(resourceLock.getBeginTime(,time)){
      resourceLock.setLockFlag(1);  //上锁
      resourceLock.setStatus(P); //处理中
      resourceLock.setBeginTime(new Date());
      //update resourceLock;
      return true;
   }else if(new Date()>=resourceLock.addDateTime(resourceLock.getBeginTime(,time)){
     //超时未正常执行结束,获取锁失败
     return false;
   }else{
     return false;
   } 
}

解锁unlock方法的伪代码如下:

public void unlock(String v,status){
      resourceLock.setLockFlag(0);  //解锁
      resourceLock.setStatus(status); S:表示成功,F表示失败
      //update resourceLock;
      return ;
}

整体流程:

try{
if(lock(keyResource,time)){ //加锁
   status = process();//你的业务逻辑处理。
 }
} finally{
    unlock(keyResource,status); //释放锁
}

其实这个悲观锁实现的分布式锁,整体的流程还是比较清晰的。就是先select … for update 锁住主键key_resource那个记录,如果为空,则可以插入一条记录,如果已有记录判断下状态和时间,是否已经超时。这里需要注意一下哈,必须要加事务哈。

2.2 数据库乐观锁实现的分布式锁

除了悲观锁,还可以用乐观锁实现分布式锁。乐观锁,顾名思义,就是很乐观,每次更新操作,都觉得不会存在并发冲突,只有更新失败后,才重试。它是基于CAS思想实现的。我以前的公司,扣减余额就是用这种方案。

搞个version字段,每次更新修改,都会自增加一,然后去更新余额时,把查出来的那个版本号,带上条件去更新,如果是上次那个版本号,就更新,如果不是,表示别人并发修改过了,就继续重试。

大概流程如下:

  • 查询版本号和余额。
select version,balance from account where user_id ='666';

假设查到版本号是oldVersion=1。
逻辑处理,判断余额。

if(balance<扣减金额){
   return}

left_balance = balance - 扣减金额;

进行扣减余额。

update account set balance = #{left_balance} ,version = version+1 where version 
= #{oldVersion} and balance>= #{left_balance} and user_id ='666';

大家可以看下这个流程图哈:
在这里插入图片描述
这种方式适合并发不高的场景,一般需要设置一下重试的次数。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值