背景

当前项目需要一个拨测系统来检测服务是否正常运行,拨测系统需要满足以下需求:

支持对接口请求结果做判断。

支持对接口的耗时做判断。

支持重试:可能在某一瞬间网络出现了延迟,导致接口请求失败,所以需要重试,连续重试N次失败才算异常。

失败告警,可配置不同的告警接收人。

通用、可配置:支持各种场景的接口协议。

图表展示(可选)。

方案

拨测系统原理上就是定时检查服务,那是否可以偷懒,拿开源的定时任务系统来改造呢。基于这种想法,在研究多个开源项目之后,选择了xxl-job(当前版本2.2.1)。

改造

1. 添加任务

改造任务界面

以上是xxl-job添加定时任务的界面,先修改jobinfo.index.ftl文件,隐藏掉跟监控无关的字段,隐藏的字段相当于采用了默认值。效果如下:

这里保留了:

任务描述。

Cron:自定义配置执行间隔。

运行模式:常规场景,通过BEAN+JobHandler即可满足, 当拨测的接口协议很复杂,无法使用通用的拨测方法时,这里可以选择GUL模式来自定义请求脚本。

JobHandler:选择拨测类别,当前仅提供了接口HTTP拨测,后续会添加其他的拨测类型,比如redis检查。

任务超时时间:可以当做接口性能检测,判断性能是否符合预期。

失败重试次数:可能在某一瞬间网络出现了延迟,导致接口请求失败,所以需要重试,连续重试N次失败才算异常。

负责人。

报警邮件:拨测失败报警,天企业微信名,多个之间用','分隔。

任务参数:输入拨测时需要的参数,比如拨测的URL。

开发接口拨测Handler

在executor项目开发处理器(xxl-job分为admin和executor两个项目,admin是管理、分发,executor执行定时任务业务逻辑),拨测URL等参数由任务参数来输入,这里选择json作为输入格式,定义了几个json字段:

url:拨测地址,必填。

method:HTTP请求Method,可选,默认是POST。

resultKeys:字符串数组格式,当接口返回值里包含了数组里的每一项时,才算拨测成功,可选。

postBody:POST包体,可选。

HTPP拨测Handler代码:

@Component

@Slf4j

public class DialTestHandler {

private WebClient webClient;

public DialTestHandler() {

webClient = WebClient.builder()

.clientConnector(new ReactorClientHttpConnector(

// 允许重定向

HttpClient.create().followRedirect(true)

)).build();

}

/**

* @param param

* @return com.xxl.job.core.biz.model.ReturnT

* @author

* @date

*/

@XxlJob("httpDialTestHandler")

public ReturnT httpDialTestHandler(String param) {

Stopwatch stopwatch = Stopwatch.createStarted();

XxlJobLogger.log("begin httpDialTestHandler, param: {}", param);

try {

DialTestConfig dialTestConfig = JSON.parseObject(param, DialTestConfig.class);

if (StringUtils.isBlank(dialTestConfig.getUrl())) {

throw new Exception("url required!");

}

if (StringUtils.isBlank(dialTestConfig.getMethod())) {

dialTestConfig.setMethod("POST");

}

Mono resultMono = webClient.method(HttpMethod.valueOf(dialTestConfig.getMethod())).uri(dialTestConfig.getUrl())

.contentType(MediaType.APPLICATION_JSON_UTF8)

.accept(MediaType.APPLICATION_JSON_UTF8)

.bodyValue(Optional.ofNullable(dialTestConfig.getPostBody()).orElse(""))

.retrieve()

.onStatus(status -> !status.is2xxSuccessful(), resp -> {

Mono body = resp.body(BodyExtractors.toMono(String.class));

return body.flatMap(str -> {

log.error("request[{}]error,result: {}", dialTestConfig.getUrl(), str);

XxlJobLogger.log("request[{}]error,result: {}", dialTestConfig.getUrl(), str);

// 业务返回的错误

if (StringUtils.isNotBlank(str)) {

throw new RequestException(str, resp.statusCode().value());

}

throw new RequestException(resp.statusCode().getReasonPhrase(), resp.statusCode().value());

});

}).bodyToMono(String.class)

.doOnError(Exception.class, err -> {

throw new RequestException(err.getMessage(), -1);

});

// 这里不做超时限制,由xxl-job任务的超时时间来打断(interrupt)

// String result = resultMono.block(Duration.ofSeconds(2000));

String result = resultMono.block();

if (!CollectionUtils.isEmpty(dialTestConfig.getResultKeys())) {

for (String key : dialTestConfig.getResultKeys()) {

if (!result.contains(key)) {

throw new Exception(String.format("request[%s]error,result not contain: %s", dialTestConfig.getUrl(), key));

}

}

}

log.info("httpDialTestHandler success, param: {}", param);

XxlJobLogger.log("httpDialTestHandler success, param: {}", param);

return ReturnT.SUCCESS;

} catch (RequestException re) {

log.error("httpDialTestHandler RequestException, param: {}, code: {}", param, re.getCode(), re);

XxlJobLogger.log("httpDialTestHandler error, param: {}, code: {}, RequestException: {}", param, re.getCode(), re.getMessage());

} catch (Exception e) {

log.error("httpDialTestHandler Exception, param: {}", param, e);

XxlJobLogger.log("httpDialTestHandler error, param: {}, Exception: {}", param, e.getMessage());

} finally {

stopwatch.stop();

XxlJobLogger.log("finish httpDialTestHandler, param: {}, cost: {}", param, stopwatch.elapsed(TimeUnit.MILLISECONDS));

log.info("finish httpDialTestHandler, param: {}, cost: {}", param, stopwatch.elapsed(TimeUnit.MILLISECONDS));

}

return ReturnT.FAIL;

}

}

