1. 程式人生 > >某隊列積壓問題分析、解決

某隊列積壓問題分析、解決

tex 圖片 rim wait uniq timeout mat lock pla

07.29 最高到了115w積壓, 07.30也有持續幾分鐘上萬的走勢

2017.07.29隊列走勢

技術分享圖片

2017.07.30 隊列走勢

技術分享圖片

分析

對update_betting_offer的處理包括2部分,Handler_Filter和Handler_Trigger, 性能問題發生在Handler_Filter階段

Handler_Filter各個子階段在07.29日大於1s的個數如下:

階段名大於1s個數
odds_source_save 12233
odds_match_map 423910
odds_get_event 4798
odds_source_filter 12513
odds_cache_save 179

08.11 開始新的統計各階段大於1s次數

階段名08.1108.1208.13
odds_source_save 92218 85183 26141
odds_match_map 45237 24560 8566
odds_get_event 79546 83638 25125
odds_source_filter 125564 124135 35280
odds_cache_save 968 1960 687
odds_risk_control 113275 111902 33028

主要問題點:

odds_match_map 將賠率源的比賽轉化為內部的比賽信息

gearman同步調用,發送賠率信息到某隊列
1.1 結構問題: 同一個時刻會收到大量同一場比賽的賠率信息, 這些相同比賽的賠率都會進行findMatch處理(其實沒必要),只要該比賽的處理慢,那都會慢,導致隊列堆積

加鎖 鎖名稱 : lock:update_betting_offer:match_map:賠率源:源sport_id:源match_id 緩存名稱: update_betting_offer:match_map:賠率源:源sport_id:源match_id , 存儲匹配後的match_id, 值為0表示無法匹配。 過期時間 3分鐘

1.2 程序問題

  • 在findMatch最後,不管匹配數據是否發生變化,都會向map_soccer_match更新數據,再加上並發問題,導致大量無謂的更新操作。下圖中的count表示數據更新的次數。 (對數據進行了判斷,數據變化的時候才去更新)

技術分享圖片

  • 當模糊匹配時,需要匹配的比賽多是,性能下降。 經測試,SQL框架比直接連接mysql查詢要慢。 277場比賽慢2s,框架2.6s,直連0.6s
  • addMapMatchLog函數中sql沒有索引,影響大。表是map_sort_match_log,可以對ms_id加索引,同時因為並發問題,導致很多重復數據 (08.07添加的索引). 並改成異步寫
  • findMatchByTeamId函數中sql語句優化, 裏面有聯表查詢

