应用场景
服务在运行过程中,难免出现异常情况,有些异常通过重试等手段可以自动恢复,有些则不能,严重异常甚至会中断客户业务。所以我们需要一个系统来记录这些异常,并且在满足特定的条件时触发报警。传统方法是打印文件日志,通过收集日志到特定的系统,例如开源的ELK(ElasticSearch, Logstash, Kibana)中。 这些开源的系统往往是由多个复杂的分布式系统组成,自行维护面临着技术门槛高、时间和人力成本高的问题。
云监控提供了一个开箱即用的事件监控功能,能很好解决这些问题。 下面通过几个例子简单说明下如何使用事件监控功能。
实战案例
第一步:上报异常
事件监控提供了JAVA SDK和Open API两种上报数据的方式,这里介绍通过JAVA SDK 上报数据。
Step1 添加 Maven 依赖
<dependency>
<groupId>com.aliyun.openservices</groupId>
<artifactId>aliyun-cms</artifactId>
<version>0.1.2</version>
</dependency>
Step2 初始化SDK
// 这里的118代表云监控的应用分组ID,可以以应用的角度来对事件归类, 可以到云监控应用分组列表中查看分组的ID。
CMSClientInit.groupId = 118L;
// 这里的地址是事件系统上报的入口,目前是公网地址。accesskey和secretkey用于身份识别。
CMSClient c = new CMSClient("https://metrichub-cms-cn-hangzhou.aliyuncs.com", accesskey, secretkey);
Step3 考虑是否异步上报数据
云监控事件默认提供了同步的上报策略。 好处是编写代码简单、 保证每次上报事件的可靠,不丢失数据。
但是同步策略也带来一些问题。因为要在业务代码中嵌入事件上报代码,如果网络出现波动,可能会出现阻塞代码执行,影响正常的业务。
有很多业务场景并不需要100%要求事件可靠不丢,所以我们需要一个简单的异步上报封装。将事件写到一个LinkedBlockingQueue中,然后通过ScheduledExecutorService异步在后台批量上报。
//初始化queue与Executors:
private LinkedBlockingQueue<EventEntry> eventQueue = new LinkedBlockingQueue<EventEntry>(10000);
private ScheduledExecutorService schedule = Executors.newSingleThreadScheduledExecutor();
//上报事件:
//每一个事件都包含事件的名称与事件的内容,名称用于识别事件,内容是事件的详细信息,支持全文搜索。
public void put(String name, String content) {
EventEntry event = new EventEntry(name, content);
// 这里事件队列满后将直接丢弃,可以根据自己的情况调整这个策略。
boolean b = eventQueue.offer(event);
if (!b) {
logger.warn("事件队列已满,丢弃事件:{}", event);
}
}
//异步提交事件,初始化定时任务,每秒执行run方法批量上报事件。可以根据自己的情况调整上报间隔。
schedule.scheduleAtFixedRate(this, 1, 1, TimeUnit.SECONDS);
public void run() {
do {
batchPut();
} while (this.eventQueue.size() > 500);
}
private void batchPut() {
// 从队列中取出99条事件,用于批量上报
List<CustomEvent> events = new ArrayList<CustomEvent>();
for (int i = 0; i < 99; i++) {
EventEntry e = this.eventQueue.poll();
if (e == null) {
break;
}
events.add(CustomEvent.builder().setContent(e.getContent()).setName(e.getName()).build());
}
if (events.isEmpty()) {
return;
}
// 批量上报事件到云监控, 这里并未重试, SDK也没有重试, 如果对事件可靠度要求高需要自己加重试策略。
try {
CustomEventUploadRequestBuilder builder = CustomEventUploadRequest.builder();
builder.setEventList(events);
CustomEventUploadResponse response = cmsClient.putCustomEvent(builder.build());
if (!"200".equals(response.getErrorCode())) {
logger.warn("上报事件错误:msg: {}, rid: {}", response.getErrorMsg(), response.getRequestId());
}
} catch (Exception e1) {
logger.error("上报事件异常", e1);
}
}
Step4 事件上报Demo
Demo1:http controller的异常监控
主要目的是监控http请求是否有大量异常,如果每分钟异常次数超过一定数量就报警。
实现原理是通过spring的拦截器或者servlet filter等技术对HTTP请求拦截,如果出现异常就记录日志,最后通过配置报警规则来达到报警的目的。
上报事件的demo如下:
// 每个事件应该有丰富的信息来帮助我们搜索和定位问题,这里使用的map来组织事件, 最后转成Json格式作为事件的content。
Map<String, String> eventContent = new HashMap<String, String>();
eventContent.put("method", "GET"); // http 请求方法
eventContent.put("path", "/users"); // http path
eventContent.put("exception", e.getClass().getName()); //异常类名,方便搜索
eventContent.put("error", e.getMessage()); // 异常报错信息
eventContent.put("stack_trace", ExceptionUtils.getStackTrace(e)); // 异常堆栈,方便定位问题
// 最后使用前面封装好的异步上报方法提交事件,这里是异步上报,并且没有重试,可能会小概率丢事件,但是已经能很好的满足http未知异常报警这个场景了。
put("http_error", JsonUtils.toJson(eventContent));
Demo2:后台定时任务执行情况的监控与消息消费情况的监控
同上面的http事件,有很多类似的业务场景需要报警,例如后台任务与消息队列消费等,都可以通过类似的方式上报事件达到监控的目的。当异常发生时,第一时间收到报警。
//消息队列的事件组织:
Map<String, String> eventContent = new HashMap<String, String>();
eventContent.put("cid", consumerId); // 代表消费者的身份
eventContent.put("mid", msg.getMsgId()); // 消息的id
eventContent.put("topic", msg.getTopic()); // 消息的主题,
eventContent.put("body", body); // 消息的主体
eventContent.put("reconsume_times", String.valueOf(msg.getReconsumeTimes())); // 消息失败重试的次数
eventContent.put("exception", e.getClass().getName()); // 发生异常时的异常类名
eventContent.put("error", e.getMessage()); // 异常信息
eventContent.put("stack_trace", ExceptionUtils.getStackTrace(e)); // 异常堆栈
// 最后上报事件
put("metaq_error", JsonUtils.toJson(eventContent));
对队列消息消费异常设置报警:
Demo 3:记录重要事件
事件还有一种使用场景是用来记录一些重要的业务发生,但是不需要报警,方便日后翻看。 例如重要业务的操作日志,改密码,修改订单,异地登录等。