Skywalking 学习

定位

An APM(application performance monitor) system, especially designed for microservices, cloud native and container-based architectures.

应用性能监控系统,主要针对微服务云原生容器化架构系统。

功能

  1. 支持多种语言Agent
  2. 轻量高效,无需部署大数据平台和大量服务器
  3. 模块化,UI、存储、集群管理都有多种机制可选
  4. 支持 Webhook 告警
  5. 自带可视化

架构

Untitled

包含采集(Agent)、收集处理(OAP – Observability Analysis Platform)、存储、UI 四个部分。

处理流程

Untitled

如何采集?

agent + 插件化,例如dubbo采集由dubbo-plugin支持,还有tomcat-plugin等等,实现代码无侵入、拓展性好可插拔

如何开发插件?

官方开发插件 Doc

简言之 skywalking 利用的是 bytebuddy 动态生成字节码技术和 AOP 概念,通过生成对应组件的字节码暴露 hook(进行方法拦截等),以继承的方式引用自定义拦截器,调用自定义拦截器组织组件的调用信息等,由 SW 进行传输、采集。

bytebuddy 较之 ASM,屏蔽了字节码底层的细节,封装了提供易上手的 API,降低使用成本。

开发者只需要声明需要拦截的类名(可通过规则匹配)以及定义拦截器、实现中的逻辑即可,拦截的维度包括类静态方法、实例方法和构造方法。

参考 dubbo-2.7.x-plugin 插件:
org.apache.skywalking.apm.plugin.asf.dubbo.DubboInstrumentation

// 继承 ClassInstanceMethodsEnhancePluginDefine 说明只增强非静态方法
public class DubboInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {

        // 声明要增强的组件具体的 class,用于匹配要拦截目标类
    private static final String ENHANCE_CLASS = "org.apache.dubbo.monitor.support.MonitorFilter";

        // 声明拦截器类,包含具体要执行的拦截逻辑
    private static final String INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.asf.dubbo.DubboInterceptor";
...
        @Override
        public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
                // 不拦截构造器
        return null;
    }

    @Override
    public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
        return new InstanceMethodsInterceptPoint[] {
            new InstanceMethodsInterceptPoint() {
                @Override
                public ElementMatcher<MethodDescription> getMethodsMatcher() {
                                        // 声明拦截方法名为 invoke 的方法,也是用于匹配
                    return named("invoke");
                }

                @Override
                public String getMethodsInterceptor() {
                                        // 拦截器类名
                    return INTERCEPT_CLASS;
                }

                @Override
                public boolean isOverrideArgs() {
                    return false;
                }
            }
        };

org.apache.skywalking.apm.plugin.asf.dubbo.DubboInterceptor

public class DubboInterceptor implements InstanceMethodsAroundInterceptor {

        @Override
    public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
                             MethodInterceptResult result) throws Throwable {
        Invoker invoker = (Invoker) allArguments[0];
        Invocation invocation = (Invocation) allArguments[1];
        RpcContext rpcContext = RpcContext.getContext();
        boolean isConsumer = rpcContext.isConsumerSide();
        URL requestURL = invoker.getUrl();

        AbstractSpan span;

        final String host = requestURL.getHost();
        final int port = requestURL.getPort();

        boolean needCollectArguments;
        int argumentsLengthThreshold;
        if (isConsumer) {
            final ContextCarrier contextCarrier = new ContextCarrier();
            span = ContextManager.createExitSpan(generateOperationName(requestURL, invocation), contextCarrier, host + ":" + port);
            //invocation.getAttachments().put("contextData", contextDataStr);
            //@see https://github.com/alibaba/dubbo/blob/dubbo-2.5.3/dubbo-rpc/dubbo-rpc-api/src/main/java/com/alibaba/dubbo/rpc/RpcInvocation.java#L154-L161
            CarrierItem next = contextCarrier.items();
            while (next.hasNext()) {
                next = next.next();
                // 消费端:利用 dubbo attachment 传递 trace 等信息 (其他组件也是类似 例如 httpClient 通过 header 传递、 RocketMq 通过 properties 传递等等)
                rpcContext.setAttachment(next.getHeadKey(), next.getHeadValue());
                if (invocation.getAttachments().containsKey(next.getHeadKey())) {
                    invocation.getAttachments().remove(next.getHeadKey());
                }
            }
            needCollectArguments = DubboPluginConfig.Plugin.Dubbo.COLLECT_CONSUMER_ARGUMENTS;
            argumentsLengthThreshold = DubboPluginConfig.Plugin.Dubbo.CONSUMER_ARGUMENTS_LENGTH_THRESHOLD;
        } else {
            ContextCarrier contextCarrier = new ContextCarrier();
            CarrierItem next = contextCarrier.items();
            while (next.hasNext()) {
                next = next.next();
                 // 服务端:从 dubbo attachment 获取 trace 等信息
                next.setHeadValue(rpcContext.getAttachment(next.getHeadKey()));
            }

            span = ContextManager.createEntrySpan(generateOperationName(requestURL, invocation), contextCarrier);
            span.setPeer(rpcContext.getRemoteAddressString());
            needCollectArguments = DubboPluginConfig.Plugin.Dubbo.COLLECT_PROVIDER_ARGUMENTS;
            argumentsLengthThreshold = DubboPluginConfig.Plugin.Dubbo.PROVIDER_ARGUMENTS_LENGTH_THRESHOLD;
        }

        Tags.URL.set(span, generateRequestURL(requestURL, invocation));
        collectArguments(needCollectArguments, argumentsLengthThreshold, span, invocation);
        span.setComponent(ComponentsDefine.DUBBO);
        SpanLayer.asRPCFramework(span);
        }

        @Override
    public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
                             Object ret) throws Throwable {
        ...
                dealException()
                ContextManager.stopSpan();
        }
}

