akka設計模式系列-akka在秒殺場景的應用


  本博客討論一下akka在秒殺場景下的應用,提出自己的見解,只做拋磚引玉,大神勿噴。秒殺活動涉及到前中后台各個階段,為了說明問題,我們簡化場景,只研究akka在后台如何處理秒殺業務。

  秒殺活動

  所謂的秒殺活動,簡單點來說,就是把某個稀缺商品或促銷商品,掛到頁面,供大量客戶搶購。這里有兩個關鍵點,商品數量不多客戶量非常大或搶購流量非常大。客戶量或搶購流量往往意味着並發量非常大,容易給服務器造成很大的瞬時壓力。

  同樣,為了簡化問題,我們把秒殺活動中的概念也進行簡化,分為庫存和搶購請求。庫存:待搶購商品的數量。搶購請求:客戶為了搶購商品的點擊動作,也就是一次請求。搶購請求分為成功和不成功兩種,搶購成功會減少庫存,否則不會。

  其實在活動中經常會有海量的、重復的、分布的搶購請求,因為同一個客戶會點擊多次或開多個頁面進行搶購。如果處理這些請求的服務器只有一台,其他的不說,這個瞬時流量都不一定能夠承受,因為寬帶搞不定了啊。所以應該是多少個節點來處理。另外搶購請求是海量的、分布的,這就意味着並發量很大,如果處理不當還可能造成超賣的情況。傳統技術解決這個問題,無非就是加鎖、用事務、用隊列。

  我們把搶購請求只划分為成功和失敗兩種,請大家一定要注意,這里其實我們忽略了付款的問題,因為這會增加問題的復雜度,不利於分析問題。其實吧,付款是可以規避掉的,提前讓用戶付款,然后再搶購不就好了?搶購成功意味着付款成功

  在akka中,重要的是如何通過actor實現我們的業務邏輯。其實細細分析可以發現,庫存就是一個Actor集,每一個商品的實例就是一個actor,也就是一個SKU對應一個actor。搶購請求就是actor收到的消息。

  了解akka機制的同學,大概已經知道怎么解決秒殺問題了,不就是把每個商品抽象成一個actor,每個請求抽象成這個actor的消息么?這跟隊列沒啥區別啊,因為actor就是用隊列來接收消息的。這跟傳統的使用隊列解決問題非常類似,但還是有些區別的。傳統的隊列只是用來解決並發問題的,畢竟解決並發的根本方法就是局部串行化。抽象成actor除了使用隊列把並發編程串行之外,還分散了計算能力。傳統的方案中,可能就是把請求塞到隊列,然后使用多個消費者處理這些請求,很難做到分布式,如果做成了分布式,其實跟akka就差不多了。

  那么究竟該如何用akka解決這個問題呢?

  首先每個商品(也就是SKU)抽象成一個actor,庫存有多少,就有多少個actor。讀者可能會問,會不會把內存撐爆,其實不會。搶購的商品,其庫存一般都很低,一般都是千級別的,撐死了萬級別的。如果你說你的業務場景是十萬級別的,甚至百萬級別的,那請你麻煩在下面留言討論。^_^。每個actor自身大概會占用400字節,1G內存大概可以有250萬個actor;如果加上你商品的其他屬性或其他信息,姑且算每個actor占用4K的空間,那么1G內存可以生成26萬的actor。26萬件商品對於常規的秒殺活動,應該足夠了。

  其次搶購請求抽象成actor的一個消息。那是不是就是簡單的把消息發送給actor呢?其實消息路由的過程才是難點。比如用戶X發送的搶購消息該發送給哪個商品的actor呢?隨機發送?用戶發送多次請求,不就會搶購到多個商品?用戶ID計算hash之后發送給固定的actor?那如果這個商品被其他人搶到了呢?其實我們還需要一個處理搶購消息的分發器。

  還需要一個搶購請求的router。當然也可以不需要。在每個搶購消息發送之前都需要經過該actor,由該actor對消息進行分發。該router的作用,就是把同一個客戶的搶購請求路由到固定的商品actor。那如何實現呢?其實也很簡單,那就是用一致性HASH。每個商品和用戶都會有一個HASH值,總是把用戶ID的HASH值跟商品HASH進行比較,把搶購消息路由給與用戶ID的HASH值最接近的商品actor上就可以了。但這個HASH算法需要做到盡可能的分散用戶和商品,避免出現熱點。其實這個actor的設計比較關鍵,也比較難,我這里只是提供了方案,並沒有說明具體的實現細節,請大家見諒。

  另外商品actor的郵箱類型需要修改成有界隊列。因為每個商品就是一個actor,而每個商品只能買個一個客戶,所以嚴格上來說,這個actor只能接收一個搶購請求。所以商品actor的郵箱類型必須是有界隊列,避免消息過多撐爆內存,當然這個隊列的長度必須是大於1的。超過這個隊列長度的消息怎么辦呢?在這個商品被搶購成功之前,其實可以直接丟棄,因為已經有其他客戶占用這個商品了。當然,占用不意味着一定能夠搶成功,也可能失敗,比如觸發了反薅羊毛策略,或數據庫更新失敗。因為隊列中還有其他的搶購請求,前面的客戶搶購失敗,還是可以把該商品分配給其他客戶的。

  最后還需要計算當前商品的庫存,這也需要一個actor。每個商品actor啟動時,給stateActor發消息;商品被搶購成功后,給stateActor發消息,同時stop掉自身。這樣stateActor就可以異步的獲取當前的庫存了。

  當然秒殺活動,還有其他很多的技術細節。比如商品actor如何更新數據庫呢?畢竟不能每個actor都分配一個數據庫鏈接,壓力太大;還比如某個商品被搶購成功后,后面的搶購請求消息需不需要分發給其他商品呢;再比如,如果同時有多個秒殺活動又該怎么辦呢;還比如使用分布式,由此帶來的一致性、通信異常問題如何解決呢。當然了,這些都是可以用akka技術非常優美的解決的,我這里就不啰嗦了,歡迎大家留言討論。

 


   補充。

  在上面的介紹中,我們還忽略了一個很重要的操作,那就是更新數據庫,畢竟最終還是需要把訂單等信息寫到數據庫的,而最終落庫才是真正的搶購成功。那么該如何落庫呢?

  我們知道每個商品都會處理搶購請求,也就是說,如果有n個商品,那么至少會有n個寫數據庫的請求,該由誰來完成呢?

  其實有兩種方案,一是由stateAggregation 來完成,在收到對應商品actor的stop消息后,更新數據庫;二是單獨再用一個actor來完成該功能。其實兩者都差不多,讀者可自行決定。不過我更建議單獨用一個actor來完成該功能,因為更新數據庫的請求相對來說還是很大的。比如有1萬個商品,那么同時就可能有1萬個寫數據庫的請求,相對數據庫來說,量還是很大的。此時我們可以用多個actor來完成數據庫的寫入,此時用一個router,分散更新請求。

  不過更新數據庫也有兩種方案,一種是單條寫入,一種是批量寫入。其實秒殺這個場景,更適合批量寫入。因為搶購的行為是短時間內的,也就意味着更新數據庫的請求也是短時間內的。此時我們可以每1000條更新一次數據庫,而不必過分考慮批量時間間隔的問題。因為我們可以把更新數據庫的請求看成,全部商品一次性發送過來 。批量寫入可以大大節省寫數據庫的時間,這樣也能盡可能的把插入數據庫的結果返回給商品actor。只不過最后不滿1000條的請求會慢一點,要等下一個超時時間后,批量寫入。不過這個時間可以設置的很短,比如1秒。這也就意味着,所有的入庫請求,最慢時間是1秒。

 


  2018年9月20日

  其實無論數據庫怎么優化,最終都會成為秒殺活動的瓶頸的,因為是海量(對數據庫而言)的並發寫啊。有時候解決問題的最好方法,就是避免問題的出現。加一層緩存應該可以的嘍?

 

   上面是優化后的架構。外網用戶搶購的請求通過多台Nginx進行轉發,nginx將同一個用戶的請求轉發給后面的同一個region。region根據用戶ID將其請求轉發給相同的shard。shard里面有一組商品,shard會將相同用戶的請求分配給相同的商品。但這會造成即使有商品,某特用戶也搶不到,其實吧,這算是變相的公平,也就是說,商品會被隨機的分發給不同的用戶。不過shard的分配算法也可以修改,分給該shard的用戶共同爭搶該組內的商品。

  其實商品actor的信息最終是要落庫的,考慮到高並發對數據庫的壓力是不可承受的,在數據庫之前需要一層緩沖。可以使用kafka等其他可以迅速將消息落地的技術或框架,緩沖的數據落地到數據庫,可以是一個緩慢的過程,因為搶購成功到最終發貨還是需要一段時間的。但這里還有一個問題,就是用戶去哪里查詢自己已經搶到了呢?數據庫,還是緩存?其實吧,要我說,只要告訴用戶搶購已經結束(庫存為0),不一定要立即告訴他有沒有搶到。等緩存的消息全都落庫,訂單最終生成,再告訴他有沒有搶到就好了


 

   2018年9月20日19:39:47

  

  商品actor如何快速的落庫就成了我們的“心頭大患”,本來我想只是簡單的把訂單信息寫入到kafka,kafka雖然也可以保證一定寫成功,但又引入了第三方框架。為了偷懶,又設計了上面的架構,即用CacheActor替換kafka等消息隊列。

  CacheActor對訂單消息的持久化就是關鍵了,那該怎么做呢?為了偷懶,我選擇了內存映射文件,也就是把訂單信息寫入本地文件。然后等搶購結束后在讀取該文件,再進行落庫。另外為了減少文件的大小,我准備使用一個位圖保存這些信息。即,用戶的序號為2345,則在文件的第2345個byte上面寫入255,否則就是0。減少文件大小還可以增加寫文件的速度,避免錯誤的發生。

  經測試,在1萬個商品,10萬個用戶,每個用戶重復點擊100次搶購按鈕的情況下,搶購完成的時間為1341毫秒。

  機器環境如下:

  

 

 

 

 

秒殺系統架構分析與實戰

搶購(秒殺)業務的技術要點

電商技術解密——秒殺系統

秒殺活動一般怎么做


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM