實戰:工作中對并發問題的處理 | 京東物流技術團隊

1. 問題背景

問題發生在快遞分揀的流程中,我盡可能將業務背景簡化,讓大家只關注并發問題本身。

分揀業務針對每個快遞包裹都會生成一個任務,我們稱它為 task。task 中有兩個字段需要關注,一個是分揀中發生的異常(exp_type),另一個是分揀任務的狀態(status)。另外,需要關注分揀狀態上報接口,通過它來記錄分揀過程中的異常和狀態變更。

Task概覽.png

一般情況下,分揀機在分揀異常發生時會及時調用接口上報,在分揀完成時調用接口來標記為完成狀態,兩次接口調用的時間間隔較長,不會發生并發問題。

但是有一種特殊的分揀機,它不會在異常發生時及時上報,而是在分揀完成時將分揀過程中發生的異常和分揀結果一起上報,那么此時分揀狀態上報接口在同一時間內就會有兩次調用,這時便發生了預期外的并發問題。

我們先看下分揀狀態上報接口的執行流程:

  1. 先查詢到該分揀任務 task,默認情況下 exp_type 和 status 均為默認值0
  2. 分揀異常修改 task 中的 exp_type,分揀完成修改 status 字段信息
  3. 修改完成將 task 寫入

并發問題發生的圖示如下:

并發問題流程圖.png

數據庫初始值為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 轉載請注明出處

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/37779.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/37779.shtml
英文地址,請注明出處:http://en.pswp.cn/news/37779.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

DIP: Spectral Bias of DIP 頻譜偏置解釋DIP

On Measuring and Controlling the Spectral Bias of the Deep Image Prior 文章目錄 On Measuring and Controlling the Spectral Bias of the Deep Image Prior1. 方法原理1.1 動機1.2 相關概念1.3 方法原理頻帶一致度量與網絡退化譜偏移和網絡結構的關系Lipschitz-controlle…

Linux常規操作命令

日升時奮斗,日落時自省 目錄 1、vim 1.1、工作模式 1.2、末行模式操作相關命令 1.2.1、保存退出操作 1.2.2、查找替換 1.3、輸入模式操作相關命令 1.3.1、移動相關命令 1.3.2、刪除和剪切命令 1.3.3、復制操作 1.3.4、撤銷 2、head 3、tail 4、ps 5、…

數據結構算法--2 冒泡排序,選擇排序,插入排序

基礎排序算法 冒泡排序 思想就是將相鄰元素兩兩比較,當一個元素大于右側相鄰元素時,交換他們的位置,小于右側元素時,位置不變,最終序列中的最大元素,像氣泡一樣,到了最右側。 這時冒泡排序第一…

linux Socket簡單編程實例

服務端 網絡編程中服務端接受連接的套接字創建過程如下: 1.調用socket函數創建套接字 2.調用bind函數分配IP地址和端口號 3.調用listen函數轉為可接收請求狀態 4.調用accept函數受理連接請求 #include <stdio.h> #include <stdlib.h> #include <sys/types.h>…

Java實現根據姓名生成頭像(釘釘樣式)

頭像生成器代碼如下&#xff1a; package com.hua.util;import org.apache.commons.lang3.StringUtils;import javax.imageio.ImageIO; import java.awt.*; import java.awt.geom.RoundRectangle2D; import java.awt.image.BufferedImage; import java.io.File; import java.i…

配置listener tcps加密 enable SSL encryption for Oracle SQL*Net

一 配置客戶端和服務端的wallet 2端配置方法一致&#xff0c;相互添加證書 orapki wallet create -wallet “/u01/oracle/wallet” -pwd Wdkf984jkkgekj434FKFD -auto_login_local orapki wallet add -wallet “/u01/oracle/wallet” -pwd Wdkf984jkkgekj434FKFD -dn “CNho…

Zabbix監控MySQL數據庫實戰

zabbix監控mysql的方式 只是安裝agent 啟用模板監控 啟用自定義腳本的模板監控 使用zabbix模版及結合shell腳本監控mysql 創建mysql的zabbix授權用戶 mysql> grant all PRIVILEGES on *.* to zabbixlocalhost identified by zabbix; ###創建一個有權限的訪問用戶lqb密碼設…

es1.7.2 按照_type先聚合,再按照時間二次聚合

// 設置查詢條件if (this.query ! null) {this.searchbuilder.setQuery(this.query);}TermsBuilder typeAggregation AggregationBuilders.terms("agg_type").field("_type");DateHistogramBuilder dateTermsBuilder AggregationBuilders.dateHistogram(…

[Android] 通過JNI 讓 JAVA 調用 android native 接口

