• 极客专栏正式上线!欢迎访问 https://www.jikewenku.com/topic.html
  • 极客专栏正式上线!欢迎访问 https://www.jikewenku.com/topic.html

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

技术杂谈 勤劳的小蚂蚁 4个月前 (12-29) 85次浏览 已收录 0个评论 扫描二维码

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。
祝:搬砖愉快:)

丨极客文库, 版权所有丨如未注明 , 均为原创丨
本网站采用知识共享署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议进行授权
转载请注明原文链接:【工程】在线诊断系统设计与实现
喜欢 (0)
[247507792@qq.com]
分享 (0)
勤劳的小蚂蚁
关于作者:
温馨提示:本文来源于网络,转载文章皆标明了出处,如果您发现侵权文章,请及时向站长反馈删除。

您必须 登录 才能发表评论!

  • 精品技术教程
  • 编程资源分享
  • 问答交流社区
  • 极客文库知识库

客服QQ


QQ:2248886839


工作时间:09:00-23:00