实战:工作中对并发问题的处理 | 京东物流技术团队
1. 问题背景
问题发生在快递分拣的流程中,我尽可能将业务背景简化,让大家只关注并发问题本身。
分拣业务针对每个快递包裹都会生成一个任务,我们称它为 task。task 中有两个字段需要关注,一个是分拣中发生的异常(exp_type),另一个是分拣任务的状态(status)。另外,需要关注分拣状态上报接口,通过它来记录分拣过程中的异常和状态变更。
一般情况下,分拣机在分拣异常发生时会及时调用接口上报,在分拣完成时调用接口来标记为完成状态,两次接口调用的时间间隔较长,不会发生并发问题。
但是有一种特殊的分拣机,它不会在异常发生时及时上报,而是在分拣完成时将分拣过程中发生的异常和分拣结果一起上报,那么此时分拣状态上报接口在同一时间内就会有两次调用,这时便发生了预期外的并发问题。
我们先看下分拣状态上报接口的执行流程:
- 先查询到该分拣任务 task,默认情况下 exp_type 和 status 均为默认值0
- 分拣异常修改 task 中的 exp_type,分拣完成修改 status 字段信息
- 修改完成将 task 写入
并发问题发生的图示如下:
数据库初始值为1, 0, 0
,分拣异常和分拣完成几乎同时上报,它们都读取到该值。分拣异常动作将 exp_type 修改为9,写入数据库,此时数据库值为1, 9, 0
;分拣完成动作将 status 修改为1,写入数据库,使得数据库最终值为1, 0, 1
,它将异常字段的值覆盖掉了。正常情况下,最终值应该为1, 9, 1
,分拣完成动作应该读取到分拣异常完成后的值1, 9, 0
后再进行修改才对。
2. 解决方案
发生这个问题的原因很容易就能发现:两个事务同时执行读取-修改-写入序列,其中一个写操作在没有合并另一个写操作变更的情况下,直接覆盖了另一个写操作的结果,所以导致了数据的丢失。
这种问题是比较典型的丢失更新问题,可以通过对数据库读操作加锁或者改变数据库的隔离级别为可串行化使事务串行执行的方式进行避免。下面我会将大家在讨论避免丢失更新问题时提出的方案进行介绍,并尽可能的用代码来表现它们。
2.1 数据库读操作加锁和可串行化隔离级别
我们可以考虑:如果对每条Task数据修改的事务都是在当前事务完成之后才允许后续事务进行修改,使事务串行执行,那么我们就能够避免这种情况。比较直接的实现是通过显式加锁来实现,如下
select exp_type, status
from task
where id = 1
for update;
先查询该行数据的事务会获取到该行数据的排他锁,后续针对该数据的所有读写请求都会被阻塞,直到先前事务执行完将锁释放。
这样通过加锁的方式实现了事务的串行执行。但是,在为SQL添加加锁语句时,需要确定是不是为该行数据加锁而不是锁住了整个表,如果是后者,那么可能会造成系统性能严重下降,而且还需要关注有哪些业务场景使用到了该SQL,是否存在长时间执行的只读事务使用,如果存在的话可能会出现因加锁导致延迟和系统性能下降,所以需要谨慎的评估。
此外,可串行化的数据库隔离级别也能保证事务的串行执行,不过它针对的是所有事务。一般情况下为了保证性能,我们不会采用这种方案(默认使用MySQL可重复读隔离级别)。
MySQL的InnoDB引擎实现可串行化隔离级别采用的是2PL机制:在第一阶段事务执行时获取锁,第二阶段事务执行完成释放锁。
2.2 针对业务只修改必要字段
如果异常状态请求仅修改 exp_type 字段,分拣完成仅修改 status 字段的话,那么我们可以梳理一下业务逻辑,仅将必要修改的字段写入数据库,这样就不会发生丢失更新的异常,如下代码所示:
// 处理异常状态请求,封装修改数据的对象
Task task = new Task();
tast.setId(id);
task.setExpType(expType);// 更改数据
taskService.updateById(task);
在执行修改数据前,创建一个新的修改对象,并只为其必要修改字段赋值。但是还需要考虑的是:如果这个业务流程处理已经很复杂了,很可能不清楚该为哪些字段赋值而导致再发生新的异常,所以采用这种方法需要对业务足够熟悉,并且在修改完后进行充分的测试。
2.3 分布式锁
分布式锁的方法与方法一类似,都是通过加锁的方式来保证同时只有一个事务执行,区别是方法一的锁加在了数据库层,而分布式锁是借助Redis来实现。
这种实现方式的好处是锁的粒度小,发生锁争抢仅限于单个包裹,无需像数据库加锁一样去考虑锁的粒度和对相关业务的影响。伪代码如下所示:
// 分布式锁KEY
String distributedKey = String.format(DISTRIBUTED_KEY_PREFIX, packageNo);
try {// 分布式锁阻塞同一包裹号的修改lock(distributedKey);// 处理业务逻辑handler();
} finally {// 执行完解锁redissonDistributedLocker.unlock(distributedKey);
}
需要注意,lock()
加锁方法要保证加锁失败或发生其他异常情况不影响业务逻辑的执行,并设定好锁持有时间和等待锁的阻塞时间,此外解锁方法务必添加到finally代码块中保证锁的释放。
2.4 CAS
CAS是乐观的解决方案,它一般通过在数据库中增加时间戳列来记录上次数据更改的时间,当新的事务执行时,需要比对读取时该行数据的时间戳和数据库中保存的时间戳是否一致,以此来判断事务执行期间是否有其他事务修改过该行数据,只有在没有发生改变的情况下才允许更新,否则需要重试这个事务。样例SQL如下所示:
update task
set exp_type = #{expType}, status = #{status}, ts = #{currentTs}
where id = #{id} and ts = #{readTs}
它的原理不难理解,但是实现起来可能会存在困难,因为需要考虑在执行失败后该如何重试,重试的方式和重试的次数需要根据业务去判断。
巨人的肩膀
- 《数据密集型应用系统设计》第七章 事务
作者:京东物流 王奕龙
来源:京东云开发者社区 自猿其说Tech 转载请注明出处
相关文章:

实战:工作中对并发问题的处理 | 京东物流技术团队
1. 问题背景 问题发生在快递分拣的流程中,我尽可能将业务背景简化,让大家只关注并发问题本身。 分拣业务针对每个快递包裹都会生成一个任务,我们称它为 task。task 中有两个字段需要关注,一个是分拣中发生的异常(exp…...
解决VSCode CPU高占问题的方法
如果你也遇到VSCode的CPU占用过高的问题,可以尝试使用官方自带的插件Bisect(扩展二分查找)功能来查找具体是哪个扩展出了问题。 找到“糟糕”的扩展可能很容易,也可能很困难。 打开扩展视图 ( CtrlShiftX ),禁用扩展&…...
tensorflow 1.x和3090、cuda部署
这里写目录标题 3090、cuda和tensorflow 1.x 3090、cuda和tensorflow 1.x 因为3090只支持cuda11.0的版本,而tensorflow1.已经不再维护,没有出支持cuda11.0的版本了。 nvidia提供了TF1.x对RTX 3090、cuda11等新硬件的支持。卸载已有的tensorflow-gpu包和…...
vue-photo-preview( 照片预览功能 )
安装 npm install vue-photo-preview --save 引入项目 import preview from vue-photo-preview import vue-photo-preview/dist/skin.csslet options {fullscreenEl: false //是否可以全屏预览 }; Vue.use(preview, options)如果不写options 和 Vue.use(preview,options)&…...
Angular 独立组件入门
Angular 独立组件入门 如果你正在学习 Angular,那么你可能已经听说过独立组件(Component)。顾名思义,独立组件就是可以独立使用和管理的组件,它们能够被包含在其他组件中或被其他组件引用。 在本文中,我们…...
Lie group 专题:Lie 群
Lie group 专题:Lie 群 流形 流形的定义 一个m维流形是满足以下条件的集合M:存在可数多个称为坐标卡(图集)的子集合族.以及映到的连通开子集上的一对一映射,,称为局部坐标映射,满足以下条件 坐标卡覆盖M…...

Vue-打印组件页面
场景: 需要将页面的局部信息打印出来,只在前端实现,不要占用后端的资源。经过百度经验,决定使用 print-js和html2canvas组件。 1. 下载包 npm install print-js --save npm install --save html2canvas 2. 组件内引用 <script>impo…...

Python爬虫——scrapy_基本使用
安装scrapy pip install scrapy创建scrapy项目,需要在终端里创建 注意:项目的名字开头不能是数字,也不能包含中文 scrapy startproject 项目名称 示例: scrapy startproject scra_baidu_36创建好后的文件 3. 创建爬虫文件&…...
30 | 中国高校数据分析
一、数据源 本项目使用了两个csv的数据文件,一个是中国高校(大学)的数据,一个是中国高校专业设置的数据 数据基本栏位:高校(大学)的数据高校专业设置的数据学校学校省份专业类别城市专业名称地址国家特色专业水平层次办学类别办学类型985211双一流二、数据分析目标 本…...

开源低代码平台Openblocks
网友 HankMeng 想看低代码工具,正好手上有一个; 什么是 Openblocks ? Openblocks 是一个开发人员友好的开源低代码平台,可在几分钟内构建内部应用程序。 传统上,构建内部应用程序需要复杂的前端和后端交互,…...

每日汇评:黄金在 200 日移动平均线附近似乎很脆弱,关注美国零售销售
1、金价预计将巩固其近期跌势,至 6 月初以来的最低水平; 2、对美联储再次加息的押注继续限制了贵金属的上涨; 3、金融市场现在期待美国零售销售报告带来一些有意义的推动; 周二金价难以获得任何有意义的牵引力,并在…...

DFT笔记 DC/AC mode与Func
DFT scan可以分为DC和AC两种,区别如下图 DC模式需要ate测试机台提供test clock时钟(最快100M),DFT工程师需要升级普通reg变成带si和so,se pin的reg,并插入扫描链(scan chain)&#x…...
docker核心操作
docker核心操作 1、docker安装(1)可选参数:(2)输出参数解释:2、docker镜像(1)拉取镜像:[https://www.docker.com/](https://www.docker.com/)3、运行docker镜像(1) 运行容器:(2) 挂载硬盘:4、docker容器的生命周期5.1、进入容器内部5.2、怎么解决Error: exec fai…...

《电路》基础知识入门学习笔记
文章目录: 一:电路模型和电路规律 1.电路概述 2.电路模型 3.基本电路物理量:电流、电压、电功率和能量 4.电流和电压的参考方向 5.电路元件—电阻 6. 电路元件—电压源和电流源 7.受控电源 8.基尔霍夫(后面都要用这个方法…...

什么是P2P?
P2P (Peer-to-Peer) 是一种分布式的网络架构,其中各个节点(通常被称为“peers”或“节点”)直接进行数据共享和交换,而无需依赖中央服务器。P2P 网络强调平等的参与和共享,每个节点既可以是数据的消费者(下…...

matlab RANSAC拟合多项式曲线
目录 一、功能概述1、算法概述2、主要函数3、参考文献二、代码实现三、结果展示四、参考链接本文由CSDN点云侠原创,原文链接。爬虫网站自重,把自己当个人。爬些不完整的误导别人有意思吗???? 一、功能概述 1、算法概述 使用RANSAC对点进行多项式拟合。...
微信小程序nfc指令异常记录
小程序nfc相关代码: readEvent(){wx.getNFCAdapter().startDiscovery({success:(res)>{console.log(--------------start--------)console.log(res);wx.getNFCAdapter().onDiscovered(callback>{console.log(------------onDiscovered----------)console.log(callback)…...

10 - 把间隔的几个commit整理成1个
查看所有文章链接:(更新中)GIT常用场景- 目录 文章目录 把间隔的几个commit整理成1个 把间隔的几个commit整理成1个...

关于 Eclipse 的一场 “三角关系”
上个世纪 90 年代,世界上的计算机要么不联网,要么在企业内部联网。但是,在互联网的概念下,计算机之间共享信息和资源的需求成为了必要。 1995 年 5 月,Java 横空出世。Java 的父亲是当时凭借 Solaris 操作系统风头正盛…...

koa 使用 Mongoose 查询数据
Mongosee 操作符koa 使用 Mongoose 进行 翻页查询koa 使用 Mongoose 进行 多条件查询 mongosee 操作符 在使用 Koa 和 Mongoose 进行数据库查询时,你可以使用以下常用的操作符来构建查询条件: $eq:等于 示例:{ field: { $eq: valu…...
Elasticsearch的搜索流程描述
Elasticsearch 的搜索流程是一个结合 分布式查询、分片协同、结果聚合和排序 的复杂过程,其设计目标是在海量数据中实现快速检索和精准结果返回。以下是搜索流程的详细解析: 一、搜索流程总览 Elasticsearch 搜索流程示意图 (图源:Elastic 官方文档) 二、详细步骤解析 …...

JAVA理论-JAVA基础知识
1.Java 基础 知识 1.1 面向对象的特征(了解) 面向对象的特征:封装、继承、多态、抽象 封装:就是把对象的属性和行为(数据)结合为一个独立的整体,并尽量隐藏对象的内部细节,公开我希…...
在.NET Core控制器中获取AJAX传递的Body参数
.Net Core是支持前后端不分离式的开发的,如果在原始系统中采用不分离式开发,后面需要在原系统中增加功能,并且新的服务采用其他语言开发,且系统原来功能保持原样,这样前端系统可以单独调用新开发的接口。 但是&#x…...

基于3D对象体积与直径特征的筛选
1,目的 筛选出目标3D对象。 效果如下: 2,原理 使用3D对象的体积与直径特征进行筛选。 3,代码解析 3.1,预处理2.5D深度图。 * 参考案例库:select_object_model_3d.hdev * ****************************…...
Vue中的自定义事件
一、前言 在 Vue 的组件化开发中,组件之间的数据通信是构建复杂应用的关键。而其中最常见、最推荐的方式之一就是通过 自定义事件(Custom Events) 来实现父子组件之间的交互。 本文将带你深入了解: Vue 中事件的基本概念如何在…...

『uniapp』把接口的内容下载为txt本地保存 / 读取本地保存的txt文件内容(详细图文注释)
目录 预览效果思路分析downloadTxt 方法readTxt 方法 完整代码总结 欢迎关注 『uniapp』 专栏,持续更新中 欢迎关注 『uniapp』 专栏,持续更新中 预览效果 思路分析 downloadTxt 方法 该方法主要完成两个任务: 下载 txt 文件:通…...

【设计模式-3.4】结构型——代理模式
说明:说明:本文介绍结构型设计模式之一的代理模式 定义 代理模式(Proxy Pattern)指为其他对象提供一种代理,以控制对这个对象的访问,属于结构型设计模式。(引自《设计模式就该这样学》P158&am…...

DeviceNet转Modbus TCP网关的远程遥控接收端连接研究
在港口码头作业中,遥控器因其精确的操作控制和稳定的性能,已成为起重机货物装卸作业的重要辅助工具。然而,在某港口码头实施无线遥控器远程控制掘进机的过程中,由于通信协议的不兼容,遭遇了技术难题。具体而言…...
es在Linux安装
如果服务器能访问外网: wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.4.0.zip不能就手动下载,解压文件到合适目录 unzip elasticsearch-6.4.0.zip -d /opt/ mv /opt/elasticsearch-6.4.0 /opt/elasticsearch创建和日志…...

[10-2]MPU6050简介 江协科技学习笔记(22个知识点)
1 2 3 欧拉角是描述三维空间中刚体或坐标系之间相对旋转的一种方法。它们由三个角度组成,通常表示为: • 偏航角(Yaw):绕垂直轴(通常是z轴)的旋转,表示偏航方向的变化。 • 俯仰角&a…...