任务下发优化分析过程记录
任务下发优化分析过程记录
背景
最近接手了一个任务下发平台, 基本功能是接收任务脚本, 下发给目标服务器执行.
简化的业务流程如下:
sequenceDiagram
autonumber
participant client
participant server
participant DB
participant MQ
participant target
client->>+server: 任务请求
server->>DB: 新建任务和任务明细
server->>target: 下发任务
target->>+MQ: 上报结果
MQ->>-server: 处理结果
server->>DB: 更新任务和任务明细
server->>-client: 返回结果
理论上讲同一个 task 内目标执行是并行的, 整个流程耗时与目标数量无关.
但实际情况是目标数量大时, 耗时会超线性增长.
排查
分析代码看消费者主要业务逻辑为更新任务明细表, 通过查询日志看消息处理时长非常不稳定. 推测整个业务流程遇到了竞争.
表结构如下:
// task表
create table task
(
id bigint not null comment '主键id' primary key
)
// task_server表, 用于记录任务明细
create table task_server
(
id bigint not null comment '主键'
primary key,
task_id bigint not null comment '主表task_id',
ip varchar(20) not null comment '具体执行任务的机器内网ip',
status tinyint(1) not null comment '任务状态;0:待发送,1:执行中,2执行完成,3:发送失败:4:手动终止,9:任务超时',
result tinyint(1) null comment '任务结果;0:执行失败,1:执行成功,2:执行超时',
msg varchar(128) null comment '失败详情',
)
更新语句如下:
update task_server set status={status},result={result},u_time={utime},msg={msg}
where task_id={taskId} and ip={ip}
通过 explain 查看, 发现索引类型为 index_merge, 同时用到了 idx_task_id,idx_ip 两个索引;
根据分析, 同一个 task 下更新一条任务明细数据时, 会将其它任务明细加上行锁, 导致大量锁竞争.
mysql> explain UPDATE task_server t SET t.status = 1 WHERE t.task_id=95722684647319 AND t.ip='192.168.68.92';
+----+-------------+-------+------------+-------------+--------------------+--------------------+---------+------+------+----------+--------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------------+--------------------+--------------------+---------+------+------+----------+--------------------------------------------------+
| 1 | UPDATE | t | NULL | index_merge | idx_task_id,idx_ip | idx_task_id,idx_ip | 8,82 | NULL | 1 | 100.00 | Using intersect(idx_task_id,idx_ip); Using where |
+----+-------------+-------+------------+-------------+--------------------+--------------------+---------+------+------+----------+--------------------------------------------------+
验证行锁
测试数据如下:
id | task_id | ip | msg |
---|---|---|---|
95722684658071 | 95722684647319 | 192.168.68.92 | |
95722684658583 | 95722684647319 | 10.9.188.56 | |
95722699750295 | 95722699740567 | 192.168.68.92 | |
95722699750807 | 95722699740567 | 10.9.188.56 |
session1
begin;
UPDATE task_server t SET t.status = 1 WHERE t.task_id=95722684647319 AND t.ip='192.168.68.92';
session2
update task_server set msg='foo' where id=95722684658071;
// blocked
update task_server set msg='foo' where id=95722684658583;
// blocked
update task_server set msg='foo' where id=95722699750295;
// blocked
update task_server set msg='foo' where id=95722831021975;
Query OK, 0 rows affected (0.01 sec)
解决方案
通过以上分析可以知道耗时大量增加是因为大量数据库竞争锁导致的.解决方案就是避免加锁.
我采用的方案如下:
select id from task_server where task_id={taskId} and ip={ip};
update task_server set ... where id={id};
虽然两条语句无法保证一致性, 但是在这个业务场景下完全没问题.