使用数据库进行分布式加锁实现

网络可视媒体的智能计算-胡事民 本文为胡事民教授在山大做报告的笔记,现场见下图: 可视媒体是承载视觉信息的媒体类型,如图像、视频等,它具有网络化、海量和无序涌现等特征。 可视媒体计算的研究意义 可视媒体作为一种信息承载的介质,是用来表达语义信息的。那么就有一种介质与信息之间相互转化的问题。转化方向正反两种,举个栗子说明一下: 1. 可视媒体→→\rightarrow语义信息:比如看到画面,理解其中发生的事件、各主题之... 阅读详情

1.使用数据库进行分布式加锁

行锁以mysql为例,进行举例 :

1.1 for update

在mysql中使用for update获得行锁。

for update是一种行级锁,又叫排它锁,一旦用户对某个行施加了行级锁,则该用户可以更新也可以查询也可以更新被加锁的数据行,其他用户只能查询,不能更新被加锁的数据行,如果其他用户想更新该表中的数据行,则也必须对表施加行级锁。

释放行级:

1.执行提交commit语句.

2.退出数据库

3.程序停止运行.

通常情况下,select语句是不会对数据加锁的,妨碍影响其他DDL或者DML操作。

select…for update是我们经常使用的手工加锁语句。如果查询条件带有主键,会锁行数据,没有则进行锁表操作。针对innodb引擎。

举个例子:

锁行示例:

drop table if exists data_user;


create table data_user(
id bigint(15) not null primary key,
name varchar(50),
code varchar(50)
)engine=innodb  comment='测试用户表';


insert into data_user (id,name,code) values(1,'name1','code1');
insert into data_user (id,name,code) values(2,'name2','code2');
insert into data_user (id,name,code) values(3,'name3','code3');

在命令行1中输入:

mysql> begin;

select * from data_user where  id = 2 for update;
Query OK, 0 rows affected (0.00 sec)

+----+-------+-------+
| id | name  | code  |
+----+-------+-------+
|  2 | name2 | code2 |
+----+-------+-------+
1 row in set (0.01 sec)

mysql> 

在命令行2中输入

mysql> begin;

select * from data_user where  id = 2 for update;
Query OK, 0 rows affected (0.00 sec)

会发现SQL卡住了,这就是锁行了.

然后再命令行1中操作

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

这时命令行2中就可以看到:

mysql> begin;

select * from data_user where  id = 2 for update;
Query OK, 0 rows affected (0.00 sec)

+----+-------+-------+
| id | name  | code  |
+----+-------+-------+
|  2 | name2 | code2 |
+----+-------+-------+
1 row in set (46.07 sec)

之前卡住的信息又恢复了。

这就是行锁最简单的一个演示,如果在使用for update不加主键那将是锁表。

for update实现分布式锁

使用for update实现一个分布式场景下的锁:

先给出数据库的SQL:

drop table if exists goods;

create table goods(
id bigint(15) not null primary key,
name varchar(50),
dataNum int(5)
)engine=innodb  comment='商品信息';

insert into goods values(1,'mac',800);

先看数据库操作:

public class JdbcOperator {
  private static final String DRIVER_CLASS = "com.mysql.jdbc.Driver";
  private static final String MYSQL_URL =
      "jdbc:mysql://localhost:3306/zsc?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";
  private static final String USER_NAME = "root";
  private static final String PASSWORD = "123456";

  static {
    // 加载驱动
    try {
      Class.forName(DRIVER_CLASS);
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    }
  }

  /**
   * 批量SQL操作
   *
   * @param sqlList
   * @return
   */
  public static int executeSql(List<String> sqlList) {

    int updateRsp = 0;
    try (Connection connection = DriverManager.getConnection(MYSQL_URL, USER_NAME, PASSWORD); ) {
      // 关闭自动提交
      connection.setAutoCommit(false);

      for (int i = 0; i < sqlList.size(); i++) {
        runStatementSql(sqlList.get(i), connection);
      }

      // 完成后提交事务
      connection.commit();
    } catch (SQLException e) {
      e.printStackTrace();
    }

    return updateRsp;
  }

  private static void runStatementSql(String sql, Connection connection) {
    try (PreparedStatement stat = connection.prepareStatement(sql)) {
      stat.execute();
      System.out.println(Thread.currentThread().getId() + "," + sql);
    } catch (SQLException e) {
      e.printStackTrace();
    }
  }
}

