記一次隊列積壓問題的分析、解決

現象:mysql

同事負責的項目轉到我部門,整理服務過程當中發現了隊列的積壓問題。redis

爲了搞清楚積壓的嚴重程度, 對隊列任務數每分鐘進行一次採樣,生成一個走勢圖, 隊列積壓狀況一目瞭然,很是嚴重。 sql

 分析:mongodb

聽了同事對系統的介紹,猜想是mongo性能影響了處理效率,因而針對mongo進行分析shell

1. 使用mongotop  /usr/local/mongodb/bin/mongotop --host 127.0.0.1:10000數據庫

odds_easy.basic_odds表的操做一直排第一,寫操做佔大部分時間json

 

2. 看mongo shard日誌數組

大量超過1s的操做,集中在odds_easy.basic_odds寫操做,  看日誌lock數量不少緩存

 

查詢某一個文檔的更新,在同一秒中竟然有15個更新操做,這樣的操做產生什麼樣的結果: 大量的寫鎖,而且影響讀;並且仍是最影響性能的數組的$push, $set操做架構

 

看看文檔的結構,數組的數量之大,並且裏面仍是對象嵌套; 對這樣一個文檔不停的更新, 性能可想而知

 

 看看 db.serverStats()的lock狀況

 

 看看odds_easy的db.basic_odds.stats()狀況,大量的更新衝突

 

3.  看看sharding狀況

  使用腳本,查看sharding狀況,重定向到文件中查看。
  sql='db.printShardingStatus({verbose:true})'
  echo $sql|/usr/local/mongodb/bin/mongo --host 192.168.1.48:30000 admin --shell 

   basic_odds的sharding信息:

    shard key: { "event_id" : 1, "betting_type_id" : 1 }    event_id爲mysql自增字段,betting_type_id爲玩法id(意味着幾個固定的值,區別度不大)

    shard 分佈狀況, 從圖裏面能夠看到mongo主要根據event_id這個自增字段的範圍進行數據拆分,  意味着相鄰比賽的數據大機率會被分配到同一個shard分區上(這就是爲何01機器上的日誌大小遠大於其餘機器的緣由吧,目前的數據都集中在shard1上)    

 

 下圖爲數據庫讀寫狀況, 更新操做是查詢操做的4倍。 對一個寫多讀少的數據庫, 本該將寫操做分佈到不一樣的分區上,結果因爲sharding key的錯誤選擇形成了寫熱點,將寫集中到了同一個分區,進一步加重了寫的阻塞

 

結論

  1. 文檔結構不合理,數組過大、更新過於頻繁,特別是對同一文檔。 對數組頻繁的更新操做是mongo最不推薦的,不只影響本機的性能,還影響oplog的數據同步
  2. sharding key不合理形成了寫熱點,  在第一點不合理的基礎上,更加重了性能的急劇降低,  還會形成頻繁的mongo數據遷移
    (因爲odds_easy.basic_odds的更新量大,目前問題在這個表上,可是其餘表也有一樣的問題)

     【能夠看到前期合理的架構設計是多麼的重要】

解決思路

  1. 減小同一時刻對同一文檔的更新操做,將必定時間內的屢次更新改成一次更新。 
  2. 將更新最頻繁的process字段從文檔中移出,寫到新的表中。 在新表中,同一event_id,betting_type_id, 賠率公司的變化在同一條記錄中
  3. 文檔結構中加入時間字段,方便數據遷移,按期將歷史數據進行遷移,進行冷熱數據分離
  4. 修改sharding key爲hash或者其餘字段,將寫操做分佈到不一樣的分區上

解決方案

分兩個階段:

第一階段 結構優化

  1.  odds_easy庫中basic_odds, main_odds再也不存儲最近10條的變化,去掉process字段。  
  2.  數據直接 mongo push到odds_change中對應的記錄rows字段中 
  3.  單獨提供接口,數據變化從odds_change中讀取,  使用 mongo的$slice讀出最近n條信息,而後程序排序截取便可
  4. odds_easy庫和odds_change庫中的表都使用 event_id 做 hash sharding key
  5. odds_easy, odds_change, odds_bet007, odds_betbrain_bb, odds_betbrain_v5, odds_txodds這些庫中的記錄都加入match_time字段。   新增的記錄直接加入;歷史的記錄補全
  6. 加入分佈式鎖,解決併發問題,提升系統橫向擴張能力

第二階段 冷熱分離

  目的

  1. 解決積壓問題
  2. 提升訪問速度
  3. 防止用戶對大量歷史的訪問從而影響熱數據的訪問。(能夠在配置中加入開關, 出現問題時關閉歷史數據的訪問)

 系統中加入redis作熱數據緩存, zookeeper/etcd做爲配置服務中心以及熱數據導入的流程控制中心

架構圖

2. 相關流程

 update_betting_offer隊列的GermanWorker啓動新增流程

  1. 從服務配置中心讀取熱點比賽列表
  2. 須要在服務配置中心註冊節點,節點內容:「本機ip進程號_update_setting」   (去掉ip中的點號)
  3. 在german註冊一個任務名稱,名字爲第一步中的節點內容

 

"本機IP進程號_update_setting"任務處理流程:

  1. 在服務配置中心註冊新節點
  2. watch 服務配置中心的 「導入數據OK節點」
  3. 收到watch變化後,更新程序的熱點event_id列表 
  4. 刪除在服務配置中心註冊的節點

 

定時任務流程:

  1. 找出最近2天內未結束比賽的event_id列表
  2. watch服務配置中心 「導入數據OK節點」,內容爲0
  3. 從服務配置中心獲取全部update_betting_offer的GermanWorker節點,並根據節點內容的發送任務(任務名稱=節點內容,任務內容=event_id列表)
  4. 等待watch的節點數==german worker數後, 從服務配置中心讀取如今的event_id列表,與新的列表進行對比。將新增的event_id數據從mongo導入redis,過時時間3天
  5. 導入完畢後, 改變 「導入數據OK節點」,內容爲1
3. redis結構

初賠結構, key值:  「event_id:betting_id:start」 ,  value值爲hash類型,hash_key:provider_id;hash_value:跟mongo中的結構一致,json格式;如{"i":{"t0":{"h":4.27,"d":3.24,"a":1.88},"t1":0.9305,"t2":{"h":0.2179,"d":0.2872,"a":0.4949},"t3":{"h":0.93,"d":0.93,"a":0.93}},"s":1,"t":"2017-04-03 13:39:28","b":0,'p':744   }

終賠結構,key值:"event_id:betting_id:end"  value值同初賠

平均結構,key值:"event_id:betting_id:avg"  value值同初賠

變化過程,key值:"event_id:betting_id:provider_id:boundary", value值爲sorted set, member爲賠率信息,跟mongo中的結構一致,json格式;如{"i":{"t0":{"h":4.27,"d":3.24,"a":1.88},"t1":0.9305,"t2":{"h":0.2179,"d":0.2872,"a":0.4949},"t3":{"h":0.93,"d":0.93,"a":0.93}},"s":1,"t":"2017-04-03 13:39:28","b":0,'p':744   }。 score爲時間,如20170403133928

 

 若是按照上面的結構進行存儲, 進行了大概的預估。

對2789場比賽進行了歐賠統計,平均一場比賽2006個初賠,2006個終賠;583個boudary值,每一個boundary結構中存23個賠率變化;這樣計算一場比賽須要大概 5.5m, 盤口數據大概爲歐賠的一半。總8M

若是放入全部未開賽的比賽,大概1個半月的比賽,1w場比賽,所需內存80G,這個量太大了。

因此只放入熱數據,2天內未開賽的比賽,保存3天,3天比賽450場左右。 須要 3.6G

 

----------------後續:第一階段優化完後高峯期最高120左右

相關文章
相關標籤/搜索