mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-11-16 22:23:45 +08:00
fix img
This commit is contained in:
@@ -201,12 +201,12 @@ function hide_canvas() {
|
||||
<h4>日志(Logging)</h4>
|
||||
<p><strong>日志</strong>是系统中的常见功能,我们前面说的数据来源的各个部分都有可能产生日志。日志一般的描述是:在特定时间发生的事件,被以结构化的形式记录并产生的文本数据。</p>
|
||||
<p>日志可以为我们展现系统在任意时间的运行状态,又因为它是结构化的文本,所以我们很容易通过某种格式来进行检索,比如下图就是对 7 月 24 日用户支付下单操作的记录:</p>
|
||||
<p><img src="assets/Ciqc1F8n2O6AXUU_AACiGPnzHJ0060.png" alt="4.png" /></p>
|
||||
<p><img src="assets/Ciqc1F8n2O6AXUU_AACiGPnzHJ0060.png" alt="png" /></p>
|
||||
<p>由于日志是最容易生成的,如果它大量地输出,会占据比较大的存储空间,进而影响整个应用程序的性能,比如 Java 中 logback 的日志框架,就算使用了异步线程来执行,也会对磁盘和 I/O 的使用率造成影响。</p>
|
||||
<p>当然,也有一部分系统是利用日志可追溯、结构化的特点,来实现相关功能的,比如我们最常见的 WAL(Write-Ahead Logging)。WAL 就是在操作之前先进行日志写入,再执行操作;如果没有执行操作,那么在下次启动时就可以通过日志中结构化的,有时间标记的信息恢复操作,其中最典型的就是 MySQL 中的 Redo log。</p>
|
||||
<h4>统计指标(Metrics)</h4>
|
||||
<p><strong>统计指标</strong>也是我们经常使用的。它是一种可累加的聚合的数值结果,具有原子性。因此,我们可以通过各种数学计算方式来获取一段时间内的数值。</p>
|
||||
<p><img src="assets/CgqCHl8nrU6AQfBSAACjSdwOzUQ263.png" alt="image" /></p>
|
||||
<p><img src="assets/CgqCHl8nrU6AQfBSAACjSdwOzUQ263.png" alt="png" /></p>
|
||||
<p>统计指标针对数据的存储、处理、压缩和检索进行了优化,所以一般可以长期存储并以很简单的方式(聚合)查询。但由于涉及数据的处理(数学计算方式)和压缩,所以它也会占用一定的 CPU 资源。</p>
|
||||
<p>统计指标是一个压缩后的数值,因此如果指标出现异常,我们很难得知是什么原因导致的异常。此外,如果我们使用了一个高基数的指标来作为统计的维度,那么统计就很容易给机器带来高性能损耗,比如,在基于用户 ID 的维度去做数据统计时,因为在统计的时候需要一段时间范围,如果数据过多就必然会造成内存上的占用。</p>
|
||||
<p>讲到这里,你应该对指标有了一定的认识。我们后端经常说的 QPS、TPS、SLA 都是计算后得到的指标;基础设施中的 CPU 使用率、负载情况也可以认为是指标。</p>
|
||||
@@ -225,7 +225,7 @@ function hide_canvas() {
|
||||
</ol>
|
||||
<p>根据这 2 个级别,我们可以对上面的 3 个内容加以细化,其中<strong>链路追踪</strong>是请求级别,因为它和每个请求都挂钩;<strong>日志</strong>和<strong>统计指标</strong>可以是请求级别,也可以是聚合级别,因为它们可能是真实的请求,也可能是系统在对自身诊断时记录下来的信息。</p>
|
||||
<p>那么当它们两两组合之后又是什么关系呢?我们可以从下图中看到:</p>
|
||||
<p><img src="assets/CgqCHl8n0peAVsZsAAIx_TZards535.png" alt="3.png" /></p>
|
||||
<p><img src="assets/CgqCHl8n0peAVsZsAAIx_TZards535.png" alt="png" /></p>
|
||||
<ol>
|
||||
<li><strong>链路追踪+统计指标</strong>(Request-scoped metrics)<strong>,请求级别的统计</strong>:在链路追踪的基础上,与相关的统计数据结合,从而得知数据与数据、应用与应用之间的关系。</li>
|
||||
<li><strong>链路追踪+日志</strong>(Request-scoped events)<strong>,请求级别的事件</strong>:这是链路中一个比较常见的组合模式。日志本身是每一条单独存在的,将链路追踪收集到的信息集成在日志中,可以让日志之间具备关联性,使其具有除了事件维度以外的另一个新的维度,上下文信息。</li>
|
||||
|
||||
@@ -160,7 +160,7 @@ function hide_canvas() {
|
||||
<p>上一课时我对数据的来源做了简单的讲解,在对可观测性的 3 个核心概念的介绍中,我首先提到的就是日志。我们知道,在应用程序、端上和传输系统中,日志无处不在。因此,这节课我将带你了解,日志为什么会是保障系统稳定性的关键。</p>
|
||||
<h3>日志功能</h3>
|
||||
<p>日志可以记录系统中硬件、软件和系统的信息,同时还可以观测系统中发生的事件。用户可以通过它来检查错误发生的原因。如下图:</p>
|
||||
<p><img src="assets/CgqCHl8qWrmAflAzAAGAn77KKSE971.png" alt="Lark20200805-100952.png" /></p>
|
||||
<p><img src="assets/CgqCHl8qWrmAflAzAAGAn77KKSE971.png" alt="png" /></p>
|
||||
<p>一般来说,日志具有以下几个功能:</p>
|
||||
<ol>
|
||||
<li><strong>便于调试</strong>:开发人员在进行应用开发时,都会在测试或本地环境中调试。添加日志可以让你感知到它进入了某个函数,执行了某些行为,甚至进入了代码层级,记录代码判断后的行为。我们一般会将日志设置为调试级别,或者在上线前将它们统一删除。</li>
|
||||
@@ -172,7 +172,7 @@ function hide_canvas() {
|
||||
</ol>
|
||||
<h3>日志级别</h3>
|
||||
<p>日志级别,这是一个为人熟知的概念。尽管大家都了解它,但我还是发现很多开发人员在用法上存在一些问题。这里我会按照从低到高的顺序,介绍其中比较关键的 4 个日志等级,同时也会指出大家在用法上存在的问题并给出我的理解。</p>
|
||||
<p><img src="assets/CgqCHl8qWvGAFSXqAADfoMRTx1w354.png" alt="Lark20200805-102122.png" /></p>
|
||||
<p><img src="assets/CgqCHl8qWvGAFSXqAADfoMRTx1w354.png" alt="png" /></p>
|
||||
<ol>
|
||||
<li><strong>debug</strong>:调试级别。在这个级别,通常会记录一些调试的内容,比如程序进入方法或函数时,其中的参数信息。debug 级别的日志会极大地影响 CPU 和磁盘 I/O 写入的性能,所以我们一般只会在测试或本机环境中使用。除了自己编写的日志,一些常见的第三方框架也会记录一些日志以方便对程序的调试。这种日志量,在生产环境中你很难抓到重点,会浪费大量的时间在日志检索,所以我并不建议在生产环境中使用。</li>
|
||||
<li><strong>info</strong>:信息级别。这个应该是开发人员最常用的日志等级了。我们一般也是通过这个日志等级完成上面提到的功能,比如信息埋点、追踪数据变化、数据分析等。虽然大家都在使用,但我发现有些时候,开发同学经常会把 info 级别当作 debug 级别,输出了很多没必要的日志内容,导致线上环境产生了大量的垃圾和重复信息,很不便于日志检索。</li>
|
||||
@@ -184,7 +184,7 @@ function hide_canvas() {
|
||||
<p>查询问题的原因时,如果实在找不到,你可以按照一定的顺序对日志逐一排查,说不定就找到原因了。找问题原因的过程其实是一个不断否定、不断排除的过程,排除了所有的不可能,剩下的就是真相。</p>
|
||||
<p>所以,在这一课时的最后,我会介绍一些常见的日志的来源,以方便你在需要逐层检索的时候,有一个整体的概念。</p>
|
||||
<p>与我们在 01 课时介绍监控数据的来源时一样,日志的来源也可以按照用户端到服务端来划分。如下图所示:</p>
|
||||
<p><img src="assets/CgqCHl8qdjSADk7DAACHGDIzLd0093.png" alt="3.png" /></p>
|
||||
<p><img src="assets/CgqCHl8qdjSADk7DAACHGDIzLd0093.png" alt="png" /></p>
|
||||
<h4>终端层</h4>
|
||||
<p>这里的终端层我指的是像网页、App、小程序这样的形式。在这一层的所有日志信息都不在我们的服务器端,而是在用户的电脑、手机中。所以我们想要收集的话,一般是通过打点的形式上传到后端服务,再记录下来。</p>
|
||||
<p>终端层更偏向用户的真实操作行为和一些异常信息的记录,比如用户当前的网络环境、系统状态、手机型号等。通过观察这部分数据,我们可以看出是哪一类用户在操作时容易产生问题,这也更加方便后端和终端的研发人员定位问题。</p>
|
||||
|
||||
@@ -159,7 +159,7 @@ function hide_canvas() {
|
||||
<div><h1>03 日志编写:怎样才能编写“可观测”的系统日志?</h1>
|
||||
<p>在 02 课时,我带你重新认识了系统日志,介绍了日志在系统中的重要性。既然日志如此重要,那我们应该如何编写它呢?</p>
|
||||
<p>这一节,我将带你从编写日志的工具、编写日志的方式,以及日志编写后的管理,就像是购物的售前、售中、售后,这 3 个方面来讲解,怎么样才可以写出更具有“可观测性”的日志内容。</p>
|
||||
<p><img src="assets/CgqCHl8tHg-AR1niAABq5d5i1-4639.png" alt="2.png" /></p>
|
||||
<p><img src="assets/CgqCHl8tHg-AR1niAABq5d5i1-4639.png" alt="png" /></p>
|
||||
<h3>日志框架</h3>
|
||||
<p>在编写日志之前,咱们先来了解一下有哪些日志框架可以协助我们编写日志。</p>
|
||||
<p>在介绍日志框架之前,我需要说明一下,如果你仍在使用 System.out.println、Exception.printStackTrace 或类似的控制台输出日志的方式,我推荐你改用第三方日志框架编写。这种控制台输出的方式,可以从它们的源码了解到它们是线程同步的,大量使用这种方式,会对程序性能造成严重的影响,因为它们同一时间只能有一个线程在进行执行。</p>
|
||||
@@ -179,7 +179,7 @@ function hide_canvas() {
|
||||
<li>不同框架竞争:如果要引入多个日志框架,我们还需要考虑各个框架的输出位置。要是多个日志框架同时写入一个日志文件,还会涉及竞争问题,导致性能无法发挥。</li>
|
||||
</ol>
|
||||
<p>由此就出现了面向接口的日志框架,它提供了统一的 API。开发人员在编写代码的时候,直接使用这套面向接口的日志框架,当业务项目人员在使用时,只需要选择好实现框架,就可以统一日志实现框架。</p>
|
||||
<p><img src="assets/Ciqc1F8tHjOAWiuMAAB_uuNhVnM257.png" alt="3.png" /></p>
|
||||
<p><img src="assets/Ciqc1F8tHjOAWiuMAAB_uuNhVnM257.png" alt="png" /></p>
|
||||
<p>目前使用最为广泛的日志接口框架是 SLF4J,出自 logback 的开发者,目前基本已经形成规范。SLF4J 提供了动态占位符的功能,大大提高了程序的性能,无须开发人员再对参数信息进行拼接。</p>
|
||||
<p>比如默认情况下程序是 info 级别的,在原先的代码方式中想要进行日志输出需要自行拼接字符串:</p>
|
||||
<pre><code>logger.debug("用户" + userId + "开始下单:" + orderNo + ",请求信息:" + Gson.toJson(req));
|
||||
@@ -198,7 +198,7 @@ function hide_canvas() {
|
||||
<li>日志开发时(前 5 项):怎么样写出更有效率的日志?</li>
|
||||
<li>日志完成后(后 3 项):上线前后有哪些需要注意的?</li>
|
||||
</ul>
|
||||
<p><img src="assets/Ciqc1F8tHiCALdt1AACxdCqLBeM381.png" alt="4.png" /></p>
|
||||
<p><img src="assets/Ciqc1F8tHiCALdt1AACxdCqLBeM381.png" alt="png" /></p>
|
||||
<h4>日志编写位置</h4>
|
||||
<p>日志编写的位置可以说是重中之重,好的日志位置可以帮你解决问题,也可以让你更加了解代码的运行情况。我总结了几点比较重要的编写日志的位置,以供参考。</p>
|
||||
<ol>
|
||||
|
||||
@@ -170,23 +170,23 @@ function hide_canvas() {
|
||||
<p>介绍了指标的作用后,我们来看一下统计指标都有哪些类型,它们又分别有哪些不同的作用?</p>
|
||||
<h4>计数器(Counter)</h4>
|
||||
<p>计数器是一个数值单调递增的指标,一般这个值为 Double 或者 Long 类型。我们比较常见的有 Java 中的 AtomicLong、DoubleAdder,它们的值就是单调递增的。QPS 的值也是通过计数器的形式,然后配合上一些函数计算得出的。</p>
|
||||
<p><img src="assets/CgqCHl8yW_eAKnuGAABkAkOJ2w4716.png" alt="image" /></p>
|
||||
<p><img src="assets/CgqCHl8yW_eAKnuGAABkAkOJ2w4716.png" alt="png" /></p>
|
||||
<p>图 1:计数器</p>
|
||||
<h4>仪表盘(Gauge)</h4>
|
||||
<p>仪表盘和计数器都可以用来查询某个时间点的固定内容的数值,但和计数器不同,仪表盘的值可以随意变化,可以增加也可以减少。比如在 Java 线程池中活跃的线程数,就可以使用 ThreadPoolExecutor 的 getActiveCount 获取;比较常见的 CPU 使用率和内存占用量也可以通过仪表盘获取。</p>
|
||||
<p><img src="assets/Ciqc1F8yXACACNtbAAB9D2b9yO0247.png" alt="image" /></p>
|
||||
<p><img src="assets/Ciqc1F8yXACACNtbAAB9D2b9yO0247.png" alt="png" /></p>
|
||||
<p>图 2:仪表盘</p>
|
||||
<h4>直方图(Histogram)</h4>
|
||||
<p>直方图相对复杂一些,它是将多个数值聚合在一起的数据结构,可以表示数据的分布情况。</p>
|
||||
<p>如下图,它可以将数据分成多个<strong>桶(Bucket)</strong>,每个桶代表一个范围区间(图下横向数),比如第 1 个桶代表 0~10,第二个桶就代表 10~15,以此类推,最后一个桶代表 100 到正无穷。每个桶之间的数字大小可以是不同的,并没有规定要有规律。每个桶和一个数字挂钩(图左纵向数),代表了这个桶的数值。</p>
|
||||
<p><img src="assets/Ciqc1F8yXAyAAr1_AANyRfEvYDI870.png" alt="2.png" /></p>
|
||||
<p><img src="assets/Ciqc1F8yXAyAAr1_AANyRfEvYDI870.png" alt="png" /></p>
|
||||
<p>图 3:直方图</p>
|
||||
<p>以最常见的响应耗时举例,我把响应耗时分为多个桶,比如我认为 0~100 毫秒比较快,就可以把这个范围做一个桶,然后是 100~150 毫秒,以此类推。通过这样的形式,可以直观地看到一个时间段内的请求耗时分布图,这有助于我们理解耗时情况分布。</p>
|
||||
<h4>摘要(Summary)</h4>
|
||||
<p>摘要与直方图类似,同样表示的是一段时间内的数据结果,但是数据反映的内容不太一样。摘要一般用于标识分位值,分位值就是我们常说的 TP90、TP99 等。</p>
|
||||
<p>假设有 100 个耗时数值,将所有的数值从低到高排列,取第 90% 的位置,这个位置的值就是 TP90 的值,而这个桶的值假设是 80ms,那么就代表小于等于90%位置的请求都 ≤80ms。</p>
|
||||
<p>用文字不太好理解,我们来看下面这张图。这是一张比较典型的分位值图,我们可以看到图中有 6 个桶,分别是 50、75、80、90、95、99,而桶的值就是相对应的耗时情况。</p>
|
||||
<p><img src="assets/Ciqc1F8yXCuAAFmKAADKIjwhWjo693.png" alt="3.png" /></p>
|
||||
<p><img src="assets/Ciqc1F8yXCuAAFmKAADKIjwhWjo693.png" alt="png" /></p>
|
||||
<p>图 4:分位值图</p>
|
||||
<p>通过分位值图,我们可以看到最小值和最大值以外的一些数据,这些数据在系统调优的时候也有重要参考价值。</p>
|
||||
<p>在这里面我需要补充一个知识点,叫作<strong>长尾效应</strong>。长尾效应是指少部分类数据在一个数据模型中占了大多数样本,在数据模型中呈现出长长的尾巴的现象。如图所示,最上面的 TP99 相当于这个图表的尾巴,可以看到,1% 用户访问的耗时比其他 5 个桶加起来的都要长。这个时候你如果通过指标查看某个接口的平均响应时间,其实意义不大,因为这 1% 的用户访问已经超出了平均响应时间,所以平均响应时间已经无法反映数据的真实情况了。这时用户会出现严重的量级分化,而量化分级也是我们在进行系统调优时需要着重关注的。</p>
|
||||
|
||||
@@ -182,7 +182,7 @@ function hide_canvas() {
|
||||
</ol>
|
||||
<h4>通用指标</h4>
|
||||
<p>端上的资源请求,一般都会经历以下几个步骤:DNS 寻找,建立与服务器的链接,发送请求,请求响应。这几个步骤是可以被监控起来,现在很多主流的拨测软件也会提供这样的统计功能,拨测软件其实就是利用各个不同地方的机器发起请求,来测试网络情况。</p>
|
||||
<p><img src="assets/Ciqc1F81E42AK_yyAABp5FkSw7A395.png" alt="11.png" /></p>
|
||||
<p><img src="assets/Ciqc1F81E42AK_yyAABp5FkSw7A395.png" alt="png" /></p>
|
||||
<p>App 和网页,在发送请求和获取数据内容的过程中,除了以上提到的指标,还有以下几个指标需要注意:</p>
|
||||
<ol>
|
||||
<li><strong>DNS 响应时间</strong>:通常用来记录访问地址向 DNS 服务器发起请求,到 DNS 返回服务器 IP 地址信息的时间。</li>
|
||||
|
||||
@@ -170,7 +170,7 @@ function hide_canvas() {
|
||||
<li><strong>营收</strong>(revenue):公司是否从用户这里获得了营收,其中最典型的就是用户购买了你的内容,你所获得的成单金额。</li>
|
||||
<li><strong>传播</strong>(referral):老用户对潜在用户的病毒式传播及口碑传播,进行“<strong>老拉新</strong>”,比如拉勾教育的分销就可以认为是传播,并由此算出传播系数。</li>
|
||||
</ol>
|
||||
<p><img src="assets/CgqCHl8-V4WAAYnyAACYudHQzEU730.png" alt="Drawing 1.png" /></p>
|
||||
<p><img src="assets/CgqCHl8-V4WAAYnyAACYudHQzEU730.png" alt="png" /></p>
|
||||
<p>在这一流程中,你会发现其中每个部分都可以根据不同的功能,产生不同的数据指标,然后你可以通过这些更细化的指标优化产品,从而让产品更具有商业价值。</p>
|
||||
<h4>性能数据</h4>
|
||||
<p>性能层的数据会更加方便研发人员了解程序的运行情况。通过观测这部分数据,你能快速感知是哪些业务出现了异常,再结合日志或是我在下一课时要讲的链路,来快速定位问题出现的原因。</p>
|
||||
@@ -252,7 +252,7 @@ for (String time : sortedKeys) {
|
||||
</code></pre>
|
||||
<p>这样的计算方式,通常与计数器(Counter)一同使用,因为计数器的数据一般是递增的,但有时很难看到增长率。通过速率,你可以看出哪些时候的增长比较多,哪些时候又基本不变,比如拉勾教育的课程购买人数增速占比。在课程上线时我们会开展 1 元购的活动,通过查看活动前后的人数增长率,我们就能很清楚地知道在活动期间购买的人数会大幅增加,以后也会更多地开展类似的活动。</p>
|
||||
<p>2.<strong>irate</strong>:同样也叫速率。与 rate 的计算方式不同,irate 只计算最近两次数据之间的增长速率。rate 和 irate 的函数变化如下图:</p>
|
||||
<p><img src="assets/Ciqc1F8-WBmADMj-AADTy1fR_lA516.png" alt="Drawing 2.png" /></p>
|
||||
<p><img src="assets/Ciqc1F8-WBmADMj-AADTy1fR_lA516.png" alt="png" /></p>
|
||||
<p>这张图中红线的就是 irate 函数,而绿色线的就是 rate 函数。图中可以很明显地看出来,rate 更平缓一些,irate 则能更“实时”地体现出数据。</p>
|
||||
<p>Rate 会对指定时间段内的所有值做平均计算,导致部分精度丢失。因此,irate 通常比 rate 更加精准。但 rate 的曲线更平滑,能更直接地反映出数据整体的波动。</p>
|
||||
<p>3.<strong>环比</strong>:指连续 2 个统计周期的变化率。我们在计算销售量时,就可以使用环比,比如这个月的销售量环比增长 10%,指的就是同上一个月销售量相比,增长了 10%。计算公式如下:</p>
|
||||
|
||||
@@ -183,7 +183,7 @@ function hide_canvas() {
|
||||
<p>既然都是基于线程的,而线程中基本会伴随着方法栈,即每进入一个方法都会通过压入一个方法栈帧的情况来保存。<strong>那我们是不是可以定期查看方法栈的情况来确认问题呢?答案是肯定的</strong>。比如我们经常使用到的 jstack,其实就是实时地对所有线程的堆栈进行快照操作,来查看当前线程的执行情况。</p>
|
||||
<p>利用我上面提到的 2 点,再结合链路中的上下文信息,我们可以通过<strong>周期性地对执行中的线程进行快照操作,并聚合所有的快照,来获得应用线程在生命周期中的执行情况,从而估算代码的执行速度,查看出具体的原因</strong>。</p>
|
||||
<p>这样的处理方式,我们就叫作<strong>性能剖析</strong>(Profile),原理可以参照下图:</p>
|
||||
<p><img src="assets/Ciqc1F9HZ0uASChgAABQpC64934541.png" alt="Drawing 1.png" /></p>
|
||||
<p><img src="assets/Ciqc1F9HZ0uASChgAABQpC64934541.png" alt="png" /></p>
|
||||
<p>在这张图中,<strong>第一行代表线程进行快照的周期情况</strong>,每一个周期都可以认为是一段时间,比如 10ms、100ms。周期的时间长短,决定了对程序性能影响的大小。因为在进行线程快照时程序会暂停,当快照完成后才会继续进行操作。</p>
|
||||
<p><strong>第二行则代表我们需要进行观测的方法的执行时间</strong>,线程快照只能做到周期性的快照获取。虽然可能并不能完全匹配,但通过这种方式,相对来说已经很精准了。</p>
|
||||
<p>性能剖析与埋点相比,有以下几个优势:</p>
|
||||
@@ -199,11 +199,11 @@ function hide_canvas() {
|
||||
<p>这个时候我们一般可以通过 2 种方式查看线程聚合的结果信息,它们分别是<strong>火焰图</strong>和<strong>树形图</strong>。</p>
|
||||
<h4>火焰图</h4>
|
||||
<p>火焰图,顾名思义,是和火焰一样的图片。<strong>火焰图是在 Linux 环境中比较常见的一种性能剖析展现方式</strong>。火焰图有很多种展现形式,这里我就以我们会用到的 CPU 火焰图为例:</p>
|
||||
<p><img src="assets/Ciqc1F9HZ2KAXgC0AAaJrTEo0uQ972.png" alt="Drawing 2.png" /></p>
|
||||
<p><img src="assets/Ciqc1F9HZ2KAXgC0AAaJrTEo0uQ972.png" alt="png" /></p>
|
||||
<p>CPU 火焰图</p>
|
||||
<p>在 CPU 火焰图中,每一个方格代表一个方法栈帧,方格的长度则代表它的执行时间,所以方格越长就说明该栈帧执行的时间越长。火焰图中在某一个方格中增高一层,就说明是这个方法栈帧中,又调用了某个方法的栈帧。最顶层的函数,是叶子函数。叶子函数的方格越宽,说明这个方法在这里的执行耗时越长。</p>
|
||||
<p>如果觉得上面的火焰图太复杂的话,我们可以看一张简化的图,如下:</p>
|
||||
<p><img src="assets/CgqCHl9HZ2mAaGjhAACnwkzLj5I930.png" alt="Drawing 3.png" /></p>
|
||||
<p><img src="assets/CgqCHl9HZ2mAaGjhAACnwkzLj5I930.png" alt="png" /></p>
|
||||
<p>图中,a 方法是执行的方法,可以看出来,其中 g 方法是执行时间相对较长的。</p>
|
||||
<p>无论是火焰图,还是这张简化的图,它们都通过图形的方式,让我们能够快速定位到执行缓慢的原因。但是这种的方式也存在一些问题:</p>
|
||||
<ol>
|
||||
@@ -211,7 +211,7 @@ function hide_canvas() {
|
||||
<li><strong>很难发现非叶子节点的问题</strong>。我们在简化图中可以发现,我们在 d 方法中除了 e 和 f 方法的调用以外,其实 d 方法还有一段的时间是自己消耗的,并且没有被处理掉,这一问题在火焰图中会更加明显。</li>
|
||||
</ol>
|
||||
<h4>树形图</h4>
|
||||
<p><img src="assets/Ciqc1F9HZ6SAAuuSAACg533klAQ565.png" alt="Drawing 5.png" /></p>
|
||||
<p><img src="assets/Ciqc1F9HZ6SAAuuSAACg533klAQ565.png" alt="png" /></p>
|
||||
<p>为了解决这 2 个问题,就有了另外一种展现方式,那就是<strong>树形图</strong>。树形图就是<strong>将方法的调用堆栈通过树形图的形式展现出来</strong>。这对于开发人员来说相对直观,因为你可以通过树形图的形式快速查看整体的调用情况,并且针对火焰图中的问题,树形图也有很好的解决方法:</p>
|
||||
<ul>
|
||||
<li>使用这样的形式,栈帧很容易识别,并且之间的调用关系也很容易理解。</li>
|
||||
|
||||
@@ -206,7 +206,7 @@ function hide_canvas() {
|
||||
<p>我就不介绍 ELK 的安装方式了,ELK 已经使用多年了,整体相对稳定,它的安装方式很容易就能在网上搜到。接下来我会对常见的 Kibana 的使用做简要说明。</p>
|
||||
<h4>Kibana 的使用方式</h4>
|
||||
<p><strong>Kibana 是一个针对 ElasticSearch 的数据分析与可视化平台,用来搜索、查看存储在 ElasticSearch 中的数据</strong>。如果你感兴趣,可以点击<a href="https://demo.elastic.co/app/kibana#/discover">这里</a>,前往官网体验提供的 demo。</p>
|
||||
<p><img src="assets/Ciqc1F9gjhSAAQLeAAUetyp06UA251.png" alt="Drawing 1.png" /></p>
|
||||
<p><img src="assets/Ciqc1F9gjhSAAQLeAAUetyp06UA251.png" alt="png" /></p>
|
||||
<p>图中是一个比较典型的日志检索界面。</p>
|
||||
<p>它支持通过时间筛选日志内容,我们可以在最上方通过 KQL 或者 Filter 来检索数据,比如我们的系统根据用户 ID 来进行检索,此时就可以输入指定的语句,筛选出符合条件的日志内容。链路追踪的 TraceID 是一个比较常见的查询方式。</p>
|
||||
<p>左边的竖列就是目前系统中所有已知的字段列表,一般这个列表有 2 个作用:</p>
|
||||
|
||||
@@ -201,7 +201,7 @@ function hide_canvas() {
|
||||
<p>我会先依据我刚才讲的指标系统的原理,对 Prometheus 的系统架构做详细说明,然后讲解其中的 4 个常见功能。</p>
|
||||
<h4>系统架构</h4>
|
||||
<p>我们先来看一张 Prometheus 的架构图:</p>
|
||||
<p><img src="assets/Ciqc1F9pyTGATVArAAFB6U7Uslw981.png" alt="Drawing 0.png" /></p>
|
||||
<p><img src="assets/Ciqc1F9pyTGATVArAAFB6U7Uslw981.png" alt="png" /></p>
|
||||
<p>根据我在原理中讲到的数据收集、指标聚合、指标查询和规则告警,我们也可以通过这 4 个步骤来了解 Prometheus。</p>
|
||||
<p>我们可以从图中看到,Prometheus 主要是采取拉取模式获取数据的,它提供了完善的服务发现机制,结合 K8s 的 API 可以动态感知服务的创建与销毁。通过可配置的方式,定期拉取服务列表的数据。对于一些短期存在的任务,Prometheus 同样提供了 PushGateway,让业务程序能够推送数据,再由收集系统来收集。</p>
|
||||
<p>其次是 Prometheus 的指标聚合。Prometheus 服务器接收到数据之后,会通过 TSDB 存储引擎聚合数据,然后存储到磁盘上。TSDB 中的数据结构和我在**13 | 告警质量:如何更好地创建告警规则和质量?**这一课时中,讲到的时序数据库的结构一样。</p>
|
||||
@@ -256,7 +256,7 @@ description: "{{ $labels.instance }} 实例中的请求错误数超过 20
|
||||
<p>上述代码声明了一个告警规则,接下来就可以通过 Alertmanager 组件进行更详细的通知方式配置。这部分内容十分简单,你可以通过<a href="https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/">官方提供的文档</a>学习。</p>
|
||||
<h4>Grafana</h4>
|
||||
<p>最后我带你简单认识一下 Grafana,它是<strong>一个通过可视化的形式查看指标数据的展示系统</strong>。通过它,你可以快速构建出 dashboard。其数据源就是依赖 Prometheus,通过 PromQL 的查询实现的。如下图所示:</p>
|
||||
<p><img src="assets/CgqCHl9pyVSAaRiNAAFaF113VPU106.png" alt="Drawing 1.png" /></p>
|
||||
<p><img src="assets/CgqCHl9pyVSAaRiNAAFaF113VPU106.png" alt="png" /></p>
|
||||
<p>这张图是官方 demo 所提供的展示内容,可以点击<a href="http://demo.robustperception.io:3000/d/KyOBFkuik/host-stats-prometheus-node-exporter?orgId=1">这里</a>访问。</p>
|
||||
<p>Grafana是依赖于 Prometheus 提供的数据搭建而成的。其中,<strong>最上面一行分别展示了当前的展示模板和对应查询时间范围</strong>,这个和我介绍的 Kibana 十分类似。<strong>下方的每一个模块展示的是通过 PromQL 所查询出数据的结果</strong>,这个部分可以选择不同的展示方式,例如柱状图、折线图、列表等。</p>
|
||||
<p>Grafana 还支持导入和导出展示模板,你可以下载一些已经很成熟的模板信息来使用。·</p>
|
||||
|
||||
@@ -190,7 +190,7 @@ function hide_canvas() {
|
||||
<li>可扩展性:一个优秀的链路追踪系统,在链路采集层面一定会有良好的扩展性,而不是仅适用于单独的一个业务或者框架。因此,就需要这个链路追踪系统支持更多的框架,同时尽量不对系统的性能造成过高的影响。字节码增强因其实现方式的原因,可扩展性相对埋点会更高一些,但我们还是应该依据具体的实现方式和框架来选择。</li>
|
||||
</ol>
|
||||
<p>你可以通过下图更直观地看到二者之间的差别。</p>
|
||||
<p><img src="assets/Ciqc1F9sMXOAKAomAAB5R_3zHyM553.png" alt="Drawing 1.png" /></p>
|
||||
<p><img src="assets/Ciqc1F9sMXOAKAomAAB5R_3zHyM553.png" alt="png" /></p>
|
||||
<p>这两种链路采集方案没有绝对的好坏之分,还要考虑项目的具体使用场景上。如果是使用开源或者商业方案时,还要考虑到与整个链路追踪系统的集成程度、支持的组件等。</p>
|
||||
<h4>数据收集</h4>
|
||||
<p><strong>从链路采集到数据之后,我们就可以对这些数据进行解析、分析等工作,并最终存储到相应的存储引擎中</strong>,常见的引擎有 ElasticSearch、HBase、MySQL 等。</p>
|
||||
@@ -213,7 +213,7 @@ function hide_canvas() {
|
||||
<p>Zipkin 是一款开源的链路追踪系统,它是基于我们之前提到的<em><strong>Dapper</strong></em>论文设计的,由 Twitter 公司开发贡献。</p>
|
||||
<h4>系统架构</h4>
|
||||
<p>我们先来看 Zipkin 的系统架构图,它展现了 Zipkin 的整体工作流程:</p>
|
||||
<p><img src="assets/Ciqc1F9sMZyAE2w1AAFoissS5jA558.png" alt="Drawing 3.png" /></p>
|
||||
<p><img src="assets/Ciqc1F9sMZyAE2w1AAFoissS5jA558.png" alt="png" /></p>
|
||||
<p>这一部分对应我在原理中讲到的链路采集、数据收集和数据查看的步骤,我们从上往下依次来看。</p>
|
||||
<p><strong>首先是链路采集</strong>。紫色的部分代表业务系统和组件,图中是以一个典型的 RPC 请求作为所需要追踪的链路,其中 client 为请求的发起方,分别请求了两个服务端。其中被观测的客户端和服务端会在启动的实例中增加数据上报的功能,这里的数据上报就是指从本实例中观测到的链路数据,一并上报到 Zipkin 中,传输工具常见的有 Kafka 或者 HTTP 请求。</p>
|
||||
<p><strong>数据传输到 Zipkin 的收集器后,会经过 Zipkin 的存储模块,存储到数据库中</strong>。目前支持的数据库有 MySQL、ElasticSearch、Cassandra 这几种类型,具体的数据库选择可以根据公司内部运维的实力评估出最适合的。</p>
|
||||
@@ -241,12 +241,12 @@ public OkHttpClient buildOkHttpClient(HttpTracing tracing) {
|
||||
<p><strong>消息传递是在链路追踪中保持上下游服务相同链路的关键,一般会通过消息透传的方式来做到</strong>。比如上下游是通过 HTTP 的方式进行数据交换的,此时就可以在上游准备发送时的 HTTP 请求头中增加链路的上下文信息;下游接收到请求后,解析相对应的 HTTP 请求头数据,确认是否有链路上下文信息。</p>
|
||||
<p>如果存在链路上下文信息则可以继续将链路信息传递,认定是相同链路,从而来实现链路追踪;如果没有,则可以认定为是一个全新的链路。</p>
|
||||
<p>以刚才的 OkHttp 框架为例,我们尝试发送一个请求,然后通过 WireShark 工具观测数据内容,就可以获取到如下信息:</p>
|
||||
<p><img src="assets/CgqCHl9sMaqATYvXAADJK-NCV7U599.png" alt="Drawing 4.png" /></p>
|
||||
<p><img src="assets/CgqCHl9sMaqATYvXAADJK-NCV7U599.png" alt="png" /></p>
|
||||
<p>在这张图中,我们可以清楚地看到请求时的详细数据。</p>
|
||||
<p>请求头中除了基础的 Header 信息以外,还会有很多以 "X-B3" 开头的内容,比如TraceId、SpanId 等关键信息,就是经由 Zipkin 产生的链路上下文信息。</p>
|
||||
<h4>数据展示</h4>
|
||||
<p>我们来看一张相对简单的链路数据展示图。图中主要模拟就是如项目架构图中类似的 client 端发送请求,server 端接收请求的链路逻辑。</p>
|
||||
<p><img src="assets/Ciqc1F9sMbOABg6OAAXsVfGd0-Q188.png" alt="Drawing 5.png" /></p>
|
||||
<p><img src="assets/Ciqc1F9sMbOABg6OAAXsVfGd0-Q188.png" alt="png" /></p>
|
||||
<p>左侧部分展示的是 client 端接收到了上游的请求,然后交由 server 获取数据内容的链路信息。</p>
|
||||
<p>右侧上半部分分别显示的是客户端发送、服务端接收、服务端处理结束、客户端获取到数据中每一个节点的时间关系。</p>
|
||||
<p>右侧下半部分展示的是当前我们选中的 Span 的标签信息,和我在“<strong>10 链路分析:除了观测链路,还能做什么?</strong>”中所讲的自定义数据十分相似,在这里你可以通过自定义属性信息来完成信息的定制化。</p>
|
||||
|
||||
@@ -165,7 +165,7 @@ function hide_canvas() {
|
||||
<p>SkyWalking 和 Zipkin 的定位不同,决定了它们不是相同类型的产品。<strong>SkyWalking 中提供的组件更加偏向业务应用层面</strong>,并没有涉及过多的组件级别的观测;<strong>Zipkin 提供了更多组件级别的链路观测</strong>,但并没有提供太多的链路分析能力。你可以根据两者的侧重点来选择合适的产品。</p>
|
||||
<h3>系统架构</h3>
|
||||
<p>下面是官网提供的 SkyWalking 的系统架构图,我们先通过这张图来了解它:</p>
|
||||
<p><img src="assets/CgqCHl9xYUSAMdjMAAM5S7oWJck457.png" alt="1.png" /></p>
|
||||
<p><img src="assets/CgqCHl9xYUSAMdjMAAM5S7oWJck457.png" alt="png" /></p>
|
||||
<p>从中间往上看,<strong>首先是 Receiver Cluster,它代表接收器集群,是整个后端服务的接入入口,专门用来收集各个指标,链路信息,相当于我在上一节所讲的链路收集器。</strong></p>
|
||||
<p><strong>再往后面走是 Aggregator Cluster,代表聚合服务器,它会汇总接收器集群收集到的所有数据,并且最终存储至数据库,并进行对应的告警通知</strong>。右侧标明了它支持的多种不同的存储方式,比如常见的 ElasticSearch、MySQL,我们可以根据需要来选择。</p>
|
||||
<p>图的左上方表示,我们可以使用 CLI 和 GUI,通过 HTTP 的形式向集群服务器发送请求来读取数据。</p>
|
||||
@@ -195,14 +195,14 @@ function hide_canvas() {
|
||||
<p>最后,基于 OAL 语言,我们自定义了统计指标。我们可以将其运用在自定义的 UI 展示和告警中,将动态统计指标的优势最大化,从而来实现一套高度可定制化的观测平台。</p>
|
||||
<h4>拓扑图</h4>
|
||||
<p>除了统计指标之外,链路分析中的另外一个关键点就是拓扑。拓扑图可以展示服务、端点、实例之间的引用关系,将引用关系与统计指标相结合后,我们能更快地了解到系统整体的运行情况,以及流量主要分布在哪里。下图就展示了在 SkyWalking 中,是怎样展现服务之间的拓扑的。</p>
|
||||
<p><img src="assets/Ciqc1F9xYeSAGKOBAAC5r84ETek340.png" alt="1.png" /></p>
|
||||
<p><img src="assets/Ciqc1F9xYeSAGKOBAAC5r84ETek340.png" alt="png" /></p>
|
||||
<p>在这张图中,从左到右代表服务从接入流量到服务处理中的完整拓扑信息。用户发起访问,首先经由 ProjectA 服务,然后引入 ProjectB、ProjectC 和其他的云服务,ProjectB、ProjectC 又分别调用了其余的组件和服务。服务依赖之间使用线进行连接,可以清楚地描绘出彼此的关系。</p>
|
||||
<p>传统的拓扑检测,通常是利用时间窗口来推断服务之间的依赖关系。比如 RPC 中消费者发送请求给提供者,提供者会先完成请求,再将链路数据发送到链路收集器端。此时,由于收集器端并不清楚是谁调用了提供者,所以会将数据保留一段到内存中。消费者完成请求处理后,将链路信息再发送到链路收集器中,此时再进行数据匹配,才能得知提供者的消费者是哪一个。得知消费者之后,保存在内存中的数据就会被删掉。</p>
|
||||
<p>在分布式系统中,RPC 的请求数量可能非常巨大,如果使用传统的拓扑检测,虽然也能完成,但是会导致高延迟和高内存使用。同时由于是基于时间窗口模式,如果提供者的数据上报事件超过了时间窗口规定的时间,就会出现无法匹配的问题。</p>
|
||||
<p>SkyWalking 为了解决上面提到的延迟和内存问题,引入了一个新的分析方式来进行拓扑检测,这种方式叫作 STAM(Streaming Topology Analysis Method)。</p>
|
||||
<p><strong>STAM 通过在消息传递的内容中注入更多的链路上下文信息,解决了传统拓扑检测中高延迟和高内存的问题。</strong></p>
|
||||
<p>Zipkin 和 SkyWalking 在 OkHttp 框架的消息传递时,都会将链路信息放置在请求头中。<strong>无论它们的采集器是如何实现的,在进行消息传递时,都会通过某种方式将链路信息设置到请求中</strong>。如下图所示:</p>
|
||||
<p><img src="assets/Ciqc1F9xYgmAfvCDAAEDn5gIMBo308.png" alt="1.png" /></p>
|
||||
<p><img src="assets/Ciqc1F9xYgmAfvCDAAEDn5gIMBo308.png" alt="png" /></p>
|
||||
<p>我们可以看到其中有三个“sw8”开头的 header 内容,“sw8”也是 SkyWalking 在进行链路上下文传递中的关键信息。这里进行了转码处理,我们可以通过阅读官方对跨线程消息透传协议的 <a href="https://github.com/apache/skywalking/blob/6fe2041b470113e626cb3f41e3789261d31f2548/docs/en/protocols/Skywalking-Cross-Process-Propagation-Headers-Protocol-v3.md.html">介绍</a>,了解到它进行了信息的传递。我列出一些其中比较关键的部分。</p>
|
||||
<ol>
|
||||
<li>Trace Id:用于记录全局的链路 Id。</li>
|
||||
|
||||
@@ -173,7 +173,7 @@ function hide_canvas() {
|
||||
<p>阿里云提供的 ARMS 就是包含上述功能的一套云观测系统,除了以上 4 点,它还提供了很多特有的功能,让你更方便地观测数据。</p>
|
||||
<h4>提供功能</h4>
|
||||
<p>ARMS 提供的功能主要分为 6 个部分:前端监控、App 监控、应用监控、自定义监控、大盘展示、报警。我们依次来看。</p>
|
||||
<p><img src="assets/CgqCHl9y50CAXyttAAEVMU02lVM306.png" alt="Lark20200929-154947.png" /></p>
|
||||
<p><img src="assets/CgqCHl9y50CAXyttAAEVMU02lVM306.png" alt="png" /></p>
|
||||
<p><strong>前端监控</strong>
|
||||
<strong>前端监控指的是通过在页面中埋入脚本的形式,让阿里云接管前端的数据上报</strong>。其中就包含我们比较常见的脚本错误次数、接口请求次数、PV、UV 等统计数据,也包含页面中脚本错误、API 访问等数据信息。通过统计数据你能快速了解前端用户的访问情况;脚本错误、API 访问等数据,则可以帮助你了解页面出现错误或者接口访问时的详细信息。</p>
|
||||
<p><strong>App 监控</strong>
|
||||
@@ -205,7 +205,7 @@ ARMS 的应用监控和我之前讲的链路追踪的内容十分相似,其中
|
||||
<p>在页面中,通过 script 标签引入一个 JavaScript 文件来进行任务处理,然后通过 pid 参数设定的应用 ID,保证数据只会上传到你的服务中。</p>
|
||||
<p><strong>网页运行时就会自动下载 bl.js 文件,下载完成后,代码会自动执行</strong>。当页面处理各种事件时,会通过异步的形式,上报当前的事件信息,从而实现对前端运行环境、执行情况的监控。常见的事件有:页面启动加载、页面加载完成、用户操作行为、页面执行时出现错误、离开页面。</p>
|
||||
<p><strong>页面加载完成之后,会发送 HEAD 请求来上报数据</strong>。其中我们可以清楚的看到,在请求参数中包含 DNS、TCP、SSL、DOM、LOAD 等信息,分别代表 DNS 寻找、TCP 建立连接、SSL 握手这类,我在“<strong>05 | 监控指标:如何通过分析数据快速定位系统隐患?(上)</strong>”中讲到的通用指标,也包含 DOM 元素加载时间这类网页中的统计指标信息。如下所示:</p>
|
||||
<p><img src="assets/CgqCHl9xdOCAJwMaAAFLBfVQzpI118.png" alt="1.png" /></p>
|
||||
<p><img src="assets/CgqCHl9xdOCAJwMaAAFLBfVQzpI118.png" alt="png" /></p>
|
||||
<p>数据上报后,ARMS 就会接收到相对应事件中的完整数据信息,从而通过聚合的方式,存储和展示数据。在 ARMS 中,针对应用有访问速度、JS 错误、API 请求这些统计指标和错误信息的数据,ARMS 可以依据不同维度的数据了解到更详细的内容,包含页面、地理、终端、网络这 4 类。通过不同的数据维度,你也可以更有针对性地了解问题。</p>
|
||||
<p><strong>App 监控</strong>
|
||||
App 的监控方式与前端监控十分类似,都需要通过增加代码的方式进行。以 iOS 为例,如果我们想要接入性能分析功能,除了要引入相关依赖,还需要在代码中进行如下的声明:</p>
|
||||
@@ -217,24 +217,24 @@ App 的监控方式与前端监控十分类似,都需要通过增加代码的
|
||||
对于服务端监控来说,ARMS 支持目前主流的 Java、PHP、Go 等语言,这里我以 Java 语言为例说明。</p>
|
||||
<p>在 Java 中,主要通过字节码增强的形式采集数据。项目启动后,会采集机器中 JVM 中的统计指标、链路数据等信息,然后结合链路,分析出统计指标、拓扑图的信息,以及应用与各个组件之间的交互细节,比如数据库查询、消息 MQ 发送量等数据信息。</p>
|
||||
<p>在服务端监控中,我们可以看到请求链路中的数据,在 ARMS 的显示中都是基于应用的维度,以树形进行展示的。比如我们有 2 个应用程序,上游服务通过“/first”接口地址对外提供服务,同时又调用了下游服务的“/second”接口。如下图所示:</p>
|
||||
<p><img src="assets/Ciqc1F9xdSaAFrAgAACCWZpq9Zk452.png" alt="1.png" /></p>
|
||||
<p><img src="assets/Ciqc1F9xdSaAFrAgAACCWZpq9Zk452.png" alt="png" /></p>
|
||||
<p>这张图中展示了对应的上下游服务、发生时间、实例地址、调用方式、服务名称和时间轴信息。并且我们可以通过点击其中单个服务的“方法栈”按钮,查看其链路中关键方法的执行流程。点开之后的页面如下:</p>
|
||||
<p><img src="assets/CgqCHl9xdTaAZPFUAADXO6lzCSY138.png" alt="1.png" /></p>
|
||||
<p><img src="assets/CgqCHl9xdTaAZPFUAADXO6lzCSY138.png" alt="png" /></p>
|
||||
<p><strong>在 ARMS 中服务端监控的功能中,最常用的是应用诊断部分,其中包含了实时诊断、异常分析、线程分析这 3 部分重点功能。</strong></p>
|
||||
<ul>
|
||||
<li><strong>实时诊断</strong>:默认情况下,服务端监控会通过采样的形式采集链路数据,以此来保证尽可能地减少对线上服务造成性能损耗。大多数情况下,指标数据都能快速体现出运行情况。在实时诊断中,会临时采取 100%采集,将所有的请求链路进行采集并上报,此时则可以看到指定时间段内的所有链路信息。</li>
|
||||
</ul>
|
||||
<p><img src="assets/Ciqc1F9xdZWAKUZzAAFEFkYvTqk447.png" alt="1.png" /></p>
|
||||
<p><img src="assets/Ciqc1F9xdZWAKUZzAAFEFkYvTqk447.png" alt="png" /></p>
|
||||
<ul>
|
||||
<li><strong>异常分析</strong>:汇总当前应用下存在的各种异常信息,你可以了解你的应用中哪些异常信息是相对较多的。有限优化占比较多的错误信息,有利于提升服务整体的成功率。</li>
|
||||
</ul>
|
||||
<p>下图中汇总了服务中出现错误的异常信息,我们可以通过点击具体的接口名称,找到对应的接口,更细致地查看接口细则。</p>
|
||||
<p><img src="assets/Ciqc1F9xdbGAB7dKAAFsqvOFvIM738.png" alt="1.png" /></p>
|
||||
<p><img src="assets/Ciqc1F9xdbGAB7dKAAFsqvOFvIM738.png" alt="png" /></p>
|
||||
<ul>
|
||||
<li><strong>线程分析</strong>:列出当前应用实例中具体的线程或者线程池列表。我们可以根据线程数或者 CPU 耗时信息来对线程进行排序,更直观地看出哪个线程池中创建的线程比较多,或者具体哪个线程消耗 CPU 资源较多。</li>
|
||||
</ul>
|
||||
<p>如果程序出现执行缓慢的情况,我们可以通过 CPU 资源消耗来寻找原因。还可以通过点击每个线程中右侧的方法栈,来快速查看指定线程的执行方法栈信息。查询到问题的原因后,我们再结合具体的业务场景处理问题。</p>
|
||||
<p><img src="assets/Ciqc1F9xdc6AJYAaAAGwMophZgc898.png" alt="1.png" /></p>
|
||||
<p><img src="assets/Ciqc1F9xdc6AJYAaAAGwMophZgc898.png" alt="png" /></p>
|
||||
<h3>总结</h3>
|
||||
<p>以上,我介绍了云端观测的作用以及在阿里云的 ARMS 系统中的实践。如果你的系统部署在云端,那么云端观测就是你进行系统观测的不二选择。你通过云端观测解决过哪些问题呢?欢迎你在留言区分享。</p>
|
||||
<p>下一节,我将带你了解如何将可观测系统与 OSS 系统相结合。</p>
|
||||
|
||||
Reference in New Issue
Block a user