【工程】在线诊断系统设计与实现

0. 概要

本文分享一些在线问题诊断的经验,主要是业务层面,服务层面的在线问题诊断一般需要依赖服务监控系统和报警系统来辅助定位问题。

1.诊断分类

在服务端的开发中,我觉得有这几类问题的诊断。

  • 仅仅知道请求关键参数的诊断。比如某个手机出了不该出的东西,我们诊断一下,只需要知道这个手机的imei即可;某个知乎用户,他的知乎为什么会出不该出的广告,只需要给他的知乎id,其他信息自己构造就可以。
  • 需要知道设备和请求详细参数的诊断。上面第一种诊断,一般问题可以解决,但是有时候线上的某个设备,你自己构造请求过去,返回的结果也是符合预期的,但是实际就是返回的内容不符合预期的,这个时候需要在线系统有能力嗅探和捕捉线上的真实请求。
  • 数据分析层面的诊断。有时候会有这么一种情况,1和2都是正常的,单独构造某个请求是正常的,那么可能是部分设备出了问题,这个时候需要动用一些数据分析,数据分析通常是和各业务指标在一起的,比较依赖于经验。

今天主要围绕第1类诊断来展开谈谈这类系统的设计与实现。

2.整体架构

在线诊断架构图

如上图,该诊断系统需要具备以下几个能力。

  • 界面。提供给开发人员方面构造请求的友好的界面。
  • 请求构造。让开发人员只需替换关键信息(如手机imei,账户ID等)即可构造请求。
  • Porxy。该模块需要支持各种不同环境(如分机房)的请求客户端构造、需要支持某个固定IP的服务的客户端、每个模块的客户端。Http或者是RPC的。
  • 结果展示。需要方便的给研发人员诊断问题的结果,一般以json来展示即可。

3.数据结构设计

诊断日志最重要的功能是:需要知道系统中每一步关键逻辑发生了什么。同时又不能够给在线系统带来相应的时间和空间的开销。所以需要给流量打上标记,标记这个流量是debug的模拟流量。

request:
{
    "deviceId": "xxx",
    "timeStamp":"1533101903000",
    ...
    "debug":"on"; //线上实际流量没有这个标记.
}

返回结果:

response:
{
    "status": "0",
    "timeStamp":"1533101903000",
    ...
    //线上实际流量没有这个标记.
    "debugOnlineInfo": {
        step1:{"", ""},
        step2:{"", ""},
        step3:{"", ""},
        ...
    }; 
}

数据结构定好之后,如果很粗糙的在需要在日志埋点的地方都需要工程师去判断是不是debug=on,那工程师会崩溃的。所以功能上要做成无侵入业务的。我想没有人会愿意付出额外成本不断的去写如下代码:

...//业务逻辑A
if (debugOn == true) {
    //工程师内心OS: 干嘛要让我判断啊,这种操作不应该封装好吗? fuck...
    debugOnlineInfo.putLog("after loggic A, the result is xxx");
}
...//业务逻辑B
List<CommonBean> commonBeans = xxx. 
if (debugOn == true) {
    //工程师内心OS: 干嘛要让我遍历啊,这种操作不应该封装好吗? fuck...
    debugOnlineInfo.putLog("after loggic B, the result is ", commonBeans.stream().map(e => e.getId()).collect(collectors.joining(",")));
}

4.实现 基于以上的分析,我们实现的时候需要考虑以下几个点:

  • 不能让业务方感知是不是debugOn的日志,也不能对线上系统有额外的开销;
  • 要提供常用遍历List的接口,打印关键信息即可;如果让业务代码自己去实现,会打印太多信息。
  • 做好NPE的判断,并记录信息; 不要让请求直接挂了;
  • 需要线程安全,因为在实际的线上环境,有的会多个线程都向这个数据里面去put数据,尤其是多条pipeline执行同一逻辑的时候;
  • 需要有序,我们需要知道日志的每一步发生了什么。 每一次请求进来的时候都构造一个DebugOnlineInfo对象,根据debugOn的信息构建类的实现,关系超级简单。

image

DebuggerOnlineImpl用作在线诊断时候的实现,DebuggerOnlineNoOp作为线上实际流量的实现,线上的真实流量DebugOnline实现为空。

DebuggerOnlineImpl实现的时候有一些细节需要注意。

1.为了只打印关键信息,重载appendLog方法:

public <T> void appendLog(String key, Collection<T> collection, IdExtractor<T> idExtractor) {
      if (StringUtils.isBlank(key)) {
            return;
      }
      String value = collection.stream().map(idExtractor::get).collect(Collectors.joining(","));
      debugMessage.put(key, value);
}

这里IdExtractor为一个解析类关键信息的接口,如果不这么做,直接toString()的话,那么会导致日志信息特别多,日志过多不利于我们定位问题。所以提供一个解析的类,可以供常用的遍历Collection,实现的时候用单例即可。

  1. 为了更加通用,用Supplier重载appendLog方法,使用lamda参数,执行延后。
public void putLog(String key, Supplier<String> stringSupplier) {
    if (StringUtils.isBlank(key) || stringSupplier == null) {
        return;
    }
    String value = stringSupplier.get();
    debugMessage.put(key, value);
}

比如想埋点某个map的所有Key,工程师埋点的时候一行代码即可搞定:

 debugOnline.putLog("after logic A ",
  () ->  map.keySet().stream().collect(Collectors.joining(",")));

3.线程安全,有序

Map<String, StringBuilder> debugMessage = Collections.synchronizedMap(new LinkedHashMap<>());

要保证最后输出有序,所以我们最后用的是LinkedHashMap,保证线程安全,用的是Collections.synchronizedMap。

祝:搬砖愉快:)

相关阅读

原文发布于微信公众号 - Leetcode名企之路(DailyLeetCode)

原文发表时间:2018-09-11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序员互动联盟

自学编程该如何入手?

光讲如何如何怎样怎样学习编程,都不是真正从零开始,针对的都是懂一些语言,有一点语言基础的人。对于一点都不懂的人有点残忍。大多数人都有自学编程的激情,但是如何才...

4249
来自专栏机器之心

放弃Python转向Go语言:我们找到了以下9大理由

选自Stream 作者:Thierry Schellenbach 机器之心编译 参与:黄小天、李亚洲 转用一门新语言通常是一项大决策,尤其是当你的团队成员中只有...

64311
来自专栏AlgorithmDog的专栏

Akka 使用系列之二: 测试

通过上一篇文章,我们已经大致了解怎么使用 Akka,期待细致用法。这篇文章将介绍如何用 Akka-testkit 对 Akka 程序进行测试。 ? ...

2107
来自专栏腾讯开源的专栏

【开源公告】unreal4引擎lua开发首选解决方案sluaunreal正式开源

2582
来自专栏狮乐园

【译】Understanding SOLID Principles - Single Responsibility

之前的第一篇文章阐述了依赖倒置原则(DIP)能够使我们编写的代码变得低耦合,同时具有很好的可测试性,接下来我们来简单了解下单一职责原则的基本概念:

731
来自专栏开源优测

[接口测试 - 基础篇] 02 你应该掌握的Python3接口测试内功

概述 本文主要介绍基于Python3进行接口测试时,应该掌握Python3哪些基本的能力,主要从以下几个方面进行说明。 Python3基本语法 ...

3506
来自专栏别先生

mysql的时间戳timestamp精确到小数点后六位

公司业务使用到Greenplun数据库,根据查询的时间戳来不断的将每个时间段之间的数据,进行数据交换,但是今天发现,mysql的时间戳没有小数点后6位,即精确度...

1281
来自专栏程序人生

永恒不变的魅力

一个程序员,无论你人生的第一个hello world是从basic开始,c开始,抑或javascript开始,接下来了解的一个概念一定是「变量」(variabl...

36112
来自专栏听雨堂

工作流参考模型点评

工作流参考模型点评 工作流参考模型是由WFMC提出来的,对工作流管理系统的实现推荐的一个参考模型。 下面分别对各个组件进行解释: 1) Work...

2156
来自专栏Crossin的编程教室

【每周一坑】用代码给图片配上文字

我们的『每日一坑』栏目里都是一些练手的小题目,难度不高,适合新手用来熟悉编程。如果想要更复杂的大项目,可以上我们的实验室栏目 lab.crossincode.c...

3286

扫码关注云+社区

领取腾讯云代金券