• 分布式链路追踪系统Skywalking的部署和应用


    一,背景

    随着业务的扩张,系统变得越来越复杂,由前端、app、api、微服务、数据库、缓存、消息队列、关系数据库、列式数据库等构成了繁杂的分布式网络。 当出现一个调用失败的问题时,要定位异常在哪个服务,需要进入每一个服务里看日志,这个过程的复杂度和工作量是不可想象的。

    ​当讲到大型微服务系统时, 下面这张图经常被引用到。

    为了解决故障定位难,链路梳理难,容量预估难的问题,一般引入APM体系统来解决,而链路追踪则是APM中尤为重要的一环。有了链路追踪, 我们可以做到:

    1. 请求链路追踪,故障快速定位:可以通过调用链路并结合业务日志快速定位问题所在
    2. 可视化:展示各阶段耗时, 进行性能瓶颈分析
    3. 应用拓扑:梳理服务依赖关系并加以优化
    4. 数据分析:汇总分析用户的行为路径


    二,术语

    APM: 应用系统的实时监控,用于实现性能管理和故障管理
    Dapper:  google一篇论文里提到, 主要详谈分布式跟踪服务的设计
    prometheus: 服务监控系统
    grafana:度量分析和可视化工具
    zipkin:分布式的跟踪系统
    cat: 大众点评开发的实时应用监控平台
    skywalking:Apache顶级项目的链路跟踪系统
    ELK:Elasticsearch、Logstash和Kibana三大开源框架
    EFK:elasticsearch、filebeat和kibana
    Filebeat:golang实现的日志采集器

    三,APM主要解决的问题

    1. Metrics集中式度量系统 (prometheus+grafana),用于可聚合的数据
    2. Tracing分布式全链接追踪系统 (zipkin,cat,skywalking等),用于请求范围内的信息
    3. Loging集中日志系统 (ELK, EFK, Filebeat+ELK),用于记录离散的事件

    三者有相互重叠的部分


    四,技术选型

    阿里的鹰眼, 点评的cat:闭源或侵入式
    zipkin:可视化方面做得太简单
    这个三个框架从技术选型上排除掉。下面主要从pinpoint和skywalking这两个作对对比
     

    对比项PinpointSkywalking
    opentracing
    协议thriftgRPC
    存储hbase+mysqles,mysql,h2,tidb
    ui丰富度一般
    代码侵入式
    性能损耗
    部署难度

    通过对比可以看到,Pinpoint和Skywalking不相上下,各有优劣,从界面、操作,集成方式来说,Pinpoint更好,  不过因为种种不得已的原因,我们今天还是聚焦在Skywalking上,它的优点是部署难度低,监控范围广、维度多,对代码侵入少,系统性能损失低,还支持接入 ELK 进行存储展示。

    其他限制
    1.只支持已知的代理,如果使用的中间件还未被支持,需要自己写插件。
    2.跨线程的场景不支持自动代理,比如任务分配,任务池,批处理的场景。

    五,skywalking原理

    什么是span
    下图描述的是树结构的Span集合,表示一次完整的跟踪,从请求到服务器开始,服务器返回response结束,跟踪每次rpc调用的耗时,存在唯一标识trace_id。

    什么是skywalking

    • 客户端是通过Agent,与Collector相连接,然后Collector将数据存储在Es中。
    • 监控页面是连接的Collector,Collector从Es中将数据查询出来。
    • 直接和数据打交道的是Collector。


    六,部署

    1, 部署elk

    docker run -dit --name elk \
        -p 5601:5601 \
        -p 9200:9200 \
        -p 5044:5044 \
        -v /data/elk-data:/var/lib/elasticsearch \
        -v /etc/localtime:/etc/localtime \
        sebp/elk:700

    2,安装Skywalking server 


    docker run --name oap --restart always -d \
    -e TZ=Asia/Shanghai \
    -p 12800:12800 \
    -p 11800:11800 \
    --link elk:es7 \
    -e SW_STORAGE=elasticsearch7 \
    -e SW_STORAGE_ES_CLUSTER_NODES=es7:9200 \
    apache/skywalking-oap-server:8.2.0-es7

    docker run -d --restart always --name skywalking-ui \
    -e TZ=Asia/Shanghai \
    -p 18080:8080 \
    --link oap:oap \
    -e SW_OAP_ADDRESS=oap:12800 \
    apache/skywalking-ui:8.2.0

    访问地址:http://服务器IP/18080

    七,无侵入跟踪采集

    注意:Skywalking并不是无侵入的,只是可以用无侵入这种来用,实际上要用traceId查询的话,还是要侵入代码,这是它不安全的地方!!!

    1,如果是准备用无侵入的方式接入采集的话,agent-jar包所在的下载地址

    1, 下载:wget  https://dlcdn.apache.org/skywalking/java-agent/9.0.0/apache-skywalking-java-agent-9.0.0.tgz
    2, 解压缩: tar -zxvf apache-skywalking-java-agent-9.0.0.tgz

    3, 在解压后的文件夹中有 skywalking-agent.jar

    2,运行jar时,加入jvm选项

    -javaagent:\path\skywalking-agent.jar -Dskywalking.agent.service_name=${service_name} -Dskywalking.collector.backend_service=${ip}:{port}
    注意:上面一行要放在 -jar选项之前

    例如:
    java -javaagent:/root/apm/skywalking-agent.jar -Dskywalking.agent.service_name=myName -Dskywalking.collector.backend_service=127.0.0.1:11800 -jar xxxx.jar

    八,侵入式记录traceid到日志

    1,引入pom

    1. <dependency>
    2. <groupId>org.apache.skywalking</groupId>
    3. <artifactId>apm-toolkit-trace</artifactId>
    4. <version>6.5.0</version>
    5. </dependency>

    2, 修改log4j.xml的pattern

    日志展现结果, 有了traceid,parrent spanid, spanid, 使得有ELK统一日志系统把具体业务

    1. pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] %-5level [%X{TRACE_ID},%X{SPAN_ID}] - %msg%xEx%n"/>

    3, filter的实现

    1. @Component
    2. public class TraceIdFilter extends OncePerRequestFilter {
    3.     private static final String TRACE_ID = "TRACE_ID";
    4.     private static final String SPAN_ID = "SPAN_ID";
    5.     private static final String SPAN_PID = "SPAN_PID";
    6.     @Override
    7.     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
    8.             throws ServletException, IOException {
    9.         String traceId = TraceContext.traceId();
    10.         if(null == traceId){
    11.             chain.doFilter(request, response);
    12.             return;
    13.         }
    14.         String spanPid = request.getHeader(SPAN_PID);
    15.         // 生成spanId
    16.         String spanId;
    17.         if(spanPid == null){
    18.             spanPid = "0";
    19.             spanId = "1";
    20.         }
    21.         else {
    22.             spanId = String.valueOf(Integer.valueOf(spanPid) +1);
    23.         }
    24.         SpanContext.getContext().initContext(spanId);
    25.         MDC.put(TRACE_ID, traceId);
    26.         MDC.put(SPAN_ID, spanId);
    27.         MDC.put(SPAN_PID, spanPid);
    28.         chain.doFilter(request, response);
    29.     }
    30.     @Override
    31.     public void destroy() {
    32.         MDC.clear();
    33.     }
    34. }


    4,  feign拦截器的实现

    1. public class FeignClientInterceptor implements RequestInterceptor {
    2.     private static final String SPAN_PID = "SPAN_PID";
    3.     @Override
    4.     public void apply(RequestTemplate requestTemplate) {
    5.         try {
    6.             SpanContext spanContext = SpanContext.getContext();
    7.             if (Objects.nonNull(spanContext)) {
    8.                 requestTemplate.header(SPAN_PID, spanContext.getSpanId());
    9.             }
    10.         } catch (Exception e) {
    11.             e.printStackTrace();
    12.         }
    13.     }
    14. }
    15. SpanContext
    16. @Data
    17. public class SpanContext {
    18.     private String spanId;
    19.     private static ThreadLocal<SpanContext> LOCAL = new ThreadLocal<>();
    20.     public static SpanContext getContext() {
    21.         SpanContext context = LOCAL.get();
    22.         if (Objects.isNull(context)) {
    23.             context = new SpanContext();
    24.             LOCAL.set(context);
    25.         }
    26.         return context;
    27.     }
    28.     /**
    29.      * 初始化
    30.      */
    31.     public void initContext(String spanId){
    32.         this.spanId = spanId;
    33.     }
    34. }

    九,UI界面

    简单记录一下,实际上我是不喜欢这个工具,没有PP好用!

    码字不易,记得点赞关注哟!

  • 相关阅读:
    数据和埋点的通俗解释
    警惕:这本期刊已被剔除,EI期刊目录更新
    HarmonyOS鸿蒙学习笔记(4)Tabs模仿安卓ViewPager+Fragment的效果
    【UE】连续射击Niagara特效
    nodejs 简单介绍一下四种流(stream)的知识
    【日志】@Slf4j 注解记录日志
    代码随想录笔记_动态规划_718最长重复子数组
    python+django+vue个人简历管理系统
    java81-静态代码块
    2023最新SSM计算机毕业设计选题大全(附源码+LW)之java校园服装租赁系统864e2
  • 原文地址:https://blog.csdn.net/wangerrong/article/details/133930197