odds_source_save 保存賠率源數據

  • 將saveOdds函數中想mongo寫數據的操作改成異步
  • saveOdds對mysql 跟上面的寫mongo一起做成異步
  • $sql = "SELECT last_updated FROM `{$tbl}` WHERE `match_id` = ‘{$data[‘match_id‘]}‘ AND `provider_id` = ‘{$data[‘provider_id‘]}‘ AND `betting_type_id` = ‘{$data[‘betting_type_id‘]}‘ AND `boundary` = ‘{$data[‘boundary‘]}‘ LIMIT 1"; 這條語句時不時超過1s。
    1: where後面的字段都是整型, 變量都賦成了字符串。 要讓數據庫減少這種類型轉換
    2: where後面的字段被設置成了 PRIMARY KEY (`match_id`,`provider_id`,`betting_type_id`,`boundary`), 而offer表 插入更新是非常頻繁的,這4個字段的主鍵會造成過大的主鍵索引和IO操作。 需要新增一個自增字段作為主鍵, 為match_id`,`provider_id`,`betting_type_id`,`boundary`創建唯一聯合索引

    主鍵的選擇:
    主鍵遞增,數據行寫入可以提高插入性能,可以避免page分裂,減少表碎片提升空間和內存的使用
    主鍵要選擇較短的數據類型, Innodb引擎普通索引都會保存主鍵的值,較短的數據類型可以有效的減少索引的磁盤空間,提高索引的緩存效率
    http://blog.csdn.net/jhgdike/article/details/60579883

    3. offer表定期清理數據,根據last_updated保留近半個月數據。大概400w條
    4. 分表數據分布不均衡,目前寫操作集中到了同一張表,修改分表算法
    5. 改成讀從庫

odds_check_league 檢查聯賽是否有效

加鎖 鎖名稱 : lock:update_betting_offer:check_league:match_id 緩存名稱: update_betting_offer:check_league:match_id , 存儲匹配後的聯賽id以及是否有效,格式(1有效,0無效): league_id:1, 過期時間 60分鐘

odds_get_providerid 獲取賠率公司id

  • 大量並發查詢相同公司id, 如果查不到還會大量插入更新。 加分布式鎖(賠率源名稱+provider_id)只查一次。
    加鎖 鎖名稱 : lock:update_betting_offer:get_provider_id:賠率源:provider_id 緩存名稱: update_betting_offer:get_providerid:賠率源:provider_id , 存儲匹配後的provider_id, 值為0表示無法匹配, 過期時間 60分鐘
  • 可以將這一步檢測提前到程序開始處,查不到的就直接返回了
  • 寫 表map_soccer_provider的操作改成異步

odds_get_event 獲取eventid

  • 加鎖 鎖名稱 : lock:update_betting_offer:get_event_id:源sport_id:match_id 緩存名稱:update_betting_offer:get_event_id:源sport_id:match_id, 存儲匹配的event_id, 值為0表示無法匹配, 過期時間 3分鐘

odds_source_filter

  • 加鎖 鎖名稱 : lock:update_betting_offer:filter_unique:賠率源:betting_type_id: provider_id:event_id 緩存名稱:update_betting_offer:filter_unique:賠率源:betting_type_id: provider_id:event_id 存儲過濾結果1:有效, 0:無效, 過期時間 3分鐘


odds_risk_control 賠率風控

  • Worker_Odds_RuleCheck隊列問題
  • 調用http接口獲取99家平均賠率時間長

    2017-08-18 23:55:07  2600723 1.0097689628601 msg=
    2017-08-18 23:55:09  2602205 3.0126769542694 msg=
    2017-08-18 23:55:09  2601298 3.0131931304932 msg=
    2017-08-18 23:55:11  2601881 1.0082378387451 msg=
    2017-08-18 23:55:11  2601235 1.0080330371857 msg=
    2017-08-18 23:55:11  2601259 1.0102519989014 msg=
    2017-08-18 23:55:12  2602482 1.0055501461029 msg=
    2017-08-18 23:55:12  2601226 1.0132040977478 msg=
    2017-08-18 23:55:14  2600658 1.0105800628662 msg=
    2017-08-18 23:55:14  2599056 1.0061559677124 msg=
    2017-08-18 23:55:14  2601298 1.0084340572357 msg=
    2017-08-18 23:55:15  2601300 1.0130741596222 msg=
    2017-08-18 23:55:23  2602194 1.0079371929169 msg=
    2017-08-18 23:55:26  2601221 1.0092370510101 msg=
    2017-08-18 23:55:28  2600658 1.0096790790558 msg=
    2017-08-18 23:55:28  2600575 1.0079090595245 msg=
    2017-08-18 23:55:31  2600710 1.012079000473 msg=
    2017-08-18 23:55:34  2599398 1.0108640193939 msg=
    2017-08-18 23:55:39  2601414 1.0104100704193 msg=
    2017-08-18 23:55:39  2600723 1.0120589733124 msg=
    2017-08-18 23:55:39  2601519 1.0102570056915 msg=
    2017-08-18 23:55:46  2602127 3.0110030174255 msg=
    2017-08-18 23:55:52  2601759 1.0077710151672 msg=
    2017-08-18 23:55:52  2601060 1.0113618373871 msg=
    2017-08-18 23:55:55  2600575 1.0087251663208 msg=
    2017-08-18 23:55:55  2601414 1.016450881958 msg=
    2017-08-18 23:55:57  2602255 1.0063278675079 msg=
    2017-08-18 23:56:00  2599400 1.0102560520172 msg=
    2017-08-18 23:56:00  2600574 1.010486125946 msg=
    2017-08-18 23:56:00  2601304 1.0060698986053 msg=

  • 99家平均由http調用改成直接讀取數據庫。 (只有一臺http機器調用超時嚴重)17.11.30
  • 調用gearman報錯

    [18-Aug-2017 13:19:50 Asia/Shanghai] PHP Warning:  GearmanClient::doNormal(): _client_do(GEARMAN_NO_ACTIVE_FDS) occured during gearman_client_run_tasks()
    
    [18-Aug-2017 13:19:51 Asia/Shanghai] PHP Warning:  GearmanClient::doNormal(): _client_do(GEARMAN_TIMEOUT) occured during gearman_client_run_tasks() -> libg

關於鎖的操作

  1. redislock 的lock函數增加參數 cache_key, 判斷是否lock成功根據wait_time和cache_key一起, 當wait_time時間未到但cache_key存在時返回 [ ‘cache_key‘ => cache_key, ‘cache_value‘ => cache_key對應的值 ];
  2. 調用lock函數設置鎖的expire_time要比wait_time長, 當超時返回lock失敗後, 調用對應接口
  3. 當lock成功後, 要設置緩存以及過期時間,最後釋放鎖
  4. 當返回的是[ ‘cache_key‘ => cache_key, ‘cache_value‘ => cache_key對應的值 ]; 按正常流程處理即可

--------------

通過上面優化的結果:高峰期的隊列任務最高100左右。

這裏面最主要問題:開發人員沒有性能意識,自己給自己挖坑,人為增加系統壓力

1. 大量無謂的重復查詢

2. 大量無謂的寫操作以及沒有分散寫

3. 主次不分, 讓邊緣功能影響了核心功能

4. 單點問題,系統無法橫向擴展

某隊列積壓問題分析、解決