12306 出票的一种算法设计
12306 一度成為技術圈的評論熱點,期間被鄙視過, 也被理解過。在我 2.5 年的技術生涯中, 至少 讀到過 8 篇關于 12306 的文章,當然,最早的文章是罵 12306 的。今年的春節也不出意外讀到了關于 12306 的文章,當然是對 12306 的理解與包容,甚至是贊美。前面兩個春節讀到 12306 的文章并沒有 引發我的思考,而這個春節,可能由于我將近 1 個月未上班的原因,我倒是認真思考起 12306 的業務 模型,進而在頭腦中有了這個初步設計。
本文的目的不是對 12306 作評價,而是基于本人對 12306 業務的理解,自己重新實現一套 12306 的系統設計。 本人對自己的這個設計方案比較滿意,認為該方案可以很好的解決 12306 的性能瓶頸。
免噴聲明
由于本人不是 12306 的工作人員,對鐵道部的運行方案不是很了解,難免有些分析不當的地方,請專業人士批判指出,請圈外人士勿噴。
本人對自己的文字表達能力相當有懷疑, 如有疑問, 歡迎交流。
業務的抽象與提取
數據結構,算法的設計
數據庫設計
設計容易犯的錯誤
我們容易忽略 1 步驟,交換 2,3 步驟, 也是設計思路是變成了:
數據庫設計
算法設計(數據結構被數據庫決定了)
以后詳細描述, 現在簡單提取
Nothing but Motto: 交流的時候統一語言,計算的時候統一量綱。
業務流程
用戶輸入起點站,終點站,選擇日期
根據輸入找到相應的班次
查詢每個班次的剩余票數
用戶點擊購買,填寫乘客人信息
根據起點站,終點站和日期分配票
準備出票:
用戶支付成功,出票
用戶超時,票回收(回滾操作)
提取
系統的核心功能是查票,購票。票作為我們的商品, 我們需要根據具體情況來定義票的概念,合理化票的量綱。
火車票的最小單位是:1(座位)* 1(個站區間), 我們約定這個最小單位是?ticket?。后續提到?票?都是指用戶購買的票(n 個?ticket?構造而成)。
對于有座位的票,要統一票 id(反正我坐火車從來沒有中途被要求換個位置)。對于這一點, 解決還是比較方便的, 對于同一座位, 放入一個票池子, 同時限制一張車票,只能從同一個池子里面去。
對于無座位的票, 統一放入一個票池子,或者分車廂放入幾個票池子。
站票的最小單位是:1(個站區間),由于站票的無固定位置特性,站票的最小單位和座位票有區別,因此站票的數據應該跟著列車的站點走。
抽象
根據上述定義,我們可以構造一張?tickets?表,座位號為行,站區間為列。假設列車經過 ABCDEFG 7 個站點,上面有 5 個座位號,那么?tickets?表為(先忽略站票):
實例
火車經過 ABCDEFG 7 個站點, 小王買了一張 B 到 D 的票。
小王買了幾張?ticket??
答:2 張,分別為?BC?和?CD?區間的?ticket?。
假設小王買的票是 2 號座位, 請標記相關?ticket?。
答: 如下圖:
下圖出票方式是否合理?
答: 不合理,其中紅色的票使得?tickets?池碎片化。 后續只能出 2 張全程票(A?G)。 如果紅色的票分配到 1 號座位,后續還可以出 3 張全程票,可使收益最大化,12306 也是要賺錢的了。
那這樣是不是會使得位置特別不均勻呢?確實是的,坐過火車的人會注意到這點的。不過這 并不是什么問題,大家總是會自動向座位空空的地方移。但實際上,這根本不是問題, 這圖上的位置不過是?邏輯位置?而已,把它跟實際?物理位置?做個映射就行。 學過計算機的人都知道內存有?邏輯地址?和?物理地址?的概念,他們有個映射關系。
綜上所述:出票分配位置時應該從上向下遍歷,找到第一個可用的。
這里不討論我對有關設計的演進了,直接指出最終設計。
tickets 數據結構
我們觀察上述圖片的時候,有沒有想過 白色區域代表 1, 著色區域代表 0, 就可以得到一個內存圖。每個座位對應的行用一個?int64?就可以代替了(我們假設列車最多經過 65 個站點,這個假設應該是合理的,假設不正確也沒關系, 不影響我們的算法)。
查詢
假設小王要查詢 B?D 的票,占據第 2 和第 3 個區間。 我們只需要構建位置值 (后續稱ticket_value) 6, 分別和每一個位置的值做?位與?運算,如果結果為 6, 說明位置可用, 遍歷一遍, 就可以得到剩余位置數。
購買
假設小王要購買 B?D 的票,從可分配的位置里面選取第一個減去?
程序系統設計數據庫系統設計
trains
id
code
type
7 | G77 | 高鐵 |
8 | D63 | 動車 |
sites
id
name
1 | 上海 |
2 | 濟南 |
3 | 北京 |
site_train_xRef
site_id
train_id
site_nth
n_sro
1 | 7 | 0 | 3 |
2 | 7 | 7 | 3 |
3 | 7 | 9 | 3 |
site_nth 為 0 代表始發站。
n_sro 為站票剩余數目,跟著列車的站點走。
tickets
id
pos_id
n_remain
1 | 1A | int_max |
1 | 3F | int_max |
建議為每一輛列車建立一個?tickets?表, 這樣也很容易分配到不同的機器上去查詢。也可以在 tickets 中加入一個?train_id?字段。
查票步驟
根據?site_train_xRef?找到符合要求的的列車班次。
分別各列車計算出票值?ticket_value?。
分別在找出的班次中檢索余票, 判斷該座位可用:?(n_remain & ticket_value) = ticket_value?。
鎖定票
用戶購買,生成訂單后,鎖定票。
解釋步驟不如直接上代碼:+
和公司 CTO 討論一下后發現上述 sql 不是個好方法:
上述語句的原子性不清楚,如果不是原子性, 那就是 bug 了。
如果上述 sql 是原子性的, 上述原子操作鎖的粒度比較大, 性能有問題。
我們得出方案, 改成兩步操作, 第二步如果失敗, 就重試:
?select id as oid from tickets ? ? ? where n_remain & ticket_value = ticket_valueorder by idlimit 1;update tickets set n_remain = n_remain - ticket_value ? ?where id = oid and n_remain & ticket_value = ticket_value;其他業務場景暫時交給讀者們了
結語
本人僅僅給出了出票算法一種設計而已, 離實用性還差很遠,鐵道部的具體賣票措施也不知道。比如說會有站點限制什么的。
總結
以上是生活随笔為你收集整理的12306 出票的一种算法设计的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ps 2023(24.7beta) ma
- 下一篇: Multi-Level Knowledg