@Data

public class DialTestConfig {

private String url;

/**

* POST/GET/...

*/

private String method;

/**

* 返回结果关键字

*/

private List resultKeys;

/**

* POST Body

*/

private String postBody;

}

public class RequestException extends RuntimeException {

public static final int DEFAULT_ERROR_CODE = HttpStatus.INTERNAL_SERVER_ERROR.value();

private int code = DEFAULT_ERROR_CODE;

public RequestException(String message) {

super(message);

}

public RequestException(int code) {

this.code = code;

}

public RequestException(String message, int code) {

super(message);

this.code = code;

}

public RequestException(String message, Throwable cause, int code) {

super(message, cause);

this.code = code;

}

public RequestException(Throwable cause, int code) {

super(cause);

this.code = code;

}

public RequestException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, int code) {

super(message, cause, enableSuppression, writableStackTrace);

this.code = code;

}

public int getCode() {

return code;

}

public RequestException setCode(int code) {

this.code = code;

return this;

}

}

代码逻辑很简单:

从任务参数里获取配置信息。

请求url。

当接口返回的HTTP STATUS不为200时,任务失败。

如果有配置resultKeys,则轮询判断接口是否包含了key,如果没有包含,任务失败。

任务完成。

通用配置

配置示例如下(为了展示的更直观,配置参数了包含了所有配置项):

这里报警邮件里填的并不是邮件,而是企业微信名,是因为后面也改造了监控,告警时同时发送企业微信和邮件,详细后面会讲到。

自定义请求脚本

当拨测接口协议比较复杂时,可以自定义请求脚本,而不是使用现有的BEAN:httpDialTestHandler模式。在允许模式选择GLUE(XXX),比如GLUE(java),然后在列表页面选择GLUE IDE编辑脚本,脚本里可以引入执行器executor项目的类,因为类最终会是在executor项目上运行。

注:当前版本的GLUE(java)不支持lambda语法。

2. 监控告警

告警次数

告警逻辑在admin项目, xxl在任务失败重试时,失败几次(含重试次数),就会告警几次,这里改造成重试的最后一次(含不重试的任务)才告警。

告警逻辑在JobFailMonitorHelper类。

