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


現象:

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

為了搞清楚積壓的嚴重程度, 對隊列任務數每分鍾進行一次采樣,生成一個走勢圖, 隊列積壓情況一目了然,非常嚴重。 

 分析:

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

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

odds_easy.basic_odds表的操作一直排第一,寫操作占大部分時間

 

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左右


免責聲明!

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



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