聊聊 Sdk 和问题排查

开发 前端
当我们以 sdk 的方式提供一种能力的时候,我们的实现不仅决定了业务的使用方式和成本,还决定用户是否乐意使用它。

不分享什么知识,聊一下最近的一些思考和看到的一些内容。这两个内容看起来没什么关系,其实也有关系。

sdk 大家都不陌生,比如我们经常用到的 npm 包。当我们以 sdk 的方式提供一种能力的时候,我们的实现不仅决定了业务的使用方式和成本,还决定用户是否乐意使用它。所以我们不能只考虑到功能,还需要考虑到使用方式以及 sdk 本身对业务的影响,不管是稳定性还是性能。当我们的 sdk 对业务来说是刚需时,如果 sdk 有问题,业务可能会联系我们处理,因为它需要这个 sdk。但是如果对业务来说这个 sdk 不是刚需时,业务可能直接 uninstall 我们的 sdk 并删除对应的代码。这对于提供 sdk 的我们来说显然不是个好事情。但是不管是否刚需,作为提供方,我们都需要努力去做好所提供的服务。

1 内嵌形式

## 1.1 内嵌于业务代码的形式

我们使用的 sdk 大多数都是引入业务代码中,然后使用它提供的功能,这种情况下,有两种模式,第一种是业务要感知 sdk 提供的 API。我们需要知道什么时候使用什么 API。第二种是业务不需要感知 sdk 提供的 API,或者说这时候 sdk 不提供 API,它本身就像一个黑盒子,业务引入后就内置了某些功能,比如我们提供一个定时上报业务内存使用情况的 sdk,那么业务就不需要关注 sdk 的具体实现。下面以统计请求耗时为例看看如何实现这个 sdk。

1.第一种

  1.   start(...) {} 
  2.   end(...) {} 

第一种方式是比较朴素的实现,sdk 提供了一个 start 和 end 的 API,业务在开始请求和结束请求时分别执行这两个 API,这样 sdk 就可以计算出这个请求的耗时。但是这种方式看起来并不是那么友好,首先会侵入业务的代码逻辑,其次业务还需要感知这个 sdk,需要考虑什么时候调 start,什么时候调 end,而且 sdk 还依赖业务传入请求和响应的上下文,才能计算出某一个请求的耗时,总的来说,这种方式比较麻烦。

2 第二种

我们希望对业务的侵入性和感知少一点,所以决定直接劫持 Node.js 里的 API。Node.js 里以下面的形式可以创建一个服务器。

  1. http.createServer((req, res) => {}) 

那么我们直接劫持这个 createServer。

  1. const createServer = http.createServer; 
  2. http.createServer = (cb) => { 
  3.   return createServer((req, res) => { 
  4.     const start = Date.now(); 
  5.     res.on('finish', () => { 
  6.         const cost = end - start; 
  7.       }); 
  8.     cb(); 
  9.   }); 

通常,sdk 是提供 API,由业务主动调用,或者说触发 sdk 的代码,因为 sdk 无法捕获业务代码什么时候需要使用 sdk 的某个功能。但是当我们可以捕获到业务什么时候需要我们时,就可以以更好的方式去提供这个 sdk。这种方式可以使得业务不需要过多感知 sdk,比如上面的例子中,业务只需要保证在调用http.createServer 之前执行我们的 sdk 就可以。sdk 内嵌业务代码中是非常常见的形式,但是我们希望尽量减少对业务的侵入,或者说减少业务的心智负担,大家可能都有过这种经历,当看到一个 sdk 提供密密麻麻的参数时,第一反应就不想用了。

2 脱离业务代码的形式

那么是否能以一种脱离于业务代码的方式提供一个 sdk,这样不仅不会影响业务代码,对于升级 sdk 来说也更容易。但是这种方式往往不容易,主要取决于场景,比如业务需要通过一个 sdk 上传文件,那么这个 sdk 以内嵌的方式会比较合适。但是,某些场景下,脱离业务代码的 sdk 是可以做到的,比如排查问题类的工具。在 Node.js 里,我们调试或诊断进程的方式通常是在业务代码里内嵌相关的代码,然后在必要的时候执行对应的代码,比如获取堆快照。因为我们的代码只有置身于进程中,才能获取到这个信息。但是不是所有的信息都需要置身于进程中才能获取,比如系统级的数据。我之前碰到一个问题,就是在某个场景下,WebSocket 连接会很快底被断开,通过再客户端 wireshark 捕获的流量中,发现服务器会发送一个 fin 包给客户端,这样就知道是服务器的问题了,但是又因为从客户端到真正的服务器中间还隔了很多层,无法知道是哪一层服务器主动断开了连接,最后通过服务器提供的工具找到了主动发送 fin 包的服务器从而解决了问题。但是我发现服务器的那些工具用起来都非常复杂,如果不经常用,很快就忘了各种命令和参数,像这种场景,就可以封装 sdk 给业务使用,这种形式不仅可以帮助业务排查问题,还不需要侵入业务代码。

3 问题排查

 

我们排查问题通常借助日志,但是日志很多时候也解决不了问题,日志是静态埋点,打多了不仅浪费存储,而且消耗性能,打少了可能缺少排查问题的上下文。但是无论如何,重点是日志是静态埋点,如果我们要加埋点,就得重启服务,有些问题稍纵即逝,重启后可能就很难复现了。所以除了静态追踪技术外,动态追踪技术就非常必要,也非常 cool 了,之前看了一下 ebpf,但是后来没看了,最近重新研究了一下 ebpf 和所衍生的一些排查问题的工具,也看了一下 openresty 作者的文章《动态追踪技术漫谈》,可谓是精彩。当一个进程或者系统有问题时,我们希望保留现场,然后再慢慢分析。但是我们在进程之外怎么能获得进程的数据呢?除了系统本身提供的一些命令外,这里想说的是一种更复杂但更强大的技术。操作系统和我们写的业务代码一样,都是一些代码的逻辑,我们在写代码时,经常会用到钩子或者劫持的技术。同样,操作系统也不例子,但是操作系统为了提供这种技术,实现上复杂得多。这种技术就是 ebpf,ebpf 是把用户写的代码注入到内核中,内核有一个虚拟机,满足条件的时候就会执行我们的代码。

操作系统提供了钩子机制,比如我们可以注册一个钩子到系统,当系统收到网络包时,就会回调我们。另外一种就是劫持,比如 kprobe 到实现,当我们写一段代码指示操作系统当有人调用 x 的时候回调我们,操作系统就会把这个地址对应的指令改成 int3(x86 架构),然后执行到 x 这个函数的时候,就会触发 int3 中断,对应的处理函数就会执行我们注册的回调,然后再执行真正的函数。很多技术都依赖 ebpf,比如 tcpdump。ebpf 厉害之处在于内核编程可编程的了,真正情况下,我们可以通过基于 ebpf 的工具,从内核中查到非常多的信息,以帮助我们排查问题。ebpf 非常流行,也非常复杂,就不讨论太多,大家可以自行查阅相关信息。

 

责任编辑:武晓燕 来源: 编程杂技
相关推荐

2021-06-01 07:55:42

DockerEOFk8s

2020-05-09 13:49:00

内存空间垃圾

2018-11-06 12:12:00

MySQL内存排查

2023-04-25 18:54:13

数据数据丢失

2022-05-08 09:11:44

WiFi树莓派GO

2021-08-24 08:01:15

死锁工具多线编程

2015-09-21 09:10:36

排查修复Windows 10

2021-12-01 15:03:56

Java开发代码

2022-01-26 19:42:05

MySQL乱码排查

2021-07-30 05:12:54

智能指针C++编程语言

2024-02-21 08:19:54

2021-11-09 06:55:03

SQLServer排序

2022-02-08 17:17:27

内存泄漏排查

2015-07-20 10:23:24

NET内存问题排查

2021-06-30 13:57:07

Arthas JVMTI

2023-02-10 21:18:10

IO测试磁盘

2024-03-15 10:05:13

Kubernetes容器云原生

2023-12-26 11:39:50

CPU系统进程

2022-03-11 10:21:30

IO系统日志

2021-06-28 08:00:00

Python开发编程语言
点赞
收藏

51CTO技术栈公众号