主頁 > 軟體設計 > vivo 全球商城:電商平臺通用取貨碼設計

vivo 全球商城:電商平臺通用取貨碼設計

2022-09-14 06:53:53 軟體設計

vivo官網商城開發團隊 - Zhou Longjian

一、背景

隨著O2O線上線下業務的不斷擴展,電商平臺也在逐步完善交易側相關的產品功能,在最近的需求版本中,業務方為進一步提升用戶的使用體驗,規劃了取貨碼生成及訂單核銷相關邏輯,目的是讓線上的用戶在付完款之后能夠到店取歡訓者安排導購派送,

日常生活中,我們對取貨碼、核銷這類功能使用的經歷大部分都來自:看電影前取票、吃飯后出示券碼、快遞柜取包裹等等,它們都有一些類似的特點,比如:

  • 取貨碼長度相對較短,比起動輒十幾二十位訂單號,幾位的數字碼更方便記憶和輸入;

  • 除了數字取貨碼,還提供二維碼,方便終端進行掃描并核銷,

取貨碼使用起很簡單,然而像“冰山”一樣,隱藏在簡單外表下面卻需要嚴謹的設計和細致的邏輯,可以說麻雀雖小五臟俱全,本文介紹的設計也比較有趣,而且按此思路可以實作市面上大多數核銷類券碼的生成,同時也能滿足業務的SaaS化,算是一個相對通用的能力,在此把整個設計分享給大家,

圖片

(圖片來源:pixabay.com)

二、簡單系統的單表業務

如果業務的體量不大,店鋪流量比較小,未形成平臺的規模,比如給個體經營者使用的系統,那么取貨碼或券碼的實作就比較簡單,跟訂單共享一張大橫表或者使用擴展表跟訂單進行關聯就行了,這個階段也無需做過度設計,

表的設計如下圖:

圖片

不過需要注意的是一般訂單號都是比較長的,通常都在十幾二十位(當然也有比較短的訂單號,如果訂單號比較短,取貨碼也可采用訂單號)我們假設訂單號18位,取貨碼8位,即訂單號的取值范圍遠大于取貨碼,那么在訂單號的生命周期內,取貨碼是有很大幾率存在重復的,解決起來相對簡單,我們只需要保證在任意條件下,未核銷狀態的數字碼不重復即可,也即已核銷的數字碼可以回收利用,

那么取貨碼的生成邏輯就很清晰了,下面用偽代碼模擬真實的實作邏輯:

偽代碼實作

for (;;) {
   step1 獲取隨機碼:String code = this.getRandomCode();
   step2 執行SQL:SELECT COUNT(1) FROM order_main WHERE code = ${code} AND write_off_status = 0;
   step3 判斷是否可以插入:if ( count > 0) { continue; }
   step4 執行資料寫入:UPDATE order_main SET code = ${code}, qr_code = ${qrCode}, write_off_status = 0 WHERE order_no = ${orderNo}
}

*注意:這里step2和step4不是原子操作,存在并發問題,實際應用中最好使用分布式鎖,把操作鎖住,

三、 復雜平臺的分庫分表業務

通過簡單的單表設計,我們能管窺一斑,了解取貨碼大致的實作邏輯,不過我們在把簡單方案往大型專案上進行落地的時候,就需要考慮很多方面,設計也需要更精巧,SaaS化的電商平臺會比簡單的單表業務復雜很多,重點體現在:

  1. SaaS 產品涉及的店鋪很多且訂單量大,需要設計大容量存盤,所以訂單表基本使用分庫分表,顯然作為訂單附屬的取貨碼表也得使用相同的策略;

  2. B端和C端用戶的體驗非常重要,服務端介面的設計需要充分考慮魯棒性,完善最基本的重試及容錯能力;

  3. 不同業務方對于取貨碼的要求可能不太一樣,取貨碼的設計需要具有通用性以及個性化的配置屬性,

3.1 詳細設計

取貨碼表的設計推薦使用和訂單一致的分庫分表策略,好處是:

  1. 和訂單一樣,支撐海量訂單行的存盤;

  2. 方便利用同樣的分庫分表因子進行查詢(例如:open_id、member_id),

在考慮落地實作上,我們遇到了第一個討論的點,那就是取貨碼是做到“門店唯一”還是“全域唯一”?

3.2 門店唯一方案