目前 skywalking-java 有 v1, v2 两套 API 用于自定义插件拦截器,v2 增加了 MethodInvocationContext 用于在 beforeMethod 和 afterMethod/handleMethodException 间传递变量,高并发场景下性能更优

traceId 生成原理

采用类似 snowflow 算法本地生成,相比分布式生成需要网络请求,性能更高。但是需要解决时钟回拨问题,采用的方案是每次记录生成的时间戳,下次生成比较上一次的时间戳,如果小于上次时间,则使用另一个自增字段作为时间戳。

Process_ID(random).Current_Thread_Id.Timestamp * 10000 + Seq_Num (ThreadLocal)

Untitled

采样频率可配置

全部采样在数据量较大的情况下可能会影响性能,所以默认设置了三秒采样三次,并且如果发现上游携带了 TraceContext ,那么会强制下游也采集,保证采样了的链路的完整性。

数据模型

Trace (仅是逻辑概念,通过多个 TraceSegment 中属性 relatedGlobalTraceId 串联维护)

    |- TraceSegment 1

        |- Span 1

        |- Span 2

    |- TraceSegment 2

对比

  • 相比于 zipkin(twitter开源)、pinpoint
    类别 skywalking zipkin pinpoint cat
    接入方式 agent,代码无侵入 引入依赖 agent 引入依赖(手动埋点)
    agent到collector的协议 grpc http, mq thrit http/tcp
    颗粒度 方法级 类级 方法级 代码级
    报警
    安装部署 较复杂 简单 复杂 较复杂
    性能影响
    存储 es, h2 (默认es) cassandra, es, mysql hbase mysql, hdfs
    支持语言 Java/C/C++/Python/Go/Node.js/PHP/.Net/nginx Java Java Java/C/C++/Python/Go/Node.js
    Trace查询
  • 相比于 Prometheus

    a monitoring system and time series database.

    简单地理解 Prometheus,它是一个时序数据库,按照时间收集了很多 metric(指标)。

    metric 可以理解为带标签的 key-value 键值对,它形如 key{label1="...", label2="..."} => 100,也就是说 Prometheus 按时间按类型收集了很多值,排列起来最终就是监控数据。具体的格式可参照下图(图片来源:《剖析Prometheus的内部存储机制》):

    Untitled

    这种 metric 数据结构,占存储空间很小,性能很高,兼容适配也很简单,相比其他 APM 工具根据日志进行解析要优越很多。

    主要区别

    1. Prometheus 收集数据基于 pull 模式,通过HTTP周期性抓取被监控组件的状态,任意组件只要提供对应的HTTP接口并且符合Prometheus定义的数据格式,就可以接入Prometheus监控。
    2. Prometheus 有自己的语言,叫做 PromQL,用于查询监控数据。(Skywalking(9.4.0开始)提供了 PromQL 服务,已经支持 PromQL 的第三方系统或可视化平台(如 Grafana),可以通过它获取指标。)
    3. UI 需要依赖其他组件 例如 Grafana
    4. Skywalking接入简单,Prometheus接入需要引入依赖帮助提供 exporter,例如 micrometer
    5. sw适合快速搭建监控系统,pm 更加灵活,拓展性更强。
    6. 另外 sw 是java编写,pm 是go语言编写

可观测系统分类

Untitled

类型 Metrics Tracing Logging
含义 指标聚合(例如某段时间qps吞吐量,cpu负载等等) 系统调用链追踪 日志采集、存储、分析
系统 Prometheus, Zabbix Cat, Zipkin, Skywalking, Google Dapper ELK

各种监控系统能力并不是完全隔离,而是会存在交叉,只是侧重点不同,但最终发展方向是实现全面、即三种能力的整合。

例如 Skywalking 其实同时具备三者能力,但重点深入 Tracing and Metrics。
SW 目前实现自定义指标接入 以及 PromQL查询并集成Grafana看板

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

Scroll to Top