mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-09-30 15:16:43 +08:00
747 lines
23 KiB
HTML
747 lines
23 KiB
HTML
<!DOCTYPE html>
|
||
|
||
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
|
||
|
||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||
|
||
<head>
|
||
|
||
<head>
|
||
|
||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
|
||
|
||
<link rel="icon" href="/static/favicon.png">
|
||
|
||
<title>09 性能剖析:如何补足分布式追踪短板?.md.html</title>
|
||
|
||
<!-- Spectre.css framework -->
|
||
|
||
<link rel="stylesheet" href="/static/index.css">
|
||
|
||
<!-- theme css & js -->
|
||
|
||
<meta name="generator" content="Hexo 4.2.0">
|
||
|
||
</head>
|
||
|
||
|
||
|
||
<body>
|
||
|
||
|
||
|
||
<div class="book-container">
|
||
|
||
<div class="book-sidebar">
|
||
|
||
<div class="book-brand">
|
||
|
||
<a href="/">
|
||
|
||
<img src="/static/favicon.png">
|
||
|
||
<span>技术文章摘抄</span>
|
||
|
||
</a>
|
||
|
||
</div>
|
||
|
||
<div class="book-menu uncollapsible">
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="/" class="current-tab">首页</a></li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="../">上一级</a></li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/分布式链路追踪实战-完/00 分布式链路追踪实战.md.html">00 分布式链路追踪实战.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/分布式链路追踪实战-完/01 数据观测:数据追踪的基石从哪里来?.md.html">01 数据观测:数据追踪的基石从哪里来?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/分布式链路追踪实战-完/02 系统日志:何以成为保障稳定性的关键?.md.html">02 系统日志:何以成为保障稳定性的关键?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/分布式链路追踪实战-完/03 日志编写:怎样才能编写“可观测”的系统日志?.md.html">03 日志编写:怎样才能编写“可观测”的系统日志?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/分布式链路追踪实战-完/04 统计指标:“五个九”对系统稳定的真正意义.md.html">04 统计指标:“五个九”对系统稳定的真正意义.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/分布式链路追踪实战-完/05 监控指标:如何通过分析数据快速定位系统隐患?(上).md.html">05 监控指标:如何通过分析数据快速定位系统隐患?(上).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/分布式链路追踪实战-完/06 监控指标:如何通过分析数据快速定位系统隐患?(下).md.html">06 监控指标:如何通过分析数据快速定位系统隐患?(下).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/分布式链路追踪实战-完/07 指标编写:如何编写出更加了解系统的指标?.md.html">07 指标编写:如何编写出更加了解系统的指标?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/分布式链路追踪实战-完/08 链路监控:为什么对于系统而言必不可少?.md.html">08 链路监控:为什么对于系统而言必不可少?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
<a class="current-tab" href="/专栏/分布式链路追踪实战-完/09 性能剖析:如何补足分布式追踪短板?.md.html">09 性能剖析:如何补足分布式追踪短板?.md.html</a>
|
||
|
||
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/分布式链路追踪实战-完/10 链路分析:除了观测链路,还能做什么?.md.html">10 链路分析:除了观测链路,还能做什么?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/分布式链路追踪实战-完/11 黑白盒监控:系统功能与结构稳定的根基.md.html">11 黑白盒监控:系统功能与结构稳定的根基.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/分布式链路追踪实战-完/12 系统告警:快速感知业务隐藏问题.md.html">12 系统告警:快速感知业务隐藏问题.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/分布式链路追踪实战-完/13 告警质量:如何更好地创建告警规则和质量?.md.html">13 告警质量:如何更好地创建告警规则和质量?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/分布式链路追踪实战-完/14 告警处理:怎样才能更好地解决问题?.md.html">14 告警处理:怎样才能更好地解决问题?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/分布式链路追踪实战-完/15 日志收集:ELK 如何更高效地收集日志?.md.html">15 日志收集:ELK 如何更高效地收集日志?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/分布式链路追踪实战-完/16 指标体系:Prometheus 如何更完美地显示指标体系?.md.html">16 指标体系:Prometheus 如何更完美地显示指标体系?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/分布式链路追踪实战-完/17 链路追踪:Zipkin 如何进行分布式追踪?.md.html">17 链路追踪:Zipkin 如何进行分布式追踪?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/分布式链路追踪实战-完/18 观测分析:SkyWalking 如何把观测和分析结合起来?.md.html">18 观测分析:SkyWalking 如何把观测和分析结合起来?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/分布式链路追踪实战-完/19 云端观测:ARMS 如何进行云观测?.md.html">19 云端观测:ARMS 如何进行云观测?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/分布式链路追踪实战-完/20 运维集成:内部的 OSS 系统如何与观测相结合?.md.html">20 运维集成:内部的 OSS 系统如何与观测相结合?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/分布式链路追踪实战-完/21 结束语 未来的监控是什么样子?.md.html">21 结束语 未来的监控是什么样子?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
|
||
|
||
<div class="sidebar-toggle-inner"></div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<script>
|
||
|
||
function add_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.add('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
function remove_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.remove('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
function sidebar_toggle() {
|
||
|
||
let sidebar_toggle = document.querySelector('.sidebar-toggle')
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let content = document.querySelector('.off-canvas-content')
|
||
|
||
if (sidebar_toggle.classList.contains('extend')) { // show
|
||
|
||
sidebar_toggle.classList.remove('extend')
|
||
|
||
sidebar.classList.remove('hide')
|
||
|
||
content.classList.remove('extend')
|
||
|
||
} else { // hide
|
||
|
||
sidebar_toggle.classList.add('extend')
|
||
|
||
sidebar.classList.add('hide')
|
||
|
||
content.classList.add('extend')
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
function open_sidebar() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.add('show')
|
||
|
||
overlay.classList.add('show')
|
||
|
||
}
|
||
|
||
function hide_canvas() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.remove('show')
|
||
|
||
overlay.classList.remove('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
</script>
|
||
|
||
|
||
|
||
<div class="off-canvas-content">
|
||
|
||
<div class="columns">
|
||
|
||
<div class="column col-12 col-lg-12">
|
||
|
||
<div class="book-navbar">
|
||
|
||
<!-- For Responsive Layout -->
|
||
|
||
<header class="navbar">
|
||
|
||
<section class="navbar-section">
|
||
|
||
<a onclick="open_sidebar()">
|
||
|
||
<i class="icon icon-menu"></i>
|
||
|
||
</a>
|
||
|
||
</section>
|
||
|
||
</header>
|
||
|
||
</div>
|
||
|
||
<div class="book-content" style="max-width: 960px; margin: 0 auto;
|
||
|
||
overflow-x: auto;
|
||
|
||
overflow-y: hidden;">
|
||
|
||
<div class="book-post">
|
||
|
||
<p id="tip" align="center"></p>
|
||
|
||
<div><h1>09 性能剖析:如何补足分布式追踪短板?</h1>
|
||
|
||
<p>在上一节,我带你了解了链路追踪的基本概念,它是可观测性中必不可少的一环。这一课时,我会带你了解链路追踪中的短板是什么,又该如何去处理。</p>
|
||
|
||
<h3>链路追踪的短板</h3>
|
||
|
||
<p>在上一课时我提到,链路追踪中最小的单位是 Span,每一个 Span 代表一个操作,但这样存在一个问题,粒度还是太粗了。如何解决粒度太粗的问题呢?在链路追踪的实现中,我们一般会有 2 种方式。</p>
|
||
|
||
<ol>
|
||
|
||
<li><strong>代码埋点</strong>:在代码中预埋点,通过侵入式的方式记录链路数据。</li>
|
||
|
||
<li><strong>字节码增强</strong>:在字节码生成之后,再对其进行修改,从而给它功能。比如像 Java 这类语言,就可以通过字节码增强,而不是人工侵入式的方式记录链路数据。</li>
|
||
|
||
</ol>
|
||
|
||
<p><strong>这两点的区别在于,是否是侵入式的</strong>。埋点的方式虽然灵活,<strong>但是依赖性较高</strong>;字节码增强的形式不需要代码层介入,但仅能支持一部分应用框架。这两种方式能够实现链路追踪,并且可以大面积的使用 ,因为框架都是通用的。</p>
|
||
|
||
<p><strong>但通用的链路监控方案的实现方式都逃不过面向切面编程,因为它们只能做到框架级别</strong>,而这又会导致我们可能会遇到程序执行缓慢或者不稳定的情况,却无法查询到原因。我之前在链路监控时,发现一个接口内部有很长的一段时间内没有 Span 信息。虽然我知道了这个情况,但还是无能为力。</p>
|
||
|
||
<p>这时候我们一般只能通过<strong>在业务中手动增加埋点</strong>的方式来进行更细粒度的 Span 开发,这种方法也有几个缺点:</p>
|
||
|
||
<ol>
|
||
|
||
<li><strong>增加埋点成本高,很难全面覆盖</strong>。这样的方式只适用于具体的业务场景,如果其他的业务场景也存在类似的问题,就需要在其他的业务场景中再次埋点。如果一次没有把需要埋点的位置加全,还可能会涉及多次的上线,降低了系统的稳定性。
|
||
|
||
大量的埋点同时还会<strong>占用 CPU 和内存的资源</strong>。虽然每个埋点的性能损耗都不高,但是随着项目不停地迭代,埋点的数量会越来越多,性能损耗也会越来越多,长期下来就会对系统性能造成持续的影响。而且埋点后还需要开发人员定期移除掉不需要的埋点信息,极其浪费人员和时间成本。</li>
|
||
|
||
<li><strong>动态增加埋点技术不可靠</strong>。既然人工难处理,那让系统自动处理呢?这便有了动态增加埋点技术。它是<strong>在某个特定的包下,对每个方法的执行都增加埋点,无须手动修改代码</strong>。但是这一技术的问题也很明显:因为会给所有的地方都增加埋点,性能的损耗可能比人工的方式更为严重,甚至因为埋点过多导致内存增长,最终造成系统崩溃,影响线上程序运行。</li>
|
||
|
||
<li>即使我们通过一个十分合理的方式解决了上面的两个问题,我们也只能在业务级别做埋点,没有办法细入 JDK 中的某个场景。因为如果要对 JDK 中的某个方法做埋点的话,可能会造成巨大的延迟风险。</li>
|
||
|
||
</ol>
|
||
|
||
<p>对于一些 JDK 中的底层工具类,如果我们处理不当,还会出现很多不可预料的错误,那时候就不光是影响程序效率这么简单的问题。</p>
|
||
|
||
<h3>性能剖析介绍</h3>
|
||
|
||
<p>那么对于以上的 3 个问题,要怎样解决呢?其实你可以换个角度去思考,不要被链路追踪中 Span 的思想框架给限制住。我在这里给出 2 点建议:</p>
|
||
|
||
<ol>
|
||
|
||
<li><strong>在编程语言中,基本所有的代码都是运行在线程中的,并且大多数的情况下都是单线程</strong>,比如 HTTP 或者 RPC 等框架接收到请求之后都会交给单独的线程去处理。</li>
|
||
|
||
<li><strong>大多数的编程模型是基于线程的这一个概念去实现很多功能的</strong>,无论是现成的框架,还是底层的 JDK,比如 Dubbo 中的 RPCContext、Java 中线程安全的随机数生成器 ThreadLocalRandom。</li>
|
||
|
||
</ol>
|
||
|
||
<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>在这张图中,<strong>第一行代表线程进行快照的周期情况</strong>,每一个周期都可以认为是一段时间,比如 10ms、100ms。周期的时间长短,决定了对程序性能影响的大小。因为在进行线程快照时程序会暂停,当快照完成后才会继续进行操作。</p>
|
||
|
||
<p><strong>第二行则代表我们需要进行观测的方法的执行时间</strong>,线程快照只能做到周期性的快照获取。虽然可能并不能完全匹配,但通过这种方式,相对来说已经很精准了。</p>
|
||
|
||
<p>性能剖析与埋点相比,有以下几个优势:</p>
|
||
|
||
<ol>
|
||
|
||
<li><strong>整个过程中不涉及任何的埋点,所以性能损耗是相对稳定可控的</strong>,不用再担心因为埋点过多导致的业务系统的压力和性能风险。同时因为不涉及埋点,所以不再需要重复的增加或者删除埋点,大大节省了人力开发和上线的时间。</li>
|
||
|
||
<li><strong>无须再担心是底层 JDK 还是框架,或是业务代码</strong>,在运行时它们都是代码,不需要对它们进行区分。</li>
|
||
|
||
<li>结合当前链路中的上下文信息,<strong>只需要对指定的链路执行时间之内进行性能剖析</strong>,而不用对每个线程定时进行线程快照操作,因此减少了程序性能的损耗。</li>
|
||
|
||
<li>通过<strong>精准到代码行级别</strong>的方式,可以定位执行缓慢的原因,减少研发定位问题的时间。</li>
|
||
|
||
<li><strong>只在需要的时候才使用</strong>,平时不会使用到这样的功能,因此性能损耗也是稳定的。</li>
|
||
|
||
</ol>
|
||
|
||
<h3>性能剖析展现方式</h3>
|
||
|
||
<p>假如我们已经利用性能剖析工具获取到了链路中的线程快照列表了,那么我们该怎么去展现它们呢?</p>
|
||
|
||
<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>CPU 火焰图</p>
|
||
|
||
<p>在 CPU 火焰图中,每一个方格代表一个方法栈帧,方格的长度则代表它的执行时间,所以方格越长就说明该栈帧执行的时间越长。火焰图中在某一个方格中增高一层,就说明是这个方法栈帧中,又调用了某个方法的栈帧。最顶层的函数,是叶子函数。叶子函数的方格越宽,说明这个方法在这里的执行耗时越长。</p>
|
||
|
||
<p>如果觉得上面的火焰图太复杂的话,我们可以看一张简化的图,如下:</p>
|
||
|
||
<p><img src="assets/CgqCHl9HZ2mAaGjhAACnwkzLj5I930.png" alt="Drawing 3.png" /></p>
|
||
|
||
<p>图中,a 方法是执行的方法,可以看出来,其中 g 方法是执行时间相对较长的。</p>
|
||
|
||
<p>无论是火焰图,还是这张简化的图,它们都通过图形的方式,让我们能够快速定位到执行缓慢的原因。但是这种的方式也存在一些问题:</p>
|
||
|
||
<ol>
|
||
|
||
<li><strong>不方便查看函数名称等信息</strong>。虽然我们可以做一些交互上的处理,比如浮动时展示,但如果我们在进行栈帧跟踪,查询还是不方便。</li>
|
||
|
||
<li><strong>很难发现非叶子节点的问题</strong>。我们在简化图中可以发现,我们在 d 方法中除了 e 和 f 方法的调用以外,其实 d 方法还有一段的时间是自己消耗的,并且没有被处理掉,这一问题在火焰图中会更加明显。</li>
|
||
|
||
</ol>
|
||
|
||
<h4>树形图</h4>
|
||
|
||
<p><img src="assets/Ciqc1F9HZ6SAAuuSAACg533klAQ565.png" alt="Drawing 5.png" /></p>
|
||
|
||
<p>为了解决这 2 个问题,就有了另外一种展现方式,那就是<strong>树形图</strong>。树形图就是<strong>将方法的调用堆栈通过树形图的形式展现出来</strong>。这对于开发人员来说相对直观,因为你可以通过树形图的形式快速查看整体的调用情况,并且针对火焰图中的问题,树形图也有很好的解决方法:</p>
|
||
|
||
<ul>
|
||
|
||
<li>使用这样的形式,栈帧很容易识别,并且之间的调用关系也很容易理解。</li>
|
||
|
||
<li>对于非叶子节点的耗时,可以直接查看到自身耗时的数据。</li>
|
||
|
||
</ul>
|
||
|
||
<p>但树形图也有一些自己的问题:</p>
|
||
|
||
<ul>
|
||
|
||
<li><strong>内容显示相对较长</strong>,在方法栈相对复杂的情况下,这一问题会更为突出。</li>
|
||
|
||
<li><strong>栈帧深度较多时,容易显示不全信息</strong>。</li>
|
||
|
||
</ul>
|
||
|
||
<p>所以在树形图中一般会配合前端的展示,比如只显示 topN 中耗时较高的内容,或者支持搜索功能。</p>
|
||
|
||
<p>火焰图和树形图没有绝对的好坏之分,只是相对应的<strong>侧重点不同</strong>:</p>
|
||
|
||
<ol>
|
||
|
||
<li><strong>火焰图更擅长快速展现出问题所在</strong>,能够最快速地找到影响最大的问题的原因。</li>
|
||
|
||
<li><strong>树形图则更倾向于具体展示出栈的执行流程</strong>,通过执行流程和耗时统计指标来定位问题的原因。</li>
|
||
|
||
</ol>
|
||
|
||
<h3>结语</h3>
|
||
|
||
<p>相信通过这篇文章的讲解,你应该对链路追踪的短板,以及如何通过性能剖析去解决这些短板有了一个整体的思路。那么,你认为怎么样将这两者结合才是最方便的呢?你在工作中又遇到过什么和链路追踪相关的问题呢?欢迎你在留言区分享你的思考。</p>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div>
|
||
|
||
<div style="float: left">
|
||
|
||
<a href="/专栏/分布式链路追踪实战-完/08 链路监控:为什么对于系统而言必不可少?.md.html">上一页</a>
|
||
|
||
</div>
|
||
|
||
<div style="float: right">
|
||
|
||
<a href="/专栏/分布式链路追踪实战-完/10 链路分析:除了观测链路,还能做什么?.md.html">下一页</a>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
|
||
|
||
</div>
|
||
|
||
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997725cae03cfa","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
|
||
|
||
</body>
|
||
|
||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||
|
||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
|
||
|
||
<script>
|
||
|
||
window.dataLayer = window.dataLayer || [];
|
||
|
||
|
||
|
||
function gtag() {
|
||
|
||
dataLayer.push(arguments);
|
||
|
||
}
|
||
|
||
|
||
|
||
gtag('js', new Date());
|
||
|
||
gtag('config', 'G-NPSEEVD756');
|
||
|
||
var path = window.location.pathname
|
||
|
||
var cookie = getCookie("lastPath");
|
||
|
||
console.log(path)
|
||
|
||
if (path.replace("/", "") === "") {
|
||
|
||
if (cookie.replace("/", "") !== "") {
|
||
|
||
console.log(cookie)
|
||
|
||
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
|
||
|
||
}
|
||
|
||
} else {
|
||
|
||
setCookie("lastPath", path)
|
||
|
||
}
|
||
|
||
|
||
|
||
function setCookie(cname, cvalue) {
|
||
|
||
var d = new Date();
|
||
|
||
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
|
||
|
||
var expires = "expires=" + d.toGMTString();
|
||
|
||
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
|
||
|
||
}
|
||
|
||
|
||
|
||
function getCookie(cname) {
|
||
|
||
var name = cname + "=";
|
||
|
||
var ca = document.cookie.split(';');
|
||
|
||
for (var i = 0; i < ca.length; i++) {
|
||
|
||
var c = ca[i].trim();
|
||
|
||
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
|
||
|
||
}
|
||
|
||
return "";
|
||
|
||
}
|
||
|
||
|
||
|
||
</script>
|
||
|
||
|
||
|
||
</html>
|
||
|