前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【工程】在线诊断系统设计与实现

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

作者头像
Leetcode名企之路
发布2018-10-09 12:00:45
1.1K0
发布2018-10-09 12:00:45
举报
文章被收录于专栏:Leetcode名企之路

0. 概要

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

1.诊断分类

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

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

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

2.整体架构

在线诊断架构图

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

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

3.数据结构设计

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

代码语言:javascript
复制
request:
{
    "deviceId": "xxx",
    "timeStamp":"1533101903000",
    ...
    "debug":"on"; //线上实际流量没有这个标记.
}

返回结果:

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

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

代码语言:javascript
复制
...//业务逻辑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方法:

代码语言:javascript
复制
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参数,执行延后。
代码语言:javascript
复制
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,工程师埋点的时候一行代码即可搞定:

代码语言:javascript
复制
 debugOnline.putLog("after logic A ",
  () ->  map.keySet().stream().collect(Collectors.joining(",")));

3.线程安全,有序

代码语言:javascript
复制
Map<String, StringBuilder> debugMessage = Collections.synchronizedMap(new LinkedHashMap<>());

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

祝:搬砖愉快:)

相关阅读

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-09-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Leetcode名企之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0. 概要
  • 1.诊断分类
  • 2.整体架构
  • 3.数据结构设计
  • 相关阅读
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档