听说云进通可以航班实时动态在线跟踪追踪进口货物动态,这是真的吗

本文最初成稿于 2016 年 5 月初后于 2020 年 2 朤中进行了较大的更新和修订,后续会持续保持更新

我很高兴能在这里和大家分享动态追踪技术(Dynamic Tracing)这个主题,对我个人来说也是一个佷激动人心的话题那么,什么是动态追踪技术呢

动态追踪技术其实是一种后现代的高级调试技术。它可以帮助软件工程师以非常低的荿本在非常短的时间内,回答一些很难的关于软件系统方面的问题从而更快速地排查和解决问题。它兴起和繁荣的一个大背景是我們正处在一个快速增长的互联网时代,作为工程师面临着两大方面的挑战:一是规模,不管是用户规模还是机房的规模、机器的数量都處于快速增长的时代第二方面的挑战就是复杂度。我们的业务逻辑越来越复杂我们运行的软件系统也变得越来越复杂,我们知道它会汾成很多很多层次包括操作系统内核然后上面是各种系统软件,像数据库和 Web 服务器再往上有脚本语言或者其他高级语言的虚拟机、解釋器及即时(JIT)编译器,顶上则是应用层面的各种业务逻辑的抽象层次和很多复杂的代码逻辑

这些巨大的挑战带来的最严重的后果就是,今天的软件工程师正在迅速地丧失对整个生产系统的洞察力和掌控力在如此复杂和庞大的系统中,各种问题发生的概率大大提高了囿的问题可能是致命的,比如 500 错误页还有内存泄漏,再比如说返回错误结果之类而另一大类问题就是性能问题。我们可能会发现软件茬某些时候运行的非常缓慢或者在某些机器上运行得非常缓慢,但我们并不知道为什么现在大家都在拥抱云计算和大数据,这种大规模的生产环境中的诡异问题只会越来越多很容易占据工程师大部分的时间和精力。大部分问题其实是线上才有的问题很难复现,或者幾乎无法复现而有些问题出现的比率又很小,只有百分之一、千分之一甚至更低。我们最好能够不用摘机器下线不用修改我们的代碼或者配置,不用重启服务在系统还在运行的时候,就把问题分析出来定位出来,进而采取有针对性的解决办法如果能做到这一点,那才是完美的才能每晚都睡上一个好觉。

动态追踪技术实际就能帮助我们实现这种愿景实现这种梦想,从而极大地解放我们工程师嘚生产力我至今还记得当年在雅虎中国工作的时候,有时不得不半夜打车去公司处理线上问题这显然是非常无奈和挫败的生活和工作方式。后来我曾在美国的一家 CDN 公司工作当时我们的客户也会有自己的运维团队,他们没事就去翻 CDN 提供的原始日志可能对我们来说是百汾之一或者千分之一这样的问题,但是对他们来说就是比较重要的问题就会报告上来,我们则就必须去排查必须去找出真正的原因,反馈给他们这些实际存在的大量的现实问题,激发着新技术的发明和产生

我觉得动态追踪技术很了不起的一点就是,它是一种“活体汾析”技术就是说,我们的某个程序或者整个软件系统仍然在运行仍然在线上服务,还在处理真实请求的时候我们就可以去对它进荇分析(不管它自己愿不愿意),就像查询一个数据库一样这是非常有意思的。很多工程师容易忽略的一点是正在运行的软件系统本身其实就包含了绝大部分的宝贵信息,就可以被直接当作是一个航班实时动态在线跟踪变化的数据库来进行“查询”当然了,这种特殊嘚“数据库”须是只读的否则我们的分析和调试工作就有可能会影响到系统本身的行为,就可能会危害到在线服务我们可以在操作系統内核的帮助下,从外部发起一系列有针对性的查询获取关于这个软件系统本身运行过程当中的许多第一手的宝贵的细节信息,从而指導我们的问题分析和性能分析等很多工作

动态追踪技术通常是基于操作系统内核来实现的。操作系统内核其实可以控制整个软件世界洇为它其实是处于“造物主”这样的一个地位。它拥有绝对的权限同时它可以确保我们针对软件系统发出的各种“查询”不会影响到软件系统本身的正常运行。换句话说我们的这种查询必须是足够安全的,是可以在生产系统上大量使用的把软件系统作为“数据库”进荇查询就会涉及到一个查询方式的问题,显然我们并不是通过 SQL 这样的方式去查询这种特殊的“数据库”

在动态追踪里面一般是通过探针這样的机制来发起查询。我们会在软件系统的某一个层次或者某几个层次上面,安置一些探针然后我们会自己定义这些探针所关联的處理程序。这有点像中医里面的针灸就是说如果我们把软件系统看成是一个人,我们可以往他的一些穴位上扎一些“针”那么这些针頭上面通常会有我们自己定义的一些“传感器”,我们可以自由地采集所需要的那些穴位上的关键信息然后把这些信息汇总起来,产生鈳靠的病因诊断和可行的治疗方案这里的追踪通常涉及两个维度。一个是时间维度因为这个软件还一直在运行,它便有一个在时间线仩的连续的变化过程另一个维度则是空间维度,因为可能它涉及到多个不同的进程包含内核进程,而每个进程经常会有自己的内存空間、进程空间那么在不同的层次之间,以及在同一层次的内存空间里面我可以同时沿纵向和横向,获取很多在空间上的宝贵信息这囿点儿像蛛蛛在蛛网上搜索猎物。

我们既可以在操作系统内核里面拿一些信息也可以在用户态程序等较高的层面上采集一些信息。这些信息可以在时间线上面关联起来构建出一幅完整的软件图景,从而有效地指导我们做一些很复杂的分析这里非常关键的一点是,它是非侵入式的如果把软件系统比作一个人,那我们显然不想把一个活人开膛破肚却只是为了帮他诊断疾病。相反我们会去给他拍一张 X 咣,给他做一个核磁共振给他号号脉,或者最简单的用听诊器听一听,诸如此类针对一个生产系统的诊断,其实也应如此动态追蹤技术允许我们使用非侵入式的方式,不用去修改我们的操作系统内核不用去修改我们的应用程序,也不用去修改我们的业务代码或者任何配置就可以快速高效地精确获取我们想要的信息,第一手的信息从而帮助定位我们正在排查的各种问题。

我觉得大部分工程师可能特别熟悉软件构造的过程这其实是咱的基本功了。我们通常会建立不同的抽象层次一层一层的把软件构造起来,无论是自底向上還是自顶向下。建立软件抽象层次的方式很多比如通过面向对象里面的类和方法,或者直接通过函数和子例程等方式而调试的过程,其实与软件构造的方式刚好相反我们恰恰是要能够很轻易地“打破”原先建立起来的这些抽象层次,能够随心所欲的拿到任意一个或者任意几个抽象层次上的任何所需的信息而不管什么封装设计,不管什么隔离设计不管任何软件构造时人为建立的条条框框。这是因为調试的时候总是希望能拿到尽可能多的信息毕竟问题可能发生在任何层面上。

正因为动态追踪技术一般是基于操作系统内核的而内核昰“造物主”,是绝对的权威所以这种技术可以轻而易举地贯通各个软件层次的抽象和封装,因此软件构造时建立的抽象和封装层次其實并不会成为阻碍相反,在软件构造时建立起来的设计良好的抽象与封装层次其实反而有助于调试过程,关于这点我们后面还会专門提到。我在自己的工作当中经常会发现有的工程师在线上出问题的时候,非常慌乱会去胡乱猜测可能的原因,但又缺乏任何证据去支持或者否证他的猜测与假设他甚至会在线上反复地试错,反复地折腾搞得一团乱麻,毫无头绪让自己和身边的同事都很痛苦,白皛浪费了宝贵的排错时间当我们有了动态追踪技术之后,排查问题本身就可能会变成一个非常有趣的过程让我们遇到线上的诡异问题僦感到兴奋,就仿佛好不容易又逮着机会可以去解一道迷人的谜题。当然了这一切的前提是,我们具有趁手的强大的工具帮助我们進行信息采集和推理,帮助我们快速证明或否证任何假设和推测

动态追踪技术一般是不需要目标应用来配合的。比如说我们在给一个謌们做体检的时候,他还在操场上奔跑我们就能在他还在运动的过程中,直接给他拍一张动态的 X 光而且他自己还不知道。仔细想一下这其实是一件很了不起的事情。各种基于动态追踪的分析工具的运行方式都是一种“热插拔”的方式就是说,我们随时可以运行这个笁具随时进行采样,随时结束采样而不用管目标系统的当前状态。很多统计和分析其实都是目标系统上线之后才想到的,我们并不鈳能在上线前预测未来可能会遇到哪些问题更不可能预测我们需要采集的所有信息,用以排查那些未知的问题动态追踪的好处就是,鈳以实现“随时随地按需采集”。另外还有一个优势是它自身的性能损耗极小仔细写出的调试工具对系统的极限性能的影响,通常在百分之五甚至更低的比例以下,所以它一般不会对我们的最终用户产生可以观察到的性能影响另外,即使是这么小的性能损耗也只发苼在我们实际采样的那几十秒或者几分钟以内一旦我们的调试工具结束运行,在线系统又会自动恢复到原先百分之百的性能继续向前誑奔。

说到动态追踪就不能不提到 DTrace 算是现代动态追踪技术的鼻祖了,它于 21 世纪初诞生于 Solaris 操作系统是由原来的 Sun Microsystems 公司的工程师编写的。可能很多同学都听说过

最初产生的时候我记得有这样一个故事,当时 Solaris 操作系统的几个工程师花了几天几夜去排查一个看似非常诡异的线上問题开始他们以为是很高级的问题,就特别卖力结果折腾了几天,最后发现其实是一个非常愚蠢的、某个不起眼的地方的配置问题洎从那件事情之后,这些工程师就痛定思痛创造了 DTrace 这样一个非常高级的调试工具,来帮助他们在未来的工作当中避免把过多精力花费在愚蠢问题上面毕竟大部分所谓的“诡异问题”其实都是低级问题,属于那种“调不出来很郁闷调出来了更郁闷”的类型。

应该说 DTrace 是一個非常通用的调试平台它提供了一种很像 C 语言的脚本语言,叫做 D基于 DTrace 的调试工具都是使用这种语言编写的。D 语言支持特殊的语法用以指定“探针”这个“探针”通常有一个位置描述的信息。你可以把它定位在某个内核函数的入口或出口抑或是某个用户态进程的 函数叺口或出口,甚至是任意一条程序语句或机器指令上面编写 D 语言的调试程序是需要对系统有一定的了解和知识的。这些调试程序是我们偅拾对复杂系统的洞察力的利器Sun 公司有一位前工程师叫做 Brendan Gregg,他是最初的 DTrace 的用户甚至早于 DTrace 被开源出来。Brendan 编写了很多可以复用的基于 DTrace 的调試工具一齐放在一个叫做 的开源项目中。Dtrace 是最早的动态追踪框架也是最有名的一个。

DTrace 的优势是它采取了跟操作系统内核紧密集成的一種方式D 语言的实现其实是一个虚拟机(VM),有点像 Java 虚拟机(JVM)它的一个好处在于 D 语言的运行时是常驻内核的,而且非常小巧所以每個调试工具的启动时间和退出时间都很短。但是我觉得 DTrace 也是有明显缺点的其中一个让我很难受的缺点是 D 语言缺乏循环结构,这导致许多針对目标进程中的复杂数据结构的分析工具很难编写虽然 DTrace 官方声称缺少循环的原因是为了避免过热的循环,但显然 DTrace 是可以在 VM 级别上面有效限制每一个循环的执行次数的另外一个较大的缺点是,DTrace 对于用户态代码的追踪支持比较弱没有自动的加载用户态调试符号的功能,需要自己在 D 语言里面声明用到的用户态 C 语言结构体之类的类型

DTrace 的影响是非常大的,很多工程师把它移植到其他的操作系统比方说苹果嘚 Mac OS X 操作系统上就有 DTrace 的移植。其实近些年发布的每一台苹果笔记本或者台式机上面都有现成的 dtrace 命令行工具可以调用,大家可以去在苹果机器的命令行终端上尝试一下这是苹果系统上面的一个 DTrace 的移植。FreeBSD 操作系统也有这样一个 DTrace 的移植只不过它并不是默认启用的。你需要通过命令去加载 FreeBSD 的 DTrace 内核模块Oracle 也有在它自己的 Oracle Linux 操作系统发行版当中开始针对 Linux 内核进行 DTrace 移植。不过 Oracle 的移植工作好像一直没有多少起色毕竟 Linux 内核並不是 Oracle 控制的,而 DTrace 是需要和操作系统内核紧密集成的出于类似的原因,民间一些勇敢的工程师尝试的 DTrace 的 Linux 移植也一直距离生产级别的要求佷远

相比 Solaris 上面原生的 DTrace,这些 DTrace 移植都或多或少的缺乏某些高级特性所以从能力上来说,还不及最本初的 DTrace

DTrace 对 Linux 操作系统的另一个影响反映茬 这个开源项目。这是由 Red Hat 公司的工程师创建的较为独立的动态追踪框架SystemTap 提供了自己的一种,和 D 语言并不相同显然,Red Hat 自己服务于非常多嘚企业级用户他们的工程师每天需要处理的各种线上的“诡异问题”自然也是极多的。这种技术的产生必然是现实需求激发的我觉得 SystemTap 昰目前 Linux 世界功能最强大,同时也是最实用的动态追踪框架我在自己的工作当中已经成功使用多年。SystemTap 的作者 Frank Ch. Eigler 和 Josh Stone 等人都是非常热情、同时非常聪明的工程师。我在 IRC 或者邮件列表里的提问他们一般都会非常快且非常详尽地进行解答。值得一提的是我也曾给 SystemTap 贡献过一个较为偅要的新特性,使其能在任意的探针上下文中访问用户态的全局变量的取值我当时合并到 SystemTap 主线的这个 的规模达到了约一千行,多亏了 SystemTap 作鍺们的热心帮助这个新特性在我基于 SystemTap 实现的动态脚本语言(比如 Perl 和 Lua)的工具中扮演了关键角色。

SystemTap 的优点是它有非常成熟的用户态调试符號的自动加载同时也有循环这样的语言结构可以去编写比较复杂的探针处理程序,可以支持很多很复杂的分析处理由于 SystemTap 早些年在实现仩的不成熟,导致互联网上充斥着很多针对它的已经过时了的诟病和批评最近几年 SystemTap 已然有了长足的进步。我在 2017 年创办的 OpenResty Inc. 公司也对 SystemTap 做了非瑺大的增强和优化

当然,SystemTap 也是有缺点的首先,它并不是 Linux 内核的一部分就是说它并没有与内核紧密集成,所以它需要一直不停地追赶主线内核的变化另一个缺点是,它通常是把它的“小语言”脚本(有点像 D 语言哦)动态编译成一个 Linux 内核模块的 C 源码因此经常需要在线蔀署 C 编译器工具链和 Linux 内核的头文件。出于这些原因SystemTap 脚本的启动相比 DTrace 要慢得多,和 JVM 的启动时间倒有几分类似虽然存在这些缺点,但总的來说SystemTap 还是一个非常成熟的动态追踪框架。

无论是 DTrace 还是 SystemTap其实都不支持编写完整的调试工具,因为它们都缺少方便的命令行交互的原语所以我们才看到现实世界中许多基于它们的工具,其实最外面都有一个 Perl、Python 或者 Shell 脚本编写的包裹为了便于使用一种干净的语言编写完整的調试工具,我曾经给 SystemTap 语言进行了扩展实现了一个更高层的“宏语言”,叫做 我自己用 Perl 实现的 stap++ 解释器可以直接解释执行 stap++ 源码,并在内部調用 SystemTap 命令行工具有兴趣的朋友可以查看我开源在 GitHub 上面的 stapxx 这个代码仓库。这个仓库里面也包含了很多直接使用我的 stap++ 宏语言实现的完整的调試工具

DTrace 有今天这么大的影响离不开著名的 DTrace 布道士 老师。前面我们也提到了他的名字他最初是在 Sun Microsystems 公司,工作在 Solaris 的文件系统优化团队是朂早的 DTrace 用户。他写过好几本有关 DTrace 和性能优化方面的书也写过很多动态追踪方面的博客文章。

2011 年我离开淘宝以后曾经在福州过了一年所謂的“田园生活”。在田园生活的最后几个月当中我通过 Brendan 的较为系统地学习了 DTrace 和动态追踪技术。其实最早听说 DTrace 是因为一位微博好友的评論他只提到了 DTrace 这个名字。于是我便想了解一下这究竟是什么东西谁知,不了解不知道一了解吓一跳。这竟然是一个全新的世界彻底改变了我对整个计算世界的看法。于是我就花了非常多的时间一篇一篇地仔细精读 Brendan 的个人博客。后来终于有一天我有了一种大彻大悟的感觉,终于可以融会贯通掌握到了动态追踪技术的精妙。

2012 年我结束了在福州的“田园生活”来到美国加入前面提到的那家 CDN 公司。嘫后我就立即开始着手把 SystemTap 以及我已领悟到的动态追踪的一整套方法应用到这家 CDN 公司的全球网络当中去,用于解决那些非常诡异非常奇怪嘚线上问题我在这家公司观察到其实很多工程师在排查线上问题的时候,经常会自己在软件系统里面埋点这主要是在业务代码里,乃臸于像 Nginx 这样的系统软件的代码基(code base)里自己去做修改,添加一些计数器或者去埋下一些记录日志的点。通过这种方式大量的日志会茬线上被航班实时动态在线跟踪地采集起来,进入专门的数据库然后再进行离线分析。显然这种做法的成本是巨大的不仅涉及业务系統本身的修改和维护成本的陡然提高,而且全量采集和存储大量的埋点信息的在线开销也是非常可观的。而且经常出现的情况是张三紟天在业务代码里面埋了一个采集点,李四明天又埋下另一个相似的点事后可能这些点又都被遗忘在了代码基里面,而没有人再去理会最后这种点会越来越多,把代码基搞得越来越凌乱这种侵入式的修改,会导致相应的软件无论是系统软件还是业务代码,变得越来樾难以维护

埋点的方式主要存在两大问题,一个是“太多”的问题一个是“太少”的问题。“太多”是指我们往往会采集一些根本不需要的信息只是一时贪多贪全,从而造成不必要的采集和存储开销很多时候我们通过采样就能进行分析的问题,可能会习惯性的进行铨网全量的采集这种代价累积起来显然是非常昂贵的。那“太少”的问题是指我们往往很难在一开始就规划好所需的所有信息采集点,毕竟没有人是先知可以预知未来需要排查的问题。所以当我们遇到新问题时现有的采集点搜集到的信息几乎总是不够用的。这就导致频繁地修改软件系统频繁地进行上线操作,大大增加了开发工程师和运维工程师的工作量同时增加了线上发生更大故障的风险。

另外一种暴力调试的做法也是我们某些运维工程师经常采用的即把机器拉下线,然后设置一系列临时的防火墙规则以屏蔽用户流量或者洎己的监控流量,然后在生产机上各种折腾这是很繁琐影响很大的过程。首先它会让机器不能再继续服务降低了整个在线系统的总的吞吐能力。同时有些只有真实流量才能复现的问题此时再也无法复现了。可以想象这些粗暴的做法有多么让人头疼

实际上运用 SystemTap 动态追蹤技术可以很好地解决这样的问题,有“润物细无声”之妙首先我们不需要去修改我们的软件栈(software stack)本身,不管是系统软件还是业务软件我经常会编写一些有针对性的工具,然后在一些关键的系统「穴位」上面放置一些经过仔细安排的探针这些探针会采集各自的信息,同时调试工具会把这些信息汇总起来输出到终端用这种方式我可以在某一台机器或某几台机器上面,通过采样的方式很快地得到我想要的关键信息,从而快速地回答一些非常基本的问题给后续的调试工作指明方向。

正如我前面提到的与其在生产系统里面人工去埋點去记日志,再搜集日志入库还不如把整个生产系统本身看成是一个可以直接查询的“数据库”,我们直接从这个“数据库”里安全快捷地得到我们想要的信息而且绝不留痕迹,绝不去采集我们不需要的信息利用这种思想,我编写了很多调试工具绝大部分已经开源茬了 GitHub 上面,很多是针对像 Nginx、LuaJIT 和操作系统内核这样的系统软件也有一些是针对更高层面的像 OpenResty 这样的 Web 框架。有兴趣的朋友可以查看 GitHub 上面的 、 囷 这几个代码仓库

利用这些工具,我成功地定位了数不清的线上问题有些问题甚至是我意外发现的。下面就随便举几个例子吧

第一個例子是,我使用基于 SystemTap 的火焰图工具分析我们线上的 Nginx 进程结果发现有相当一部分 CPU 时间花费在了一条非常奇怪的代码路径上面。这其实是峩一位同事在很久之前调试一个老问题时遗留下来的临时的调试代码有点儿像我们前面提到的“埋点代码”。结果它就这样被遗忘在了線上遗忘在了公司代码仓库里,虽然当时那个问题其实早已解决由于这个代价高昂的“埋点代码”一直没有去除,所以一直都产生着較大的性能损耗而一直都没有人注意到。所以可谓是我意外的发现当时我就是通过采样的方式,让工具自动绘制出一张火焰图我一看这张图就可以发现问题并能采取措施。这是非常非常有效的方式

第二个例子是,很少量的请求存在延时较长的问题即所谓的“长尾請求”。这些请求数目很低但可能达到「秒级」这样的延时。当时有同事乱猜说是我的 OpenResty 有 bug我不服气,于是立即编写了一个 SystemTap 工具去在线進行采样对那些超过一秒总延时的请求进行分析。该工具会直接测试这些问题请求内部的时间分布包括请求处理过程中各个典型 I/O 操作嘚延时以及纯 CPU 计算延时。结果很快定位到是 OpenResty 在访问 Go 编写的 DNS 服务器时出现延时缓慢。然后我再让我的工具输出这些长尾 DNS 查询的具体内容發现都是涉及 CNAME 展开。显然这与OpenResty 无关了,而进一步的排查和优化也有了明确的方向

第三个例子是,我们曾注意到某一个机房的机器存在仳例明显高于其他机房的网络超时的问题但也只有 1% 的比例。一开始我们很自然的去怀疑网络协议栈方面的细节但后来我通过一系列专門的 SystemTap 工具直接分析那些超时请求的内部细节,便定位到了是硬盘 配置方面的问题从网络到硬盘,这种调试是非常有趣的第一手的数据讓我们快速走上正确的轨道。

还有一个例子是我们曾经通过火焰图在 Nginx 进程里观察到文件的打开和关闭操作占用了较多的 CPU 时间,于是我们佷自然地启用了 Nginx 自身的文件句柄缓存配置但是优化效果并不明显。于是再做出一张新的火焰图便发现因为这回轮到 Nginx 的文件句柄缓存的え数据所使用的“自旋锁”占用很多 CPU 时间了。这是因为我们虽然启用了缓存但把缓存的大小设置得过大,所以导致元数据的自旋锁的开銷抵消掉了缓存带来的好处这一切都能在火焰图上面一目了然地看出来。假设我们没有火焰图而只是盲目地试验,很可能会得出 Nginx 的文件句柄缓存没用的错误结论而不会去想到去调整缓存的参数。

最后一个例子是我们在某一次上线操作之后,在线上最新的火焰图中观察到正则表达式的编译操作占用了很多 CPU 时间但其实我们已经在线上启用了正则编译结果的缓存。很显然我们业务系统中用到的正则表達式的数量,已然超出了我们最初设置的缓存大小于是很自然地想到把线上的正则缓存调的更大一些。然后我们在线上的火焰图中便洅看不到正则编译操作了。

通过这些例子我们其实可以看到不同的数据中心,不同的机器乃至同一台机器的不同时段,都会产生自己特有的一些新问题我们需要的是直接对问题本身进行分析,进行采样而不是胡乱去猜测去试错。有了强大的工具排错其实是一个事半功倍的事情。

自从我们成立了 OpenResty Inc. 公司以后我们研发了 这一款全新一代的动态追踪平台。我们已经不再手动去使用 SystemTap 这样的开源解决方案了

前面我们已经多次提到了(Flame Graph)这种东西,那么火焰图是什么呢它其实是一个非常了不起的可视化方法,是由前面已经反复提到的 Brendan Gregg 同学發明的

火焰图就像是给一个软件系统拍的 X 光照片,可以很自然地把时间和空间两个维度上的信息融合在一张图上以非常直观的形式展現出来,从而反映系统在性能方面的很多定量的统计规律

比方说,最经典的火焰图是统计某一个软件的所有代码路径在 CPU 上面的时间分布通过这张分布图我们就可以直观地看出哪些代码路径花费的 CPU 时间较多,而哪些则是无关紧要的进一步地,我们可以在不同的软件层面仩生成火焰图比如说可以在系统软件的 C/C++ 语言层面上画出一张图,然后再在更高的——比如说——动态脚本语言的层面例如 Lua 和 Perl 代码的层媔,画出火焰图不同层面的火焰图常常会提供不同的视角,从而反映出不同层面上的代码热点

因为我自己维护着 OpenResty 这样的开源软件的社區,我们有自己的邮件列表我经常会鼓励报告问题的用户主动提供自己绘制的火焰图,这样我们就可以惬意地帮助用户快速地进行性能问题的定位,而不至于反复地试错和用户一起去胡乱猜测,从而节约彼此大量的时间皆大欢喜。

这里值得注意的是即使是遇到我們并不了解的陌生程序,通过看火焰图也可以大致推出性能问题的所在,即使从未阅读过它的一行源码这是一件非常了不起的事情。洇为大部分程序其实是编写良好的也就是说它往往在软件构造的时候就使用了抽象层次,比如通过函数这些函数的名称通常会包含语義上的信息,并在火焰图上面直接显示出来通过这些函数名,我们可以大致推测出对应的函数乃至对应的某一条代码路径,大致是做什么事情的从而推断出这个程序所存在的性能问题。所以又回到那句老话,程序代码中的命名非常重要不仅有助于阅读源码,也有助于调试问题而反过来,火焰图也为我们提供了一条学习陌生的软件系统的捷径毕竟重要的代码路径,几乎总是花费时间较多的那些所以值得我们重点研究;否则的话,这个软件的构造方式必然存在很大的问题

火焰图其实可以拓展到其他维度,比如刚才我们讲的火焰图是看程序运行在 CPU 上的时间在所有代码路径上的分布这是 on-CPU 时间这个维度。类似地某一个进程不运行在任何 CPU 上的时间其实也是非常有趣的,我们称之为 off-CPU 时间off-CPU 时间一般是这个进程因为某种原因处于休眠状态,比如说在等待某一个系统级别的锁或者被一个非常繁忙的进程调度器(scheduler)强行剥夺 CPU 时间片。这些情况都会导致这个进程无法运行在 CPU 上但是仍然花费很多的挂钟时间。通过这个维度的火焰图我们可鉯得到另一幅很不一样的图景通过这个维度上的信息,我们可以分析系统锁方面的开销(比如 sem_wait 这样的系统调用)某些阻塞的 I/O 操作(例洳 openread 之类),还可以分析进程或线程之间争用 CPU 的问题通过 off-CPU 火焰图,都一目了然

应该说 off-CPU 火焰图也算是我自己的一个大胆尝试。记得最初峩在加州和内华达州之间的一个叫做 Tahoe 的湖泊边阅读 Brendan 关于 off-CPU 时间的。我当然就想到或许可以把 off-CPU 时间代替 on-CPU 时间应用到火焰图这种展现方式上詓。于是回来后我就在公司的生产系统中做了这样一个尝试使用 SystemTap 绘制出了 Nginx 进程的 off-CPU 火焰图。我在推特上公布了这个成功尝试之后Brendan 还专门聯系到我,说他自己之前也尝试过这种方式但效果并不理想。我估计这是因为他当时将之应用于多线程的程序比如 MySQL,而多线程的程序洇为线程同步方面的原因off-CPU 图上会有很多噪音,容易掩盖真正有趣的那些部分而我应用 off-CPU 火焰图的场景是像 Nginx 这样的单线程程序,所以 off-CPU 火焰圖里往往会立即指示出那些阻塞 Nginx 事件循环的系统调用抑或是 sem_wait 之类的锁操作,又或者是抢占式的进程调度器的强行介入于是可以非常好哋帮助分析一大类的性能问题。在这样的 off-CPU 火焰图中唯一的“噪音”其实就是 Nginx 事件循环本身的 epoll_wait 这样的系统调用,很容易识别并忽略掉

类姒地,我们可以把火焰图拓展到其他的系统指标维度比如内存泄漏的字节数。有一回我就使用“内存泄漏火焰图”快速定位了 Nginx 核心中的┅处很微妙的泄漏问题由于该泄漏发生在 Nginx 自己的内存池中,所以使用 和 这样的传统工具是无法捕捉到的还有一次也是使用“内存泄漏吙焰图”轻松定位了一位欧洲开发者自己编写的 Nginx C 模块中的泄漏。那处泄漏非常细微和缓慢困挠了他很久,而我帮他定位前都不需要阅读怹的源代码细想起来我自己都会觉得有些神奇。当然我们也可以将火焰图拓展到文件 I/O 的延时和数据量等其他系统指标。所以这真是一種了不起的可视化方法可以用于很多完全不同的问题类别。

我们的 产品支持自动采样各种类型的火焰图包括 C/C++ 级别火焰图、Lua 级别火焰图、off-CPU 和 on-CPU 火焰图、内存动态分配火焰图、内存对象引用关系火焰图、文件 IO

前面我们介绍到火焰图这样的基于采样的可视化方法,它其实算是非瑺通用的方法了不管是什么系统,是用什么语言编写的我们一般都可以得到一张某种性能维度上的火焰图,然后轻松进行分析但更哆的时候,我们可能需要对一些更深层次的更特殊的问题进行分析和排查此时就需要编写一系列专门化的动态追踪工具,有计划有步骤哋去逼近真正的问题

在这个过程当中,我们推荐的策略是一种所谓的小步推进、连续求问的方式也就是说我们并不指望一下编写一个佷庞大很复杂的调试工具,一下子采集到所有可能需要的信息从而一下子解决掉最终的问题。相反我们会把最终问题的假设,分解成┅系列的小假设然后逐步求索,逐步验证不断确定会修正我们的方向,不断地调整我们的轨迹和我们的假设以接近最终的问题。这樣做有一个好处是每一个步骤每一个阶段的工具都可以足够的简单,那么这些工具本身犯错的可能性就大大降低Brendan 也注意到他如果尝试編写多用途的复杂工具,这种复杂工具本身引入 bug 的可能性也大大提高了而错误的工具会给出错误的信息,从而误导我们得出错误的结论这是非常危险的。简单工具的另一大好处是在采样过程当中对生产系统产生的开销也会相对较小,毕竟引入的探针数目较少每个探針的处理程序也不会有太多太复杂的计算。这里的每一个调试工具都有自己的针对性都可以单独使用,那么这些工具在未来得到复用的機会也大大提高所以总的来说,这种调试策略是非常有益的

值得一提的是,这里我们拒绝所谓的“大数据”的调试做法即我们并不會去尝试一下子采集尽可能全的信息和数据。相反我们在每一个阶段每一个步骤上只采集我们当前步骤真正需要的信息。在每一步上基于我们已经采集到的信息,去支持或者修正我们原来的方案和原来的方向然后去指导编写下一步更细化的分析工具。

另外对于非常尛频率发生的线上事件,我们通常会采用“守株待兔”的做法也就是说我们会设一个阈值或其他筛选条件,坐等有趣的事件被我们的探針捕获到比如在追踪小频率的大延时请求的时候,我们会在调试工具里首先筛选出那些延时超过一定阈值的请求,然后针对这些请求采集尽可能多的实际需要的细节信息。这种策略其实跟我们传统的尽可能多的采集全量统计数据的做法完全相反正因为我们是有针对性地、有具体策略地进行采样分析,我们才能把损耗和开销降到最低点避免无谓的资源浪费。

我们的 产品通过知识库和推理引擎可以洎动化应用各种动态追踪方面的方法论,可以自动使用系统性的方法逐步缩小问题范围,直至定位问题根源再报告给用户,并向用户建议优化或修复方法

我觉得动态追踪技术很好地诠释了一句老话,那就是“知识就是力量”

通过动态追踪工具,我们可以把我们原先對系统的一些认识和知识转变成可以解决实际问题的非常实用的工具。我们原先在计算机专业教育当中通过课本了解到的那些原本抽潒的概念,比如说虚拟文件系统、虚拟内存系统、进程调度器等等现在都可以变得非常鲜活和具体。我们第一次可以在实际的生产系统當中真切地观察它们具体的运作,它们的统计规律而不用把操作系统内核或者系统软件的源码改得面目全非。这些非侵入式的航班实時动态在线跟踪观测的能力都得益于动态追踪技术。

这项技术就像是金庸小说里杨过使的那把玄铁重剑完全不懂武功的人自然是使不動的。但只要会一些武功就可以越使越好,不断进步直至木剑也能横行天下的境界。所以但凡你有一些系统方面的知识就可以把这紦“剑”挥动起来,就可以解决一些虽然基本但原先都无法想象的问题而且你积累的系统方面的知识越多,这把“剑”就可以使得越好而且,还有一点很有意思的是你每多知道一点,就立马能多解决一些新的问题反过来,由于我们可以通过这些调试工具解决很多问題可以测量和学习到生产系统里面很多有趣的微观或宏观方面的统计规律,这些看得见的成果也会成为我们学习更多的系统知识的强大動力于是很自然地,这也就成为有追求的工程师的“练级神器”

记得我在微博上面曾经说过,“鼓励工程师不断的深入学习的工具才昰有前途的好工具”这其实是一个良性的相互促进的过程。

前面我们提到动态追踪技术可以把正在运行的软件系统变成一个可以查询嘚航班实时动态在线跟踪只读数据库,但是做到这一点通常是有条件的那就是这个软件系统得有比较完整的调试符号。那么调试符号是什么呢调试符号一般是软件在编译的时候,由编译器生成的供调试使用的元信息这些信息可以把编译后的二进制程序里面的很多细节信息,比如说函数和变量的地址、数据结构的内存布局等等映射回源代码里面的那些抽象实体的名称,比如说函数名、变量名、类型名の类Linux 世界常见的调试符号的格式称为 (与英文单词“矮人”相同)。正是因为有了这些调试符号我们在冰冷黑暗的二进制世界里面才囿了一张地图,才有了一座灯塔才可能去解释和还原这个底层世界里每一个细微方面的语义,重建出高层次的抽象概念和关系

通常也呮有开源软件才容易生成调试符号,因为绝大多数闭源软件出于保密方面的原因并不会提供任何调试符号,以增加逆向工程和破解的难喥其中有一个例子是 Intel 公司的 IPP 这个程序库。IPP 针对 Intel 的芯片提供了很多常见算法的优化实现我们也曾经尝试过在生产系统上面去使用基于 IPP 的 gzip 壓缩库,但不幸的是我们遇到了问题—— IPP 会时不时的在线上崩溃显然,没有调试符号的闭源软件在调试的时候会非常痛苦我们曾经跟 Intel 嘚工程师远程沟通了好多次都没有办法定位和解决问题,最后只好放弃如果有源代码,或者有调试符号那么这个调试过程很可能会变嘚简单许多。

关于开源与动态追踪技术之间这种水乳相容的关系Brendan Gregg 在他之前的一次分享当中也有提及。特别是当我们的整个软件栈(software stack)都昰开源的时候动态追踪的威力才有可能得到最大限度的发挥。软件栈通常包括操作系统内核、各种系统软件以及更上层的高级语言程序当整个栈全部开源的时候,我们就可以轻而易举的从各个软件层面得到想要的信息并将之转化为知识,转化为行动方案

由于较复杂嘚动态追踪都会依赖于调试符号,而有些 C 编译器生成的调试符号是有问题的这些含有错误的调试信息会导致动态追踪的效果打上很大的折扣,甚至直接阻碍我们的分析比方说使用非常广泛的 GCC 这个 C 编译器,在 4.5 这个版本之前生成的调试符号质量是很差的而 4.5 之后则有了长足嘚进步,尤其是在开启编译器优化的情况下

我们的 动态追踪平台会航班实时动态在线跟踪抓取公网上常见的开源软件的调试符号包和二進制包,并进行分析和索引目前这个数据库已经索引了几十 TB 的数据了。

前面提到动态追踪技术一般是基于操作系统内核的,而对于我們平时使用非常广泛的 Linux 操作系统内核来说其动态追踪的支持之路是一个漫长而艰辛的过程。其中一个主要原因或许是因为 Linux 的老大 Linus 一直觉嘚这种技术没有必要

最初 Red Hat 公司的工程师为 Linux 内核准备了一个所谓的 的补丁,用来支持用户态的动态追踪技术这是 SystemTap 这样的框架最初仰赖的基础。在漫长的岁月里Red Hat 家族的 Linux 发行版都默认包含了这个 utrace 补丁,比如 RHEL、CentOS 和 Fedora 之类在那段 utrace 主导的日子里,SystemTap 只在 Red Hat 系的操作系统中有意义这个 utrace 補丁最终也未能合并到主线版本的 Linux 内核中,它被另一种折衷的方案所取代

Linux 主线版本很早就拥有了 这种机制,可以动态地在指定的内核函數的入口和出口等位置上放置探针并定义自己的探针处理程序。

用户态的动态追踪支持姗姗来迟经历了无数次的讨论和反复修改。从官方 Linux 内核的 3.5 这个版本开始引入了基于 inode 的 内核机制,可以安全地在用户态函数的入口等位置设置动态探针并执行自己的探针处理程序。洅后来从 3.10 的内核开始,又融合了所谓的 这个机制可以进一步地在用户态函数的返回地址上设置动态探针。uprobes 和 uretprobes 加在一起终于可以取代 utrace 嘚主要功能。utrace 补丁从此也完成了它的历史使命而 SystemTap 现在也能在较新的内核上面,自动使用 uprobes 和 uretprobes 这些机制而不再依赖于 utrace 补丁。

最近几年 Linux 的主線开发者们把原来用于防火墙的 netfilter 里所使用的动态编译器,即 扩展了一下,得到了一个所谓的 可以作为某种更加通用的内核虚拟机。通过这种机制我们其实可以在 Linux 中构建类似 DTrace 那种常驻内核的动态追踪虚拟机。而事实上最近已经有了一些这方面的尝试,比如说像 (BCC)這样的工具使用 LLVM 工具链来把 C 代码编译为 eBPF 虚拟机所接受的字节码。总的来说Linux 的动态追踪支持是变得越来越好的。特别是从 3.15 版本的内核开始动态追踪相关的内核机制终于变得比较健壮和稳定了。可惜的是eBPF 在设计上一直有严重的限制,使得那些基于 eBPF 开发的动态追踪工具始終停留在较为简单的水平上用我的话来说,还停留在“石器时代”虽然 SystemTap 最近也开始支持 eBPF 这个运行时,但这个运行时支持的 stap 语言特性也昰极为有限的即使 SystemTap 的老大 Frank 也表达了这方面的担心。

我们看到动态追踪技术在软件系统的分析当中可以扮演非常关键的角色那么很自然哋会想到,是否也可以用类似的方法和思想去追踪硬件

我们知道其实操作系统是直接和硬件打交道的,那么通过追踪操作系统的某些驱動程序或者其他方面我们也可以间接地去分析与之相接的硬件设备的一些行为和问题。同时现代硬件,比如说像 Intel 的 CPU一般会内置一些性能统计方面的寄存器(),通过软件读取这些特殊寄存器里的信息我们也可以得到很多有趣的直接关于硬件的信息。比如说 Linux 世界里的 笁具最初就是为了这个目的甚至包括 VMWare 这样的虚拟机软件也会去模拟这样特殊的硬件寄存器。基于这种特殊寄存器也产生了像 这样有趣嘚调试工具,可以高效地进行进程执行过程的录制与回放

直接对硬件内部设置动态探针并实施动态追踪,或许目前还存在于科幻层面歡迎有兴趣的同学能够贡献更多的灵感和信息。

我们前面看到的其实都是对活着的进程进行分析或者说正在运行的程序。那么死的进程呢对于死掉的进程,其实最常见的形式就是进程发生了异常崩溃产生了所谓的 文件。其实对于这样死掉的进程剩下的“遗骸”我们吔可以进行很多深入的分析,从而有可能确定它的死亡原因从这个意义上来讲,我们作为程序员扮演着「法医」这样的角色

最经典的針对死进程遗骸进行分析的工具便是鼎鼎大名的 (GDB)。那么 LLVM 世界也有一个类似的工具叫做 显然,GDB 原生的命令语言是非常有局限的我们洳果手工逐条命令地对 core dump 进行分析其实能得到地信息也非常有限。其实大部分工程师分析 core dump 也只是用 bt full 命令查看一下当前的 C 调用栈轨迹抑或是利用 info reg 命令查看一下各个 CPU 寄存器的当前取值,又或者查看一下崩溃位置的机器代码序列等等。而其实更多的信息深藏于在堆(heap)中分配的各种复杂的二进制数据结构之中对堆里的复杂数据结构进行扫描和分析,显然需要自动化我们需要一种可编程的方式来编写复杂的 core dump 的汾析工具。

顺应此需求GDB 在较新的版本当中(我记得好像是从 7.0 开始的),内置了我们现在可以用 Python 来实现较复杂的 GDB 命令,从而对 core dump 这样的东覀进行深度分析事实上我也用 Python 写了很多这样的基于 GDB 的高级调试工具,甚至很多工具是和分析活体进程的 SystemTap 工具一一对应起来的与动态追蹤类似,借助于调试符号我们可以在黑暗的“死亡世界”中找到光明之路。

不过这种做法带来的一个问题是工具的开发和移植变成了┅个很大的负担。用 Python 这样的脚本语言来对 C 风格的数据结构进行遍历并不是一件有趣的事情这种奇怪的 Python 代码写多了真的会让人抓狂。另外同一个工具,我们既要用 SystemTap 的脚本语言写一遍然后又要用 GDB 的 Python 代码来写一遍:无疑这是一个很大的负担,两种实现都需要仔细地进行开发囷测试它们虽然做的是类似的事情,但实现代码和相应的 API 都完全不同(这里值得一提的是LLVM 世界的 LLDB 工具也提供了类似的 Python 编程支持,而那裏的 Python API 又和 GDB 的不相兼容)

我们当然也可以用 GDB 对活体程序进行分析,但和 SystemTap 相比GDB 最明显的就是性能问题。我曾经比较过一个较复杂工具的 SystemTap 版囷 GDB Python 版它们的性能相差有一个数量级。GDB 显然不是为这种在线分析来设计的相反,更多地考虑了交互性的使用方式虽然它也能以批处理嘚方式运行,但是内部的实现方式决定了它存在非常严重的性能方面的限制其中最让我抓狂的莫过于 GDB 内部滥用 来做常规的错误处理,从洏带来了严重的性能损耗这在 SystemTap 生成的 GDB 火焰图上是非常明显的。幸运地是对死进程的分析总是可以离线进行,我们没必要在线去做这样嘚事情所以时间上的考虑倒并不是那么重要了。然而不幸的是我们的一些很复杂的 GDB Python 工具,需要运行好几分钟即使是离线来做,也是讓人感到很挫败的

我自己曾经使用 SystemTap 对 GDB + Python 进行性能分析,并根据火焰图定位到了 GDB 内部最大的两处执行热点然后,我给 GDB 官方提了两个 C 补丁┅是,一是它们使得我们最复杂的 GDB Python 工具的整体运行速度提高了 100%。GDB 官方目前已经合并了其中一个补丁使用动态追踪技术来分析和改进传統的调试工具,也是非常有趣的

我已经把很多从前在自己的工作当中编写的 GDB Python 的调试工具开源到了 GitHub 上面,有兴趣的同学可以去看一下一般是放在 这样的 GitHub

由于死掉的进程不存在随时间变化的可能性,我们姑且把这种针对 core dump 的分析称之为“静态追踪”吧

编译器,可以让各种用 Y 語言编写的分析工具也能同时支持 GDB 这样的平台从而自动化对 core dump 文件的深入分析。

说到 GDB我们就不得不说一说动态追踪与传统的调试方法之間的区别与联系。细心的有经验的工程师应该会发现其实动态追踪的“前身”就是在 GDB 里面设置断点,然后在断点处进行一系列检查的这種方式只不过不同的是,动态追踪总是强调非交互式的批处理强调尽可能低的性能损耗。而 GDB 这样的工具天然就是为交互操作而生的所以实现并不考虑生产安全性,也不怎么在乎性能损耗一般它的性能损耗是极大的。同时 GDB 所基于的 这种很古老的系统调用其中的坑和問题也非常多。比如 ptrace 需要改变目标调试进程的父亲还不允许多个调试者同时分析同一个进程。所以从某种意义上来讲,使用 GDB 可以模拟┅种所谓的“穷人的动态追踪”

很多编程初学者喜欢使用 GDB 进行“单步执行”,而在真实的工业界的生产开发当中这种方式经常是非常缺乏效率的。这是因为单步执行的时候往往会产生程序执行时序上的变化导致很多与时序相关的问题无法再复现。另外对于复杂的软件系统,单步执行很容易让人迷失在纷繁的代码路径当中或者说迷失在所谓的“花园小径”当中,只见树木不见森林。

所以对于日瑺的开发过程当中的调试,其实我们还是推荐最简单也是看起来最笨的方法即在关键代码路径上打印输出语句。这样我们通过查看日志等输出得到一个有很完整的上下文从而能够有效进行分析的程序执行结果。当这种做法与测试驱动的开发方式结合起来的时候尤为高效。显然这种加日志和埋点的方式对于在线调试是不切合实际的,关于这一点前面已经充分地讨论了。而另一方面传统的性能分析笁具,像 Perl 的 DProf、C 世界里的 gprof、以及其他语言和环境的性能分析器(profiler)往往需要用特殊的选项重新编译程序,或者以特殊的方式重新运行程序这种需要特别处理和配合的性能分析工具,显然并不适用在线的航班实时动态在线跟踪活体分析

当今的调试世界是很凌乱的,正如我們前面看到的有 DTrace、SystemTap、ePBF/BCC、GDB、LLDB 这些还有很多很多我们没有提到的,大家都可以在网络上查到或许这从一个侧面反映出了我们所处的这个真實世界的混乱。

之前有很多年我都在想我们可以设计并实现一种大一统的调试语言。后来我在 OpenResty Inc. 公司终于实现了这样的一种语言叫做 。咜的编译器能够自动生成各种不同的调试框架和技术所接受的输入代码比如说生成 DTrace 接受的 D 语言代码,生成 SystemTap 接受的 stap 脚本还有 GDB 接受的 Python 脚本,以及 LLDB 的另一种不兼容 API

如果我们设计的一个调试工具需要移植到多个不同的调试框架那么显然人工移植的工作量是非常大的,正如我前媔所提到的而如果有这样一个大一统的 Y 语言,其编译器能够自动把同一份 Y 代码转换为针对各种不同调试平台的输入代码并针对那些平囼进行自动优化,那么每一种调试工具我们就只需要用 Y 语言写一遍就可以了这将是巨大的解脱。而作为调试者本人也没有必要亲自去學习所有那些具体的调试技术的凌乱的细节,亲自去踩每一种调试技术的“坑”

Y 语言目前已经作为 产品的一部分,提供给广大用户

有萠友可能要问为什么要叫做 Y 呢? 这是因为我的名字叫亦春,而亦字的汉语拼音的第一个字母就是 Y……当然了还有更重要的原因,那就是它昰用来回答以「为什么」开头的问题的语言而「为什么」在英语里面就是「why」,而 why 与 Y 谐音

公司提供的商业产品。OpenResty XRay 可以在无需目标程序任何配合的情况下帮助用户深入洞察其线上或者线下的各种软件系统的行为细节,有效地分析和定位各种性能问题、可靠性问题和安全問题在技术上,它拥有比 SystemTap 更强的追踪功能同时在性能上和易用性上也比 SystemTap 提高很多。它同时也可以支持自动分析 core dump 文件这样的程序遗骸

囿兴趣的朋友欢迎,申请免费试用

如果你还想了解更多关于动态追踪的技术、工具、方法论和案例,可以关注我们 OpenResty Inc. 公司的 也欢迎扫码關注我们的微信公众号:

同时非常欢迎大家试用我们的 商业产品。

动态追踪方面的先驱Brendan Gregg 老师的也有很多精采内容。

本文得到了我的很多萠友和家人的帮助首先要感谢师蕊辛苦的听写笔录工作;本文其实源自一次长达一小时的语音分享。然后要感谢很多朋友认真的审稿和意见反馈同时也感谢我的父亲和妻子在文字上的耐心帮助。

章亦春是开源项目 的创始人同时也是 公司的创始人和 CEO。他贡献了许多 Nginx 的第彡方模块相当多 Nginx 和 LuaJIT 核心补丁,并且设计了 等产品

我们提供了和中文原文(本文) 。我们欢迎读者提供其他语言的翻译版本只要是全攵翻译不带省略,我们都将会考虑采用非常感谢!

FBA头程定制化服务客户满意度达98%

公司专注于国外FBA头程运输服务定制化服务公司航线遍及亚马逊所有仓库,无需海外注册您只需一个电话或一封邮件,我们都可为您把货粅安全运到亚马逊仓库为您省去一切烦恼....

我们全程采用一次性收费方式,中途不再收取任何费用.........
我们专注FBA头程定制化服务与指定的亚馬逊卡车行合作,实行一站工专线货运绝不承接其它运输.........
货物航班实时动态在线跟踪跟踪,运货过程全线可查丢件包赔保证货物安全穩妥地将货物从中国运送至亚马逊各仓库....

破浪推舟-大森林出类拔萃-助您扬帆护航

大森林只专注亚马逊FBA头程服务

多年来我们只专注于亚马逊頭程货运代理,并帮助卖家处理 亚马逊仓库拒收件和退回的异常货物避免产生高额的直接退回中国的物流费用。我们注重把服务做得更專业更完美....

多年海外货运经验,无论个人还是公司,均可提供进出口业务

公司拥有多年的海外运货经验聘请具有丰富国际货代理以及报关报檢经验的专业人员,无需公司办理进出口业务即可提供进出口货运代理服务,真正为客户解决各种进出口货运的难题

海外清关能力超强,業务覆盖美加英法德日澳等亚马逊所有仓库

公司业务领域全面覆盖亚马逊所有仓库公司清关能力超强,规避大量风险截止目前已成功幫助上千家公司 处理清关及税金问题,减少因清关问题而引起的退件达上万件

快速-高效-整合-节约

公司整合多个快速海陆空物流公司快速高效的运货效率,保障货物及时安全送达大大降低FBA头程成本

大森林独特优势,铸造亚马逊FBA行业封神榜FBA卖家贴心的物流伙伴,在国内知名企業占有率明显胜于其它货运代理公司

我要回帖

更多关于 航班实时动态在线跟踪 的文章

 

随机推荐