優惠券優惠的思路以及實踐

NO IMAGE

前言:最近做關於優惠券的開發,但是發現優惠券量大了之後,效能完全跟不上,庫中存200萬條優惠券,發一張券竟然需要5分鐘之久,然後我就著手優化,最終到發一張券只需要15毫秒左右,現在把整個思路以及程式碼貼出來,供大家一起討論和學習。

簡介

主要實現優惠券促銷活動,首先建立活動,然後建立券組,採用預處理的方式提前進行制券,在第一版本主要實現,功能的基本業務。然後在分支實現,大數量和高併發問題。

分支1.1

1:解決優惠券編碼重複問題,原先採用的是獲取資料庫所有的券,然後去比對是否重複,如果庫資料量達百萬的時候就會出現非常緩慢,而且會 出現經常制券失敗等,所以此版本捨棄原先採用隨機數的模式,通過推特的雪花演算法來避免唯一,但是依然保留優惠券字首和字尾。

2:由原來的非同步採用執行緒修改為執行緒池,以為高併發時候存在大量的執行緒佔記憶體空間。

3:由原來制券採用for迴圈模式修改為批量制券,而且採用分配插入優惠券,一批次目前定為5000.

4:加入訊息佇列(採用rabbitMQ)對於某一批次新增失敗,把失敗的放入對列中,通過佇列進行補救,已到達高可用。避免大批量優惠券來回重新匯入 訊息佇列對於異常資訊拒絕解決並重返訊息佇列中,配置2個消費者以避免其中一個服務異常,訊息處理出現死迴圈

分支1.2

1:加入操作日誌

目的:跟蹤熱點資料,查詢日誌快速跟蹤應用程式中的慢查詢或慢操作,為後面的優化奠定基礎

2:加入異常日誌

目的:快速的獲取執行緒的異常問題,通過日誌中的資料能快速修改

3:採用技術 通過aop和rabbitmq中介軟體來做,這樣減少由於日誌問題給程式帶來的效率問題

未做優化效率統計

採用資料庫mysql

資料:新增25個有效活動,每個活動下分別有2個券組,每個券組下制券是5萬張。優惠券表中250萬條記錄

業務:一個會員消費同時滿足這25個活動要送50張優惠券。

統計:整個發券過程經過10次統計得出大約消耗是306s,其中每次獲取優惠券耗時6s。如果多次迴圈必然帶來效能的瓶頸

更新優惠券狀態大約耗時是0.5s,從上我們可以看出我們的效能問題主要出在獲取優惠券上。所以才1.3版本主要通過程式來解決這個問題

分支1.3

目的:通過程式程式碼和優化資料庫來提高效能

具體方案:

1:以前獲取券組下所有的優惠券現在修改為每次只獲取100條(經測試統計得出傳送50張券消耗時間是106s,每次獲取優惠券大約耗時是2s多,整體效能提升近3倍)

2:優化sql,加入組合索引(統計得出傳送50張優惠券消耗總時間是2.5s,每次獲取優惠券大約耗時是0.015s,整體的效能提升了近42倍)

3:加入本地快取(如果一次性獲取的優惠券先放入map中,那麼下次如果還有就不需要從庫中獲取優惠券。統計發現:10件商品,每件商品發50張優惠券

不加本地快取效率耗時是7.5s,加入本地快取後耗時約5.5s,整體效能提升了2s)

效果分析:

4:對於發券採用批量更新來替代for迴圈(由上面的約5.5s效能提升為大約4.8s)

分支1.4

目的:通過非同步和訊息佇列來進行發券

具體方案:

1:通過非同步進行發券,這樣可以提高cpu的利用率,同時通過訊息佇列來保證穩定性,避免出現異常導致返回前端發券成功,但是非同步制券時候出現異常在發500張優惠券的時候效率大約提升了0.5s

2:對程式碼進行一次重構

原則:把大方法修改小方法,每個小方法處理一個業務,比如獲取活動,那麼這個方法的職責就是獲取活動,同時每個小方法儘量有返回值,這樣可以增加程式碼的可讀性

分支1.5

1:採用redis做快取,取當天有效的活動,活動下券組,券組下500張券存入快取中。

2:加入定時任務,在每天12點時候更新快取(這個時間可以通過熱點資料來監控)

3:統計結果發現:

加入快取後傳送500張優惠券耗時只有2.7s,比之前的4.8s快了2.1s,大大的提升了效能

總結:程式碼我就不貼,大家可以自己去看。感興趣的朋友可以在這個基礎繼續研發學習。在版本1.6可能加入分庫分表,目前想採用的是噹噹的sharding-jdbc

原始碼地址