剛開始考慮使用類似飯館取餐碼類似的邏輯,保證取貨碼在各自門店保持唯一就行了,類似如下圖互動,圖中用戶A和用戶B持有相同的取貨碼,用戶A、B分別去他們對應的店鋪完成核銷,整個交易程序就結束了,但是這得保證用戶A和B能正確地在各自訂單歸屬的店鋪完成核銷,顯然這個方案是帶有風險的!

圖片

下圖所示的這種情況下,用戶A、B也能正常核銷,不過串單了,原本屬于用戶A的訂單被用戶B核銷了,這種問題出現的本質原因在于純粹的數字碼無法帶有用戶的標識,雖然可以在核銷前做人為的核驗身份來避免,但依然屬于高風險的系統設計,所以門店唯一方案不可取!

圖片

3.3 全域唯一方案

全域唯一方案風險小,但實作難度稍高一點,核心問題在于如何判定隨機生成的取貨碼是全域唯一的,當然如果系統本身依賴ES這類存盤介質,可以在插入前先查詢ES,不過查詢和寫入ES對于實時性介面來說稍微有點重,沒有直接查庫表來得直接,假設某業務方分成了4個庫4張表,總計16表,取貨碼的長度確定為8位,那如何在多庫多表的Mysql中查詢并保證全域唯一呢?遍歷表的方式肯定不可取!

圖片

為解決上述的疑問,我們在設計的時候可以在取貨碼的編排上做點文章,如下步驟做具體詳解:

步驟①: 可以將8位的取貨碼分成兩個區域,“隨機碼區域”+“庫表位置”,下圖示例:

圖片

步驟②: 隨機碼區域暫不介紹,我們來看下2位庫表如何映射到4庫4表組成的16張表中,

這里也有兩套方案:

【方案一】可以選擇2位庫表的首位作為庫編號,末位作為表編號,好處是映射較為簡單,但是容量不夠大,如果分的庫或表>9,擴展就會有點麻煩,如下圖,我們把末尾“12”邏輯映射到了“1庫的編號為2的表”;

圖片

【方案二】將4庫4表二維結構轉成一維,以0為初始值進行遞增,(0庫, 0表) → 00, (0庫, 1表) → 01... , (3庫, 3表) → 15,好處是容量變大了,最大支持99張表,不受庫或表單一條件的限制,缺點就是映射邏輯寫起來麻煩點,不過這不是問題,

圖片

取貨碼經過簡單編排,我們完成了取貨碼的到庫表的映射邏輯,解決了取貨碼存取的問題,其實仔細想想,關于全域唯一的問題其實也解決掉了,我們只要保證前6位隨機碼在單表里保證唯一即可,理論上支持單表在未核銷狀態下范圍為:000000 ~ 999999條記錄,容量是足夠的,關鍵我們把多庫多表的查詢就簡化成了只跑一個SQL,效率大大提升,

3.4 方案落地遇到的問題

既然本篇是介紹SaaS化的完整方案,在落地的時候或多或少會遇到一些問題,這邊介紹三個實際遇到的典型問題,并給出一些解決方案:

【問題一】使用Math.random()生成的6位隨機碼和表里的重復了,如何處理?

【解決】其實重復的情況有兩種:

  1. 可能是表里已經存在數字相同未核銷的取貨碼;

  2. 另外一種情況就是別的事務在正在操作,正好有個分布式事務鎖住了一樣的數字碼(概率很低,但是是有可能的),

這兩種情況的出現就需要我們進行優雅地重試了!大致思路如下偽代碼:

// step1 根據分庫分表因子獲取庫表編號,userCode-用戶編號、tenantId-租戶編號
String suffix = getCodeSuffix(userCode, tenantId);
 
// step2 批量獲取6位隨機碼
for (int i=1; i<=5; i++) {
   // 批量獲取亂數,每次重試,取2的指數級量進行過濾,相比暴力執行for回圈,這種方式能減少和DB的互動
   List<String> tempCodes = getRandomCodes(2 << i);
   // 過濾掉分布式鎖
   filterDistributeLock(tempCodes);
   // 過濾掉資料庫存在的隨機碼
   filterExistsCodes(tempCodes);
   return tempCodes;
}
 
// step3 處理隨機碼,隨機碼入庫
for (String code : codes) {
   // 加鎖,判斷加鎖是否成功,推薦使用Redis分布式鎖
   boolean hasLockd = isLocked(code);
   try {
         // 執行入庫
         insert(object);
   } finally {
      // 解鎖
   }
}
 
// step4 執行后置二維碼圖片等邏輯

【注意】

  1. 推薦使用指數級重試的方式(2 << i),逐次遞增random的數量,減少和DB的互動;

  2. 建議數字碼生成完畢后加鎖并執行INSERT,生成圖片地址等耗時嚴重的動作可以后置UPDATE上去,

【問題二】專案中使用了分庫分表的組件(比如:ShardingSphere-JDBC),怎么動態修改資料源?也就是同時支持分庫分表因子(比如:member_id、open_id等)以及根據取貨碼計算的庫表動態查詢,

【解決】我們以ShardingSphere-JDBC作為為案例來給出一些配置及偽代碼,具體可以參考:《強制路由::ShardingSphere》,其他開源的分庫分表組件或者自研產品不做贅述,可以自己手動寫個插件,別怕,即使再難,也要相信有光!

配置及偽代碼

// ShardingSphere-JDBC依賴的組態檔jdbc-sharding.yaml
...
shardingRule:
  tables:
    ...
    # 取貨碼表
    order_code:
      actualDataNodes: DS00$->{0..3}.order_pick_up_0$->{0..3}
      # 配置庫的計算邏輯
      databaseStrategy:
        hint:
          algorithmClassName: com.xxx.xxxxx.xxx.service.impl.DbHintShardingAlgorithm
      # 配偶之表的計算邏輯
      tableStrategy:
        hint:
          algorithmClassName: com.xxx.xxxxx.xxx.service.impl.DbHintShardingAlgorithm
    ...
 
// java代碼
try (HintManager hintManager = HintManager.getInstance()) {
    hintManager.addDatabaseShardingValue("order_code"/** 取貨碼表 */, DbHintShardingAlgorithm.calDbShardingValue(tenantId, code));
    hintManager.addTableShardingValue("order_code"/** 取貨碼表 */, DbHintShardingAlgorithm.calTabShardingValue(tenantId, code));
     
    Object xxx = xxxMapper.selectOne(queryDTO);
}

【注意】

  1. 這里介紹一種編程式的解決方案,好處是配置簡單、比較靈活,缺點就是代碼稍微多一點,其實ShardingSphere還支持注解的方式,可以自己研究下;

  2. 第一條說了比較靈活,體現在自己實作的 “DbHintShardingAlgorithm.calDbShardingValue(tenantId, code)” 方法上,這個方法可以自己定義,所以我們的入參可以是通用的分庫分表因子,也可以是自定義的取貨碼的“庫表位置”欄位,非常靈活,

【問題三】如何做到更強的擴展性,適用SaaS平臺以及不同的業務場景?

【解決】細心的小伙伴應該注意到了 "tenantId" 這個欄位,這是個租戶的編碼,在實際編碼會進行透傳,我們可以利用這個欄位針對不同的租戶(或叫業務方)來做不同的配置,比如:取貨碼的長度、取貨碼編排的方式、取貨碼映射庫表位置的策略等等做成可配,只要把主干邏輯進一步抽象,并使用策略模式進行個性化編碼,

四、總結

實作取貨碼邏輯的時候,發現網上券碼這塊的方案、技術文章比較少,當時萌生了寫篇文章拋磚引玉做個分享的想法,事實上,我相信大多數公司可能或多或少也是這么做的,哪怕采取了別的方案也能殊途同歸,本篇文章整體只是介紹了一個思路,而這個思路類似一個簡化版的訂單分庫分表,但這就是神奇所在,事實上我們還可以將一些常用的技術方案落地到不同的應用場景,大膽地做一些嘗試,多走一些未曾設想過的道路!

主題系列文章:

  • vivo全球商城全球化演進之路—多語言解決方案

  • vivo 全球商城:商品系統架構設計與實踐

  • vivo全球商城-營銷價格監控方案的探索

  • vivo 全球商城:優惠券系統架構設計與實踐

  • vivo 全球商城:訂單中心架構設計與實踐

  • vivo 全球商城:從 0 到 1 代銷業務的融合之路

  • vivo 全球商城:架構演進之路

分享 vivo 互聯網技術干貨與沙龍活動,推薦最新行業動態與熱門會議,

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/507238.html

標籤:架構設計

上一篇:i18next-反應-Firefox回傳“de”-Chrome回傳“de-DE”

下一篇:設計模式之(11)——享元模式

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more