某队列积压问题分析、解决

07.29 最高到了115w积压, 07.30也有持续几分钟上万的走势

分析

对update_betting_offer的处理包括2部分,Handler_Filter和Handler_Trigger, 性能问题发生在Handler_Filter阶段

Handler_Filter各个子阶段在07.29日大于1s的个数如下:

阶段名大于1s个数
odds_source_save12233
odds_match_map423910
odds_get_event4798
odds_source_filter12513
odds_cache_save179

 

08.11 开始新的统计各阶段大于1s次数

阶段名08.1108.1208.13
odds_source_save922188518326141
odds_match_map45237245608566
odds_get_event795468363825125
odds_source_filter12556412413535280
odds_cache_save9681960687
odds_risk_control11327511190233028

主要问题点:

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.youkuaiyun.com/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
odds_source_filter 
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

     

关于锁的操作

  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对应的值 ];  按正常流程处理即可

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

通过上面优化的结果:高峰期的队列无积压。

这里面最主要问题:开发人员没有性能意识,自己给自己挖坑,人为增加系统压力

1. 大量无谓的重复查询

2. 大量无谓的写操作以及没有分散写

3. 主次不分, 让边缘功能影响了核心功能

4. 并发冲突,系统无法横向扩展

转载于:https://www.cnblogs.com/sealake/p/8118568.html

<think>嗯,用户的问题是Java队列卡住了,需要让队列重新执行任务。我得先理解队列卡住的原因。根据引用中的信息,队列可能因为任务堆积或者线程池配置不当导致任务无法及时处理。比如,引用[1]提到,任务调度和执行依赖于队列,如果队列中有很多未被处理的任务对象,就会导致卡顿。这时候需要用jmap检查队列中的任务数量。 接下来,线程池的配置也很重要。引用[2]和[3]讨论了线程池的大小设置,特别是CPU密集型和IO密集型任务的配置。如果线程池的线程数不足,任务就会积压队列里。比如,用户可能用了默认的单线程执行器,导致任务排队等待。这时候需要调整线程池的大小,或者使用合适的队列类型,比如有界队列,避免无限制堆积。 另外,任务本身的执行是否阻塞了线程?比如,如果任务中有同步阻塞操作,而线程池的所有线程都在等待,就会导致队列卡住。这时候可能需要检查任务代码是否有死锁或者长时间阻塞的情况,或者使用异步处理来释放线程。 还有可能任务执行失败后没有正确的重试机制,导致任务被丢弃。需要添加重试逻辑,确保失败的任务能够重新入队执行。引用[3]提到,需要保证同一实例的任务顺序执行,但线程池的线程数要足够处理任务量。 最后,监控队列状态也很重要,比如实时监控队列大小和线程池状态,及时发现问题。可能还需要优雅的重启机制,先停止新任务,处理完队列中的任务后再重启服务,避免数据丢失。 综合这些方面,解决方案应该包括检查队列堆积情况、优化线程池配置、处理任务异常、添加重试机制、异步处理和监控队列状态。需要一步步排查原因,然后针对性地调整配置和代码。</think>### 分步解决方案:Java队列卡住问题排查与恢复执行 #### 1. 确认队列卡死的根本原因 - **检查队列堆积状态** 通过命令`jmap -histo <PID> | grep "TriggerParam"`检查队列中未处理的任务对象数量[^1]。若发现大量积压,说明任务消费速度低于生产速度。 - **线程池状态分析** 使用`jstack <PID>`导出线程堆栈,观察线程池中的工作线程是否: - 全部处于`BLOCKED`或`WAITING`状态(可能死锁) - 因I/O操作被阻塞(如数据库连接未释放) - 执行耗时操作未响应中断 #### 2. 恢复队列执行的紧急措施 ```java // 示例:动态调整线程池容量(Spring环境) @Bean public TaskScheduler taskScheduler() { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); // I/O密集型场景建议设置为CPU核心数*2[^2] scheduler.setPoolSize(Runtime.getRuntime().availableProcessors() * 2); return scheduler; } ``` #### 3. 防止队列卡死的长期优化 | 优化方向 | 具体措施 | |----------------|-------------------------------------------------------------------------| | 队列选择 | 使用`LinkedBlockingQueue`设置合理容量阈值,避免无界队列导致内存溢出 | | 线程隔离 | 对关键任务使用独立线程池,防止因某一任务类型阻塞影响全局[^3] | | 任务监控 | 集成Micrometer指标监控队列深度、活跃线程数、任务耗时等关键指标 | | 优雅终止 | 实现`ShutdownHook`确保中断时先完成队列任务,再调用`awaitTermination()` | #### 4. 任务重试机制实现 ```java // 带指数退避的重试模板 public class RetryTemplate { private static final int MAX_RETRIES = 3; private static final long BASE_DELAY = 1000; public static <T> T executeWithRetry(Callable<T> task) { int retries = 0; while (retries < MAX_RETRIES) { try { return task.call(); } catch (Exception e) { long delay = (long) (BASE_DELAY * Math.pow(2, retries)); Thread.sleep(delay); retries++; } } throw new RetryException("Exceeded max retries"); } } ``` #### 5. 队列复活操作流程 1. **暂停新任务入队**:调用`queue.pause()` 2. **导出积压任务**:通过`drainTo()`方法转移现有任务到临时列表 3. **重置线程池**: ```java executor.shutdownNow(); // 发送中断信号 if (!executor.awaitTermination(10, TimeUnit.SECONDS)) { // 强制关闭未响应线程 } executor = new ThreadPoolExecutor(...); // 重建线程池 ``` 4. **重载任务**:按优先级重新提交任务,附加重试标记 5. **恢复服务**:逐步放开流量入口
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值