前言&#xff1a; JNI (java native interface) 是一個庫&#xff0c;可以讓 java 代碼和其他語言互動&#xff0c;比如 java 通過 JNI 調用融合了 jni庫的 c/c 代碼&#xff0c;注意&#xff0c;這里要求 c/c代碼中必須通過鏈接 jni 庫并按照 JNI 規范定義一套可供 JAVA 調用…

STM32自帶的DSP庫的濾波初體驗(一)

最近在弄STM32自帶的DSP庫里的濾波&#xff0c;記錄一下&#xff1a; arm_fir_instance_q15 instance_q15_S; #define NUM_TAPS 16 //濾波系數的個數 #define BLOCK_SIZE 32 q15_t firStateF32[BLOCK_SIZE NUM_TAPS]; q15_t Fir_Coeff[NUM_TAPS] {-79, -136, 312, 6…

【02】基礎知識:typescript數據類型

1、布爾類型 boolean let flag: boolean false2、數字類型 number let num: number 6 //十進制 let num2: number 0xf00d //十六進制 let num3: number 0b1010 //二進制 let num4: number 0o744 //八進制3、字符串類型 string 用雙引號&#xff08;“&#xff09;或單引…

MongoDB 簡介

什么是MongoDB ? MongoDB 是由C語言編寫的&#xff0c;是一個基于分布式文件存儲的開源數據庫系統。 在高負載的情況下&#xff0c;添加更多的節點&#xff0c;可以保證服務器性能。 MongoDB 旨在為WEB應用提供可擴展的高性能數據存儲解決方案。 MongoDB 將數據存儲為一個…

mqttfx連上OneNET生成token時的一大坑,報用戶名或密碼錯誤

整個流程如下連接&#xff1a; MQTT.fx和MQTTX 鏈接ONENET物聯網開發平臺避坑細節干貨。 其中在生成token時&#xff0c;搞了半天在連接后都會報用戶名密碼錯誤 最后發現是格式問題&#xff0c;輸入所有字符后一定要雙擊看是否可以全選中&#xff0c;可以全選中說明字符的格式…

java spring cloud 企業工程管理系統源碼+二次開發+定制化服務 em

Java版工程項目管理系統 Spring CloudSpring BootMybatisVueElementUI前后端分離 功能清單如下&#xff1a; 首頁 工作臺&#xff1a;待辦工作、消息通知、預警信息&#xff0c;點擊可進入相應的列表 項目進度圖表&#xff1a;選擇&#xff08;總體或單個&#xff09;項目顯…

springBoot中service層查詢使用多線程CompletableFuture(有返回值)

重點&#xff1a; 1. 創建線程池 Executor executor Executors.newCachedThreadPool();//保存線程List<CompletableFuture<Void>> futures new ArrayList<>();2.使用 //這里可以是多個看下面代碼是在for中使用的 CompletableFuture<Void> future …

深入解析路由與網絡:網絡的脈絡

目錄 路由 廣域網 公網 外網 局域網 內網 以太網 Wi-Fi CDN IPv4和IPv6 IP地址分類 無類別域間路由&#xff08;CIDR&#xff09; 路由 路由是指在計算機網絡中&#xff0c;將數據包從源地址傳遞到目標地址的過程。在一個復雜的網絡中&#xff0c;數據包需要經過多…

冶金作業VR虛擬仿真廠家

對于高風險行業來說&#xff0c;開展安全教育培訓是企業的重點工作&#xff0c;傳統培訓逐漸跟不上時代變化和工人需求&#xff0c;冶金安全VR模擬仿真培訓系統作為一種新型的教育和培訓工具&#xff0c;借助VR虛擬現實技術為冶金行業的工人提供一個安全、高效的培訓環境。 冶金…

Angular中的管道Pipes

Angular中的管道&#xff08;Pipes&#xff09;是一種強大的工具&#xff0c;它可以處理和轉換數據&#xff0c;然后將其呈現在視圖中。它們可以被用于排序、格式化和過濾數據等任務。在本文中&#xff0c;我們將介紹Angular中的管道以及如何使用它們來簡化開發過程。 管道的基…

Ansible Service模塊,使用 Ansible Service模塊進行服務管理

Ansible 是一種自動化工具&#xff0c;它可以簡化配置管理、應用程序部署和任務自動化等操作。Ansible 的 Service 模塊是其中一個重要的模塊&#xff0c;它提供了管理服務的功能&#xff0c;使得在遠程主機上啟動、停止、重啟和重新加載服務變得簡單和可靠。本文將介紹 Ansibl…

大疆秋招指南,網申測評和面試攻略

大疆秋招內容簡介 這是一個非常卷的時代&#xff0c;一到畢業季&#xff0c;各種各樣規模不一的公司&#xff0c;紛紛向社會招聘&#xff0c;競爭實力強&#xff0c;知名度越高的企業&#xff0c;往往越能得到能力出眾的人才的青睞&#xff0c;也正是在一批批新血液的注入下&a…