重试依然失败怎么办?这个问题曾经一直困扰我,我制定的重试策略是重试N次,重试时间间隔2倍递增,其中最大重试次数、最小重试间隔、最大重试间隔等参数都要求可以动态配置。这个重试策略并不是完美的,我一直困扰于超过最大重试次数依然失败,该怎么办?因为最终失败时我能做的只有 打印异常日志、上报异常消息到公司故障群,稍后人工介入处理异常场景。
从我的经验来看,重试失败的频率并不高,但是每次出现都需要人工介入处理真的很烦人。处理这个问题往往很棘手,需要在线上手动执行一些命令,是比较危险的人肉运维工作。
而且大多数情况下,重试失败通常不是由我的问题引起的,而是因为我的下游系统存在缺陷,导致我的系统处理失败。重试无法解决这类问题。在这种情况下,我只能找到相关的同事,请求他们尽快修复问题。待问题修复完成后,我再手动重试来解决这个异常情况。
我曾经吃过人肉运维的亏,对人肉运维工作深恶痛绝。# 点击看我的悲惨经历 所以在无数次地重复以上工作后,我终于忍无可忍,
人肉运维一时不会出错,但难保一世不会出错。出错就只能自己背锅,出来混,要学会保护自己。我需要设计一个系统,减少人肉运维的工作量。
故障可视化管理
提供可视化页面处理故障,可以有效提高安全性,避免人肉运维工作。解决思路是系统故障发生后,统一上报到故障管理平台。然后故障管理平台提供可视化页面展示故障列表,故障详情。在可视化页面提供修复工具。一般的修复工具是继续重试,如果有需求回滚故障,也可以提供回滚工具。
系统的架构图如下
系统交互共存在三个角色 业务系统、故障管理后台、故障管理后台页面。一个故障的处理流程是这样的。
当请求一直重试失败超过最大重试次数时,业务系统会上报到故障MQ,关账管理平台消费MQ,收集故障并落库。研发同学收到故障通知,同时在故障管理后台页面可以看到故障列表、故障详情。 排查问题原因、敦促相关同事修复问题后,点击重试按钮。故障管理后台收到重试请求,会通过 Rpc SPI 调用到业务系统 重试故障,并告知管理后台成功和失败结果。
故障管理处理流程
上报故障的处理流程
sequenceDiagram
业务系统 ->> 业务系统: 重试一直失败
业务系统 ->> MQ: 上报故障
MQ ->> 故障管理后台: 收集故障,落库存储
故障管理后台->> IM: 通知到公司故障群
故障重试的处理流程
sequenceDiagram
研发 ->> 后台页面: 浏览故障列表
研发 ->> 研发: 排查问题原因、敦促相关同事修复问题
研发 ->> 后台页面: 点击重试按钮
后台页面 ->> 故障管理后台: 重试故障 Http接口
故障管理后台->> 业务系统: 调用 Rpc SPI 重试故障
业务系统 -->> 故障管理后台: 返回处理结果,成功或失败原因
故障管理后台-->> 后台页面: 返回处理结果
后台页面-->> 研发: 失败继续排查原因。成功则结束
上报故障和重试故障是两个核心流程比较关键的点包括
上报故障使用 MQ 的方式,可以实现业务系统和故障后台的解耦、隔离。不妨碍业务流程。
业务系统需要实现故障重试SPI。业务系统负责重试的业务逻辑
故障管理后台是通用平台,通过故障类型区分各种故障。通过故障类型关联业务系统。重试故障时,根据故障类型查找到对应的业务系统 Rpc Client,并调用SPI方法 重试故障。
需要有前端页面实现故障列表页检索、故障详情页展示、故障重试按钮等功能。这是重中之重的环节,没有可视化页面,故障管理后台就没有存在的意义了。
系统关键实现
故障收集任务
CREATE TABLE `fault_collect_task` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增ID',
`misId` bigint(20) NOT NULL DEFAULT '0' COMMENT '创建人 ',
`taskCode` varchar(256) NOT NULL DEFAULT '' COMMENT '故障类型code码,上报时需指定,全局唯一',
`taskName` varchar(128) NOT NULL DEFAULT '' COMMENT '任务名',
`taskComment` text comment '任务备注',
`appkey` varchar(1024) DEFAULT '' COMMENT '业务系统微服务唯一键',
`port` int(11) NOT NULL DEFAULT '0' COMMENT '业务系统spi port',
`status` int(11) NOT NULL DEFAULT '0' COMMENT '状态',
`ext_config` text COMMENT '扩展字段',
`op_log` text COMMENT '操作记录',
`ctime` int(11) NOT NULL DEFAULT '0' COMMENT '创建时间',
`utime` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uni_idx_id` (`taskId`, `identityId`,`key` )
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='故障收集任务表'
故障收集任务是某一类故障的集合,任务会关联业务系统,上报故障时候,需要指定taskCode,用来标记故障属于哪一个任务。
故障表
故障表结构如下
CREATE TABLE `fault_item` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增ID',
`user_id` bigint(20) NOT NULL DEFAULT 0 COMMENT 'userId',
`biz_id` bigint(20) NOT NULL DEFAULT 0 COMMENT 'userId',
`unique_key` varchar(256) NOT NULL DEFAULT '' COMMENT '业务定义的唯一键',
`task_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '故障收集任务id',
`task_code` varchar(256) NOT NULL DEFAULT '' COMMENT '故障收集任务code码,上报时需指定,全局唯一',
`context` text COMMENT '故障内容',
`status` int(11) NOT NULL DEFAULT 0 COMMENT '状态 0 初始化,1. 已重试成功,2 上次重试失败,3 忽略 ',
`ext_config` text COMMENT '扩展字段, 被索引字段也放在里面',
`comment` text COMMENT '备注',
`retry_result` text COMMENT '重试结果',
`version` int(11) NOT NULL DEFAULT 0 COMMENT 'version',
`ctime` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间',
`utime` int(11) NOT NULL DEFAULT 0 COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uni_idx_taskid_id` (`task_id`, `unique_key`),
KEY `task_code_ctime_idx` (`task_code`, `ctime`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '故障表';
注册Rpc Client 进入Spring
业务系统需要实现故障重试的SPI,故障管理后台需要创建Rpc Client
的Bean,注册进Spring管理。
首先构建BeanDefinitionBuilder
,需要声明该bean的类型,声明bean的重要参数。 将Spring ApplicationContext
转化为 BeanFactory
。调用 registerBeanDefinition
注册bean进上下文。
ConfigurableApplicationContext configurableApplicationContext = ((ConfigurableApplicationContext) applicationContext);
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(ThriftProxy.class);
beanDefinitionBuilder.addPropertyReference("mtThriftPoolConfig", "thriftPoolConfig");
beanDefinitionBuilder.addPropertyValue("timeout", );
beanDefinitionBuilder.addPropertyValue("appKey", "");
beanDefinitionBuilder.addPropertyValue("port", );
beanFactory.registerBeanDefinition(config.getBeanName(), beanDefinitionBuilder.getRawBeanDefinition());
总结
借助于故障管理平台可以避免很多人肉运维工作,可以作为公司通用的平台,提供最大的价值。虽然系统问题出现的频率往往很少,但是修复问题的成本是比较高的。提高修复问题的效率,提高运维的安全性是故障收集平台最大的价值!