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分钟
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 赔率风控
-
调用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. 单点问题,系统无法横向扩展