if (info!=null && info.getAlarmEmail()!=null && info.getAlarmEmail().trim().length()>0) {

改造成

if (info!=null && info.getAlarmEmail()!=null && info.getAlarmEmail().trim().length()>0 && log.getExecutorFailRetryCount() == 0) {

log.getExecutorFailRetryCount()返回的是当前任务剩余重试次数。

同样的告警内容里的失败重试次数的值从剩余重试次数改为当前已重试次数。

告警内容逻辑在XxlJobTrigger.processTrigger方法里。

triggerMsgSb.append("
").append(I18nUtil.getString("jobinfo_field_executorFailRetryCount")).append(":").append(finalFailRetryCount);

改为

triggerMsgSb.append("
").append(I18nUtil.getString("jobinfo_field_executorFailRetryCount")).append(":").append(jobInfo.getExecutorFailRetryCount() - finalFailRetryCount);

告警类别

企业微信

xxl-job只有邮件告警,这里加上企业微信告警。

从JobAlarmer类看到XxlJob是通过applicationContext.getBeansOfType(JobAlarm.class)获取告警类别列表,所以只需要写一个自定义JobAlarm实现类并注册成spring的Bean既可,告警内容可以在邮件内容上做减法。

@Component

public class RtxJobAlarm implements JobAlarm {

private static Logger logger = LoggerFactory.getLogger(RtxJobAlarm.class);

/**

* fail alarm

*

* @param jobLog

*/

@Override

public boolean doAlarm(XxlJobInfo info, XxlJobLog jobLog) {

boolean alarmResult = true;

if (info != null && info.getAlarmEmail() != null && info.getAlarmEmail().trim().length() > 0) {

// alarmContent

String alarmContent = "Alarm Job LogId=" + jobLog.getId();

// 这里xxl的triggerMsg里已经使用了
,所以追加内容时也先用
,后面再统一换成企业微信的\n

alarmContent += "
TriggerMsg=
" + jobLog.getTriggerMsg();

if (jobLog.getHandleCode() > 0 && jobLog.getHandleCode() != ReturnT.SUCCESS_CODE) {

alarmContent += "
HandleCode=" + jobLog.getHandleMsg();

}

String title = I18nUtil.getString("jobconf_monitor");

List mails = Arrays.asList(info.getAlarmEmail().split(","));

try {

MessageUtil.postMessage(Lists.newArrayList(MessageTypeEnum.RTX),

mails,

title,

info.getJobDesc() + "\n" + alarmContent.replaceAll("
", "\n"));

} catch (Exception e) {

logger.error(">>>>>>>>>>> xxl-job, job fail alarm rtx send error, JobLogId:{}", jobLog.getId(), e);

alarmResult = false;

}

}

return alarmResult;

}

}

邮件

前面添加任务时,有讲到报警邮件填的不是邮件,而是企业微信名,所以在发送邮件时,需要补上企业邮箱后缀。

参考:

http拨测是什么意思_快速构建拨测系统相关推荐

  1. 使用飞冰+dva快速构建一个后台系统

    使用飞冰+dva快速构建一个后台系统 写在前面 最近我们接到这样一个需求,要写一个后台管理系统,时间很急,产品也只是给出了原型稿,把功能陈列了一下,给出的要求就是先注重功能,用起来再去考虑美化.但是作 ...

  2. 构建嵌入式linux系统_用于构建嵌入式Linux系统的4种工具

    构建嵌入式linux系统 Linux正在被部署到比Linus Torvalds在他的宿舍里工作的设备更多的设备中. 受支持的各种芯片架构令人震惊,并导致各种大小的设备都使用Linux. 从庞大的IBM ...

  3. http拨测是什么意思_网络性能拨测-网络传输速度体验检测系统有哪些指标?

    原标题:网络性能拨测-网络传输速度体验检测系统有哪些指标? 网络性能拨测是对网络情况检测中用到的,用一些特定的网络数据指标来反映不同时间和地区用户使用网络的情况,可以协助网络提供商更好地了解用户的使用 ...

  4. 录制完脚本怎么做接口自动化测试_快速构建轻量级接口自动化框架

    随着移动互联网和微服务的迅速发展,大部分企业都采用接口的方式实现客户端和服务端的交互,传统的PC端也逐渐趋向于前后端分离架构.为了应对此种架构下的业务迭代,很多QA团队开始推广接口自动化,甚至是自研接 ...

  5. 普通电笔能测几伏电压_电笔最低可以测电压是多少伏?直流十二伏电笔也可以亮吗?...

    展开全部 6V 弱电测电笔:用于电子产品的测试,一般测试电压为6v-24v.为了便于使用,电笔尾部常带有一根带夹子的e5a48de588b6323131333532363134313032313635 ...

  6. vue 圆形百分比进度条_快速构建一个圆形的进度条

    在一些特别生的网站上,用户需要一个可视化的是示,以表明网站资源仍然在加载.从Spinner到Skeleton屏幕有不同的方法来解决这类的用户体验效果. 如果我们使用的是开箱即用的解决方案,它为我们提供 ...

  7. disruptor框架为什么不流行_从构建分布式秒杀系统聊聊Disruptor高性能队列

    前言 秒杀架构持续优化中,基于自身认知不足之处在所难免,也请大家指正,共同进步.文章标题来自码友的建议,希望可以把阻塞队列ArrayBlockingQueue这个队列替换成Disruptor,由于之前 ...

  8. 使用 Azure Monitor 快速构建 OS 内部指标监控平台

    Azure Monitor 可以用于收集.分析和处理来自云与本地环境的遥测数据的综合解决方案,支持将应用程序和服务的可用性最大化.帮助企业了解应用程序的性能,并主动识别影响应用程序及其所依赖资源的问题 ...

  9. dubbo 服务压测_全链路压测资料汇总——业内大厂解决方案

    最近忙于公司的全链路压测平台调研和技术规划文档输出工作,参考了全网能搜到的业内大厂的全链路压测方案,这里做个汇总,以及将个人认为可以落地的方案做一个关键点整理. 技术链接 滴滴全链路压测解决之道 阿里 ...

最新文章

  1. Facebook 开源聊天机器人Blender,经94 亿个参数强化训练,更具“人情味”
  2. 未捕获ReferenceError:未定义$?
  3. python创建虚拟环境_Python学习笔记:创建Python开发Web程序的虚拟环境
  4. brasb 密码自动应答
  5. php+5.3.15下载,Rapid PHP2018
  6. python需要在linux上运行,在linux上运行python的方法
  7. 百战程序员-人工智能从入门到开发教程(60万下载量)
  8. 不需要数据库的php迷你博客程序,GitHub - Smilefish0/miniblog: 一个不需要数据库、轻量级、微型、开源的博客程序!...
  9. 路由器UPnP功能具体作用,有什么坏处,
  10. 渐进式Express源码学习6-独孤求败
  11. USER_TAB_COLS,USER_TAB_COLUMNS,ALL_TAB_COLS,ALL_TAB_COLUMNS获取数据库元素的区别
  12. luogu4182 [USACO18JAN] Lifeguards P (单调队列优化dp)
  13. 第六部分 项目成本管理
  14. Samael对决Lucifer!!! ……Dean的任务真相……
  15. 我协会负责人调研东风汽车·····
  16. NLP自然语言处理——文本分类(CNN卷积神经网络)
  17. 操作系统之设备管理简介
  18. 《随遇而安》读书摘记
  19. BUUCTF-另外一个世界
  20. XP 32位与64位的区别

热门文章

  1. spoon-web网页版安装教程【kettle】
  2. mmcv包在linux下无法使用inshow展示报错:qt.qpa.xcb: could not connect to display
  3. Redis客户端 Jedis 与 Lettuce
  4. python串口通信的接收与发送_31.用python中的serial向串口发送和接收数据(案例一)...
  5. inux学习笔记:14-1.rpm包
  6. linux下perror函数,Linux/Unix C编程之的perror函数,strerror函数,errno
  7. 大学里计算机专业很忙吗,大学里“特累”的10大专业,忙到没时间恋爱!
  8. 好的产品需要「显而易见」,而其本质就是简单
  9. python三种基本数据类型有哪些_python中基本数据类型有哪些
  10. R语言读取txt文件中的内容