架构和产品的制衡——说说竞价拍卖那点事
2016年1月份,我們的產品上線版已經是1.1.0,開發版已經做到1.2.0。這些版本之間除了UI上做了一些改動(可以在我的產品分類文章里看到我為什么會做這些改動),大部分是些底層的優化,比如App端緩存架構的修改,后臺Server的API優化等等。第一個階段的產品規劃此時已經進入一個相對平緩的時期,這個平緩不是說產品做完了,而是說產品從無到有(0到1)之后,進入2(大需求改動)之前一個節奏相對平衡的時期。在這個時候,我終于開始騰出手來重點關注一個一直懸而未決的需求——線上競價。
注:本文里競價和拍賣代表一個概念。這里的競價指的是傳統意義上的拍賣的線上版本,不是指像某些SP商提供的線上廣告位自動競價系統。兩者有一定區別。線上競價這個概念,在有淘寶的今天,已經不是什么新鮮事了。但是親手做一個完全模擬線下拍賣流程的在線競價系統,這樣的需求不會很多。這對我們團隊對于我個人來說,即是一個機會也是一個挑戰。
好,怎么做呢?首先看看能不能借用別人的輪子,能不重復造當然不要自己造。可是在網上搜索一大圈,幾天下來,能用的資料寥寥無幾。可能是一個競價系統太簡單了,做的人都不屑于不好意思寫下來;又或者是做的人實在是太少了,都沒來得及寫;也可能是凡是做競價的都是商業需要,不能寫;還有可能我搜索技術太爛,找不到。不管怎樣,看起來我們又是得從零做起,而且我得寫點什么,好歹將來一無是處的時候給自己的子孫吹個NB說,“你爺爺曾經做過競價系統!”“啥破玩意?…”“……”
從頭做,還是得分幾步:
1. 首先要熟悉拍賣本身,至少得先懂;沒有太多的時間讓我一點點的完全熟悉拍賣這個市場,于是我拉上運營部門里做過真實拍賣交易的人,不斷聊天了解概念,然后又親自和他們參加了幾場真實的現場拍賣會,首先對真實線下情況混個八九成熟;
2. 分析競品,了解別人都做了什么;拉上UI設計師一起,測試機器上裝上4,5個有相同功能的App,我們兩個人一起分析每一個頁面里的內容和操作流程,互相討論;
3. 根據競品和我們的實際需求,整理出基本的數據模型;在這個過程中,需要非常多得取舍決策,因為一個完整的線下拍賣有太多的概念可以抽象到數據,哪些真的需要用在產品里,哪些不重要,這個完全考察的是經驗和理解決策力;
4. 根據線下規則,競品常態和我們的實際需求,設計我們自己的業務流程;這部分,有時間的話,我想在產品分類文章里分享下我是怎么設計我們的競價產品的;
5. 根據流程,設計系統架構。
6. 根據架構原型實現中遇到的問題,優化細節。
下文將著重討論5,6這兩部分。
?
一、競價的系統架構
我們來看下常規狀態下,一個線下拍賣的核心模式。拍賣,簡單來說,就是一個人吆喝賣,多個人搶著買,在賣的人規定的條件下(一般為時間限制),出價最高者得。好,那么整體模式可以概括成下面這張圖:
那我現在把這張圖,變化一下,只保留他們之間傳遞的信息,再看一下:
這像什么?對了,聊天室。整個拍賣過程在結構上本質上就是一個群聊。只不過,普通的聊天室的實現,服務器端只是單純的廣播消息,客戶端只是單純的收發消息;而競價系統,服務器端除了負責廣播消息之外,還需要對每一條消息進行判定,并在最終聊天室關閉時(也就是競價結束時)對所有消息進行一次綜合判定,而各個不同客戶端發出的消息時,互相之間有依賴關系(后一次出價必定在前一次基礎之上)。
這樣做起來思路就相對清晰了:兩大模塊:聊天系統+判定邏輯。
俗話說,條條大路通羅馬。同樣的需求,做起來有不同的方法,只是不同的方法最后的效果可能不同。同樣是做巧克力,Teuscher的手工巧克力就是比超市里賣的廉價巧克力口感好,賣的價格高。就看具體的要求標準是什么。同理,做一個聊天系統,可以是實時系統,也可以是一個非實時系統;可以用長連接,也可以用短連接;可以用輪詢驅動,也可以用事件驅動。關鍵看需求標準。
那么,回到我們的產品。最深層次的需求是:
- 客戶端能發起出價請求;
- 當價格更新時,所有參與出價的客戶端能夠看到最新價格;
- 系統功能要求遠高于性能要求;
- 開發周期緊。
擺在我們面前的,最明顯的有2種做法:
1) 非實時的polling:
所有客戶端的頁面定時向服務器發起更新請求,拉取最新價格和出價歷史列表。
2) 實時事件驅動:
所有價格更新以事件方式主動更新到每一個激活客戶端。
兩種方式優劣對比:
| ? | 非實時Polling | 實時事件驅動 |
| 優勢 | 簡單。 后臺系統只需要設計最基本的REST接口即可,前端只需要增加一個定時刷新機制即可; 和已有Web系統的擴展方法保持一致。 | 實時;用戶體驗好。 系統壓力線性穩定,系統吞吐率高; 長連接,可以基于事件做更多實時性效果。 |
| 劣勢 | 定時間隔直接影響用戶體驗和系統壓力: 增加定時間隔,用戶的基于過期的價格上而發出失效出價的概率增大; 縮短定時間隔,在競價人數較少時將造成大量服務器無謂的請求; 而在競價人數較多時又會造成服務器的壓力成倍數增大。 | 需要一個區別于已有Web系統的支持長連接的事件驅動的系統框架 |
?
?
?
?
?
?
?
到這肯定有人會有這樣的問題:聊天系統想都不用想——很自然的應當選擇第二種直接上Node.js+socket.io啊。可是,我做任何設計,總是喜歡考慮這些問題:
我是不是非得用這種技術,是否有其他更快的方法,“殺雞要不要用牛刀”,“我是不是只有一把刀”……
那么落到手頭的實際項目,我要考慮的是:
- 不能直接使用Node.js操作數據庫,因為Node的ORM和系統的Django ORM不匹配
- 必須盡可能復用已有系統架構,在最短時間內完成功能
- 必須顧及競價系統可能存在的復雜功能的要求
- 用戶體驗優先級最高
這些東西已考慮下來,最終就形成了現在的系統方案,方案1)和方案2)的結合:
我們利用原有系統架構(圖中右下部框內部分),使用Redis作為橋梁,直接讓Node服務器連接在Django服務器:
1)Node服務器負責維護WebSocket連接并且廣播消息,通知客戶端實時更新價格;
2)Django服務器負責響應出價POST請求,直接操作數據庫;然后通知Node服務器更新事件;
這樣,我們做出的權衡就是:
- 舍棄Node的高并發的能力,把系統壓力仍然嫁接在已有Server之上;
- 利用Node對WebSocket的優秀支持,提升用戶的實時體驗;
- 復雜度上,增加客戶端(Web和App)支持WebSocket的能力,但是利用Redis隊列潤滑系統整體框架,復用已有系統的處理邏輯和方式。
??
二、細節的打磨
上面提到的是競價基礎架構的設計思路,那么想要把一個聊天系統真正的變成一個競價系統,還得靠消息判定邏輯。
原本來說,來一個出價POST請求,通過App Server判斷價格是否生效,生效修改數據庫并通知,失效則拒絕。多個請求到來時,FIFO排隊,或者數據加鎖。
很簡單,和普通的Web請求讀寫思路沒有太大區別。但是競價系統在這里有一個不得不去考慮的特殊需求:代理出價。
在這里需要解釋一下代理出價:代理出價就是“替你出價,當你不在場的時候”。相當于一個自動機器人,每當別人的報價高于自己的報價則在價格到達自己限定的最高上限之前都會自動加價后報價。比如,某件商品起拍價1000,加價幅度為100,我設定的代理出價額度為2000,那么只要價格低于2000,你無論出價多少,我都會自動比你高100出價。你出1100,我出1200,你出1300,我出1400……這個過程中,我不必在線出價,系統自動幫我出價。
那這個代理出價的需求會帶來哪些新的問題:
1) 驚群效應:在有多個代理人的情況下,因為自動競爭,價格會在最短時間內上升到最高上限。比如有3個人都設置了代理出價,分別設定最高限額是M1,M2,M3。那么一旦競價開始價格將飚升至Max(Mi)。那么,系統要不要控制這個過程?
2) 如果M1=M2=M3,最終獲勝的應該是誰?
3) 如果把系統自動出價的行為看做是一個后臺的機器人,那么這個機器人的“地位”如何?是把每一個設置代理出價的用戶都看做是單獨的機器人獨立出價,還是由一位機器人統一負責協調所有的代理出價?
其中,前2個問題是需求策略問題,最后一個是技術實現問題。
關于驚群的問題,首先來看從產品的角度要不要“刻意”控制:系統刻意延緩這個“互相抬價”的過程,還是放任讓價格在很短的時間內的飆漲到代理人群中的最高出價。開發人員在第一版的設計里,是考慮了“用戶體驗”,加入了一個定時機制,刻意延緩了整個代理“互相出價”的進程,這樣還能讓手動出價的用戶能夠在代理競爭出價的“間隙”里也能有機會出價,即時這個價格并沒有競爭力。理由是:這樣看起來更像是大家坐在一起互相出價,不然看起來“很假”。由于做這個設計的時候我正好休假,所以開發人員并沒有和我溝通就直接把代碼寫完了。我回來以后,我的第一反應是很開心,因為至少說明團隊的開發人員能“主動思考用戶體驗”,而且證明他做的很快,想到方案并快速原型實現是必須的。但是,我習慣性的問了自己一個問題:“真的有必要讓它看起來不那么假么?用戶真的在意出價過程中如果出現多個代理出價的情形,自己還必須要有機會出價么?”。要回答這個問題,我們退一步,退回到線下拍賣的真實場景中:
現實拍賣中,拍賣公司或者組織者會給每一個有代理出價需求的客戶提前安排好專門的出價代表,并和現場其他到場客戶坐在一起舉牌。線上“驚群效應”其實就是現實場景中,這些代表們爭先恐后的舉到自己代理的客戶承受的最高價才能力保客戶最大可能獲得最終勝利。那么在這個過程中,現場其他的客戶很有可能沒有機會來的及舉牌,會在意么?我看不會,為什么,因為競價不是“享受過程”,而是“獲得結果”!你們在爭相舉牌,我沒機會,但是我無所謂,因為當代理舉牌完畢后,只可能有兩種結果:當前價格我還能再加,或者當前價格已經超過我的預期。那么這兩種情況都不會降低我的體驗,我想加我就等你們消停了我再舉牌,這個時候只要你們代理不修改代理價格,你們就都不是我的競爭對手了;而如果當前哄抬的價格已經超過我的心理價位,前面我有沒有舉過牌對我來說顯然一點意義都沒有,反正我肯定是拿不到這個拍品了。所以,我問開發“把這個定時器拿掉對你的代碼有多大改動?”“不大”“好,拿掉。不需要增加額外復雜度。”?
所以這里我的權衡是:即使讓開發翻工(當然是在可以接受的范圍內),也必須去掉過度的“結構設計”。
第二個問題,理解好線下拍賣對這個問題的處理方式就比較好辦。線下的方法是:“拍賣師說的算!”其實就是說,系統保留所有解釋權。那就好辦了,自己定義了一個規則就可以。
關于第三個問題,字面上不太好理解,我們來看一個圖:
這就是描述第一種情況中的“機器人是作為獨立出價人單獨出價”,指的就是系統并不對代理出價者做特殊處理,每一個代理在接收到價格更新事件后都可以直接對競價系統發起出價請求。這種設計優劣如下:
- 優點:系統功能角色劃分清晰,每個代理可以享有獨立出價權,更接近真實拍賣場景;
- 缺點:因為每個代理都可以接收到更新事件,每個代理都可以發出新的出價請求,而這些出價請求又會被其他代理收到,再響應出新的出價請求。所以必須額外增加一個機制,防止多個代理循環出價帶來監聽的“競價事件”爆發問題。
我們再來看看第二種可能的設計:
在這里,所有的代理出價者被納入內部的“Coordination System”中,而不再和外部的手動出價人并列。當任何一個出價事件發生時,首先由這個“Coordinator”在眾多代理出價者中協調出一個最終勝者,然后再向競價服務器發出一個最終競價請求。Coordinator選出唯一勝者的同時,也把所有其他的代理給淘汰掉了。也就是說,只需要1次出價,就能夠把所有可能的代理者全部“洗清”,只剩一個最高代理者和其他手動出價人競爭。在這個“協調”過程中,忽略所有其他手動出價。這種設計優劣如下:
- 優點:系統實現清晰簡單,不會產生“驚群”問題;
- 缺點:系統邏輯和真實場景有直觀上的差距;
這兩種都有明顯的優缺點,從模擬真實線下邏輯來說,第一種更優,理解起來也更容易;而從系統結構實現角度來說,我更喜歡第二種,做起來更直接更簡單。你會問我第二種的話,如果用戶體驗很差怎么辦?其實這種場景,用戶體驗差只有一種情況,就是很多人真實在線出價,同時有大量的人設置了代理出價。而這種情況對于我們目前的業務而言,顯然遙不可及。更何況,如果真的是這種大規模并發的場景,整個系統結構都可能要做調整了。雖然我個人傾向第二種但是我們線上版本用的是第一種,因為……在我休假期間我們NB的開發把第一種做完了!(攤手)。最后想來好像應該怪我應該提前把設計提前討論到這層再交給開發,但是仔細想想,以目前的情況看,這兩種并不會對產品本身有本質影響。所以既然已經做完了,那就先用。
至于使用第一種設計,我們額外設計了附加的措施來解決具體的“驚群”問題:
因為我們是使用遍歷列表的方式掃描所有的出價代理的,那么當某個代理成功發起出價請求后,在本次掃描循環周期內,后續的代理不再出價,而只是掃描其設置的封頂價格是否失效,是則忽略,否則從列表中移除。而當每個代理接收到價格更新事件時,首先確認此次出價是否為本人,是則忽略此次更新,否則繼續發起出價事件。
?
三、總結
本文主要記錄的是我在做產品的競價功能的前后遇到的問題和設計思路。我個人認為,這是一個很好的關于產品需求和架構設計之間互相權衡乃至制衡的例子。做互聯網產品的架構師,不能只是掉在架構的黑洞里鉆研架構本身,而是應該千方百計的用最合理的架構用最快的時間去滿足產品的功能。我一直認為,做產品和做架構本身是不應該分離的,我堅持認為研發出身的人如果去能做產品,應該比其他人更有優勢,至少在產品本身的實現上,更有說服力更有效。當然,關于市場和運營本身的能力也是至關重要,但那是另外的話題了。
?
2016年2月25日,完稿于南京。
轉載于:https://www.cnblogs.com/wizardguy/p/how-to-design-a-auction-system.html
總結
以上是生活随笔為你收集整理的架构和产品的制衡——说说竞价拍卖那点事的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Yii2.0学习资源
- 下一篇: 遍历DOM元素的children属性遇到