某隊列積壓問題分析、解決
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.11 | 08.12 | 08.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分鐘
- 在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
關於鎖的操作
- redislock 的lock函數增加參數 cache_key, 判斷是否lock成功根據wait_time和cache_key一起, 當wait_time時間未到但cache_key存在時返回 [ ‘cache_key‘ => cache_key, ‘cache_value‘ => cache_key對應的值 ];
- 調用lock函數設置鎖的expire_time要比wait_time長, 當超時返回lock失敗後, 調用對應接口
- 當lock成功後, 要設置緩存以及過期時間,最後釋放鎖
- 當返回的是[ ‘cache_key‘ => cache_key, ‘cache_value‘ => cache_key對應的值 ]; 按正常流程處理即可
--------------
通過上面優化的結果:高峰期的隊列任務最高100左右。
這裏面最主要問題:開發人員沒有性能意識,自己給自己挖坑,人為增加系統壓力
1. 大量無謂的重復查詢
2. 大量無謂的寫操作以及沒有分散寫
3. 主次不分, 讓邊緣功能影響了核心功能
4. 單點問題,系統無法橫向擴展
某隊列積壓問題分析、解決