调用进行扣减库存操作:

public class Goods {

  public Goods() {}

  /** 商品的库存扣减操作 */
  public void minusGoods(int num) {

    List<String> sqlList = new ArrayList<>();
    sqlList.add("begin;");
    sqlList.add("select * from goods where  id = 1 for update;");
    sqlList.add("update goods set dataNum = dataNum-" + num + " where id = 1;");

    // 扣减库存操作
    JdbcOperator.executeSql(sqlList);
  }
}

订单信息:

public class Orders {

  /** 商品服务 */
  private Goods goods;

  public Orders(Goods goods) {
    this.goods = goods;
  }

  /**
   * 创建订单
   *
   * @return
   */
  public boolean createOrder(int num) {
    // 执行扣减库存操作
    goods.minusGoods(num);
    return true;
  }
}

测试分布式场景下的锁:

public class TestOrdersLock {

  @Test
  public void useOrder() throws InterruptedException {
    int orderNumSum = 800;
    Goods goods = new Goods();

    // 并发进行下单操作
    int maxOrder = 4;

    int count = 0;
    for (int i = 0; i < orderNumSum / maxOrder; i++) {
      CountDownLatch startLatch = new CountDownLatch(maxOrder);
      for (int j = 0; j < maxOrder; j++) {
        TaskThreadPool.INSTANCE.submit(
            () -> {
              startLatch.countDown();

              Orders instance = new Orders(goods);
              instance.createOrder(1);
            });

        count++;
      }
      // 执行等待结果
      try {
        startLatch.await();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
    System.out.println("结束,共运行:" + count + "次");

    TaskThreadPool.INSTANCE.shutdown();

    Thread.sleep(500);

    System.out.println("shutdown status:" + TaskThreadPool.INSTANCE.getPool().isShutdown());
  }
}

单元测试的结果在控制台可以看到:

15,begin;
12,begin;
13,begin;
16,begin;
19,begin;
17,begin;
14,begin;
18,begin;
13,select * from goods where  id = 1 for update;
13,update goods set dataNum = dataNum-1 where id = 1;
12,select * from goods where  id = 1 for update;
12,update goods set dataNum = dataNum-1 where id = 1;
15,select * from goods where  id = 1 for update;
15,update goods set dataNum = dataNum-1 where id = 1;
19,select * from goods where  id = 1 for update;
19,update goods set dataNum = dataNum-1 where id = 1;
16,select * from goods where  id = 1 for update;
16,update goods set dataNum = dataNum-1 where id = 1;
17,select * from goods where  id = 1 for update;
17,update goods set dataNum = dataNum-1 where id = 1;
14,select * from goods where  id = 1 for update;
14,update goods set dataNum = dataNum-1 where id = 1;
18,select * from goods where  id = 1 for update;
18,update goods set dataNum = dataNum-1 where id = 1;
......
13,begin;
13,select * from goods where  id = 1 for update;
13,update goods set dataNum = dataNum-1 where id = 1;
结束,共运行:800次
17,begin;
17,select * from goods where  id = 1 for update;
17,update goods set dataNum = dataNum-1 where id = 1;
shutdown status:true

检查数据库的结果

mysql> select * from goods;
+----+------+---------+
| id | name | dataNum |
+----+------+---------+
|  1 | mac  |       0 |
+----+------+---------+
1 row in set (0.02 sec)
劣势

讲完了实现,再来说说这个实现的问题吧。

  1. 锁操作过程中如果没有正确的提交,那将导致数据库表被锁表,其他进程将不能操作锁定的表,直白点,将直接引发生产事故。
  2. for update将占用数据库连接,如果在操作过程中使用时间过长,就容易造成锁超时。
  3. 如果并发量过大,会造成数据库直接宕机。

所以此分布式锁使用务必小心,不推荐使用。

适用的场景

使用数据库的for update的加锁机制适用于并发不是特别大的场景,这是因为数据库所能承载的并发是较小的,由于每个锁就占用一个数据库连接,如果操作时间过长,将可能导致锁超时,使用过程也务必要小心,如果某个线程使用了for update,那么在事务运行过程宕机了,那么这个锁将得不到释放,需要手动处理。如果要给出一个参考的数据的话,此并发在几十。

开源工具对比:5款主流大数据脱敏框架评测 数据脱敏(Data Masking/Anonymization),也称为数据漂白、数据去标识化,是指在不影响数据可用性的前提下,通过一定的技术手段对敏感信息进行处理,使其无法被识别或关联到具体个人或实体的过程。当应用程序向数据库写入数据(INSERT/UPDATE)时,ShardingSphere 会拦截 SQL,对配置了脱敏规则的字段,使用指定的算法进行加密/脱敏处理,然后将处理后的数据写入到。如果你的答案是“是”,那么数据脱敏,尤其是针对海量数据的。好的,各位技术同仁,数据安全领域的探索者们,大家好。 阅读详情

相关推荐

从零构建机械臂规划栈:Pinocchio、OMPL与Ruckig的工程化融合实践

本文详细介绍了如何从零构建机械臂规划栈,通过融合Pinocchio、OMPL和Ruckig三大核心组件,实现高性能的机械臂运动规划与轨迹生成。文章深入探讨了技术选型、工程架构设计及实战优化技巧,特别针对逆解、路径规划和轨迹平滑等关键环节提供了实用解决方案,适用于工业场景中的高实时性需求。

weixin_29072299的博客 433

【for update】解析 【线程安全】【分布式锁】

for update是一种DML操作级别的行级锁。在结合事务的场景中,事务A对一行数据使用select … for update加锁,其他事务只能查询但不能更新被加锁的数据行,而事务A既可以查询又可以更新该行数据。事务A提交后,其他事务才可以更新该行。

日月既往,不可复追 1473

游戏开发实战:5种AOI算法对比(九宫格 vs 灯塔 vs 十字链表)

本文深入对比了游戏开发中五种主流AOI算法(九宫格、灯塔、十字链表等)的性能与适用场景,帮助开发者根据项目规模选择最优解决方案。通过详细的技术分析和实战数据,揭示不同算法在万人同屏等极端场景下的表现差异,为MMORPG等大型多人在线游戏提供关键性能优化指南。

weixin_29268385的博客 474

万字长文!不为人所知的分布式锁实现全都在这里了

光给面试题不给答案不是我的风格。这里面的面试题也只是凤毛麟角,还有答案的话会极大的增加文章的篇幅,减少文章的可读性光给面试题不给答案不是我的风格。这里面的面试题也只是凤毛麟角,还有答案的话会极大的增加文章的篇幅,减少文章的可读性。

2401_84011114的博客 905

【补充:脑电绘图教程 2】如何手动去除ICA伪迹分量

ICA可以将脑电信号分解为多个IC分量,由每个分量的平均功率地形图可以看出哪些分量属于伪迹,去除伪迹分量后,即可得到“干净”的脑电信号。本教程主要总结如何人工识别出ICA伪迹分量,不涉及ICA的原理讲解。本教程的主要知识来源是ICLabel官网ICLabel致力于自动识别IC分量属于哪种伪迹,并标记伪迹占比,可以在eeglabel中很方便地使用。ICLabel给出了几种常见伪迹的特点,本教程主要是根据ICLabel给出伪迹特点,对自己的数据集手动去除伪迹,以更深入了解自己数据集的质量和伪迹特点。

qq_40735196的博客 4230

数学建模学习(78):多输入多输出回归预测模型(结合XGBoost实现)

多输入多输出回归预测问题求解

全栈川川 8130

常见分布式锁2:数据库分布式锁select for update或者维护一张lock表

数据库分布式锁select for update或者维护一张lock表

L'Étranger的博客 1021

【原创】下载RealEstate10K数据集原始视频的方法

目前互联网上能搜到下载RealEstate10K数据集原始视频的方法都已经不能用了,这篇博客介绍一种目前可用的下载RealEstate10K数据集原始视频的方法,并给出自动化的脚本代码。

沉迷单车的追风少年 2461

Java架构直通车——基于数据库for update实现分布式锁

文章目录使用数据库解决超卖问题(非分布式)分为三步:原始方法合并二、三步:使用update行锁使操作下沉到数据库合并一、二、三步:使用方法锁优化:使用块锁使用数据库解决分布式超卖问题 使用数据库解决超卖问题(非分布式) 解决库存超卖问题,可以另扣库存不在程序中运行,而是通过数据库。向数据库传递库存增量,扣件N个库存,增量为-N。也就是在数据库update语句计算库存,通过update行锁解决并发。...

No_Game_No_Life_的博客 1583

北斗导航 | 接收机自主完好性监测(RAIM)算法近三年研究现状总结(2022-2025)

近三年RAIM算法已从传统的单故障检测演进为多星座协同、智能融合的复杂系统,在航空、自动驾驶等安全关键领域实现规模化应用。未来需重点突破城市复杂环境适应性和标准化体系建设,推动技术从"可用"向"可信"跨越。

尘世冰封的专栏 439

手把手教你学PCIE(1.38)--基本概念:ATU(地址转换单元)

ATU(Address Translation Unit)是一种硬件单元,用于在PCIe系统中进行地址转换。它将设备的本地地址(外部地址)转换为系统内存或配置空间中的全局地址(内部地址),反之亦然。ATU的工作原理:通过地址匹配和转换,确保事务能够在不同的地址空间之间正确路由。配置寄存器:通过配置寄存器来设置地址转换规则。应用场景:地址空间隔离、访问控制、动态地址管理、虚拟化和多租户。

MHD0815的博客 2054

基于数据库来实现分布式锁

三种方式解决单体锁不能夸jvm的局限性

qq_44299529的博客 1127

深入剖析Zynq AMP模式下CPU1中断响应机制:从原理到创新实践

在异构计算领域,Zynq-7000系列SoC的AMP(Asymmetric Multiprocessing)模式堪称多核编程的瑞士军刀。独立运行环境:CPU0运行Linux,CPU1运行FreeRTOS或裸机程序硬件资源独占:CPU0管理DDR,CPU1使用OCM(片上内存)中断隔离:默认所有中断路由到CPU0这种架构非常适合需要实时响应的工业控制系统——比如用CPU0处理网络通信,CPU1负责电机控制。AMP模式下中断路由的底层原理多维度性能优化技巧工业级可靠性的实现方案。

QQ_778132974的博客 490

定时任务 @Schedule注解 分布式加锁

@Schedule注解可以在sprintboot项目中定时启动某个方法,但是如果是分布式部署方案,如k8s集群等,每个节点都会运行此方法,就会造成一系列的问题,此时就要用到@SchedulerLock 分布式加锁注解 maven: <dependency> <groupId>net.javacrumbs.shedlock</groupId> <artifactId>shedlock-spring</artifactId> <versio

u010925982的博客 2325

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

DCS实践干货:使用Redis实现分布式锁

场景介绍 很多互联网场景(如商品秒杀,论坛回帖盖楼等),需要用加锁的方式,以对某种资源进行顺序访问控制。如果应用服务集群部署,则涉及到对分布式应用加锁。当前分布式加锁主要有三种方式:(磁盘)数据库、缓存数据库、Zookeeper。接下里让我们一起看看加锁实践过程。 加锁实现测试代码假设20个线程对10台mate10手机进行抢购:运行结果配置好实际的缓存实例连接地址、端口与连接密码,运行代码,得到以...

weixin_34342992的博客 152

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解分布式情况下如何添加分布式锁 【续篇】

SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解分布式情况下如何添加分布式锁 【续篇】

日常笔记 | 干货分享 | 毕设源码 | 毕设指导 | 答辩指导 1706

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

深入理解Spring Redis的使用 (九)、通过Redis 实现 分布式锁 的 BUG,以及和数据库加锁的性能测试...

在多节点的项目中,经常要涉及到某些方法加锁的控制。而这个时候,简单易用的synchronized已经不能满足多节点的部署结构。 之前在项目中,用的比较多的是数据库的更新锁:for udpate。但是这个有个缺点,就是对于本来就容易出现瓶颈的数据库,造成了更大的压力。同时,如果是锁表的语句,同时表数据量特别大,基本服务器直接宕机了。 所以,决定绕开数据库,直接使用Redis来实现分布式锁。查了下...

weixin_30384217的博客 195

轻量级分布式定时器ShedLock使用记录

shedlock-spring分布式定时器的使用和源码分析

weixin_43956650的博客 1367

SpringBoot定时任务@Scheduled与分布式定时任务锁@SchedulerLock

spring boot 版本: <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.0</version> <relativePath/> </parent> 启动类: @Spring

之一的博客 7563

SpringBoot配置ShedLock-轻量级分布式定时任务

什么是ShedLock ShedLock是一个在分布式环境中使用的定时任务框架,用于解决在分布式环境中的多个实例的相同定时任务在同一时间点重复执行的问题,解决思路是通过对公用的数据库中的某个表进行记录和加锁,使得同一时间点只有第一个执行定时任务并成功在数据库表中写入相应记录的节点能够成功执行而其他节点直接跳过该任务。当然不只是数据库,目前已经实现的支持数据存储类型除了经典的关系型数据库,还包括M...

qq_25933249的博客 5005

实现高并发秒杀的 7 种方式,写的太好了,建议收藏!!

Disruptor是个高性能队列,研发的初衷是解决内存队列的延迟问题,在性能测试中发现竟然与I/O操作处于同样的数量级,基于Disruptor开发的系统单线程能支撑每秒600万订单。对于上面超卖现象,主要问题出现在事务中锁释放的时机,事务未提交之前,锁已经释放。对于并发量大于商品数的情况,商品秒杀一般不会出现少卖的请况,但对于并发数小于等于商品数的时候可能会出现商品少卖情况,这也很好理解。经过测试,发现使用Disruptor队列队列,与自定义队列有着同样的问题,也会出现超卖的情况,但效率有所提高。

Java技术栈,分享最主流的Java技术 997

分布式锁的实现方式

  目前市面上很多网站及应用都是分布式部署的,分布式场景中,数据一致性问题一直都是比较重要的话题。在单机环境中,Java提供了很多方法去处理多线程并发方面的情况,例如synchronized,ReentrantLock,ReentrantReadWriteLock等等,而在分布式场景中,这些API就无能为力了,所以引入了分布式锁这个概念。    一、为什么要用分布式锁   要想弄清楚分布式锁...

weixin_30478923的博客 107

RedisTemplate下Redis分布式锁引发的系列问题

自己的项目因为会一直抓取某些信息,但是本地会和线上经常一起跑,造成冲突。这其实就是我们常说的分布式集群的问题了,本地和线上的服务器构成了集群以及QPS为2的小并发(其实也不叫并发,不知道拿什么词形容?)。 首先,分布式集群的问题大家都知道,会造成数据库的插入重复问题,会造成一系列的并发性问题。 解决的方式呢也大概如下几点,百度以及谷歌上都能搜到的解决方式: ...

lmx125254的博客 2万+

mysql get seq no_dotNET Core实现分布式环境下的流水号唯一

原标题:dotNET Core实现分布式环境下的流水号唯一 业务背景在管理系统中,很多功能模块都会涉及到各种类型的编号,例如:流程编号、订单号、合同编号等等。编号各有各自的规则,但通常有一个流水号来确定编号的唯一性,保证流水号的唯一,在不同的环境中实现方式有所不同。本文将介绍在单机和分布式环境中保证流水号唯一的方式。实现思路1、在数据库中创建 seqno 表,每个业务一条数据,存储业务 code ...

weixin_32821043的博客 590

集群环境下多节点分布式定时任务数据库乐观锁

背景 Spring Task并不是为分布式环境设计的,在分布式环境下,这种定时任务是不支持集群配置的,如果部署到多个节点上,各个节点之间并没有任何协调通讯机制,因为集群的节点之间是不会共享任务信息的,每个节点上的任务都会按时执行。 解决方案 我们选择了用数据库+乐观锁的方式来解决任务互斥访问的问题。大致的思路是这样的,声明一把全局的“锁”作为互斥量,哪个应用服务器拿到这把“锁”,就有执行任务的权利,未拿到“锁”的应用服务器不进行任何任务相关的操作。 java + mongodb数据库实现 定时任务

flyingwzb的博客 1170

定时任务双边部署引发的问题-redis锁解决

一,问题描述   接之前一篇博客@schedule定时任务开启两次,使用xml配置解决以后,楼主部署war包上生产遇到另一个问题。   由于楼主公司paas应用是双边部署,异地双活机制,导致每台服务器的定时任务都启动一次。 二,解决方案   想过用数据库乐观锁解决问题,执行定时任务之前去数据库获取锁,其他线程将无法获取锁执行程序。考虑到性能问题,采用另一种方案:redis锁。当然还有zoo...

weixin_30314793的博客 242
上一篇: 总结JAVA中的锁
下一篇: 使用redis进行分布式加锁实现
null_null_bug
博客等级 码龄7年 72粉丝 71原创
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值