使用opentelemetry 搭建新一代可视化分布式监控系统

简书 · · 1525 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

使用opentelemetry 搭建新一代可视化分布式监控系统

1、分布式监控系统介绍

随着SOA,微服务架构及PaaS,Devops等技术的兴起,线上问题的追踪和排查变得更加困难。对线上业务的可观测性得到了越来越多企业的重视,由此涌现出了许多优秀的链路追踪及服务监控中间件。比较流行的有Spring Cloud全家桶自带的Zipkin,点评的CAT, 华为的skywalking,Uber的Jaeger, naver的Pinpoint。

一个典型的应用,通常有三种类型的数据需要被监控系统记录:Metric, logs and traces。让我们先了解下它们都是什么。

Metrics

提供进行运行时的指标信息。比如CPU使用率,内存使用情况,GC情况,网站流量等。

Logging

可以监控程序进程中的日志,比如集成Log4j记录的日志,或者程序运行中发生的事件或通知。

Tracing

也叫做分布式追踪,包含请求中每个子操作的开始和结束时间,传递的参数,请求间的调用链路,请求在各个链路上的耗时等信息。Tracing可以包含消息发送和接收,数据库访问,负载均衡等各种信息,让我们可以深入了解请求的执行情况。Tracing为我们提供了获取请求的时间主要消耗在哪里,请求的参数都是什么,如果发生了异常,那么异常是在哪个环节产生的等能力。

 

2、opentelemetry简介

 

opentelemetry是一款数据收集中间件。我们可以使用它来生成,收集和导出监测数据(Metrics,Logs and traces),这些数据可供支持OpenTelemetry的中间件存储,查询和显示,用以实现数据观测,性能分析,系统监控,服务告警等能力。

opentelemetry项目开始于2019年,旨在提供基于云环境的可观测性软件的标准化方案,提供与三方无关的监控服务体系。项目迄今为止已获得了Zipkin, Jaeger, skywalking, Prometheus等众多知名中间件的支持。

3、sample项目

本例中,我们使用spring cloud搭建一个简单的微服务,来体验下如何使用opentelemetry来进行系统监控,并在两个不同的监控系统(Zipkin,Jaeger)进行快速切换。项目由2个微服务,2个可视化监控系统,并使用opentelemetry 来集成微服务和监控系统。

  • gateway-service -使用spring cloud gateway搭建的服务网关
  • cloud-user-service -用户微服务,使用Spring boot + spring mvc
  • Zipkin - Zipkin监控系统服务端
  • Jaeger - Jaeger监控系统服务端

4、使用opentelemetry 集成Zipkin

示例中使用到的组件的版本:
java: 1.8
spring-cloud: 2020.0.2
spring-boot: 2.4.5
opentelemetry: 1.1.0
grpc: 1.36.1

4.1、cloud-user-service服务maven配置

引入Spring cloud 和 opentelemetry

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>io.opentelemetry</groupId>
            <artifactId>opentelemetry-bom</artifactId>
            <version>${opentelemetry.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

加入opentelemetry依赖项

<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-api</artifactId>
</dependency>
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-sdk</artifactId>
</dependency>
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-semconv</artifactId>
    <version>1.1.0-alpha</version>
</dependency>
<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-protobuf</artifactId>
    <version>${grpc.version}</version>
</dependency>
<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-netty-shaded</artifactId>
    <version>${grpc.version}</version>
</dependency>
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-exporter-zipkin</artifactId>
</dependency>

4.2、配置opentelemetry

@Configuration
public class TraceConfig {
    private static final String ENDPOINT_V2_SPANS = "/api/v2/spans";
    private final AppConfig appConfig;

    @Autowired
    public TraceConfig(AppConfig appConfig) {
        this.appConfig = appConfig;
    }

    @Bean
    public OpenTelemetry openTelemetry() {
        SpanProcessor spanProcessor = getOtlpProcessor();
        Resource serviceNameResource = Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, appConfig.getApplicationName()));

        // Set to process the spans by the Zipkin Exporter
        SdkTracerProvider tracerProvider =
                SdkTracerProvider.builder()
                        .addSpanProcessor(spanProcessor)
                        .setResource(Resource.getDefault().merge(serviceNameResource))
                        .build();
        OpenTelemetrySdk openTelemetry =
                OpenTelemetrySdk.builder().setTracerProvider(tracerProvider)
                        .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance()))
                        .buildAndRegisterGlobal();

        // add a shutdown hook to shut down the SDK
        Runtime.getRuntime().addShutdownHook(new Thread(tracerProvider::close));

        // return the configured instance so it can be used for instrumentation.
        return openTelemetry;
    }

    private SpanProcessor getZipkinProcessor() {
        String host = "localhost";
        int port = 9411;
        String httpUrl = String.format("http://%s:%s", host, port);
        ZipkinSpanExporter zipkinExporter = ZipkinSpanExporter.builder().setEndpoint(httpUrl + ENDPOINT_V2_SPANS).build();
        return SimpleSpanProcessor.create(zipkinExporter);
    }
}

4.3、在cloud-user-service中,使用opentelemetry

当我们完成了配置后,就可以在spring boot项目中,通过autowired来使用opentelemetry。

接下来我们定制一个WebFilter来拦截所有的Http请求,并在Filter类中进行埋点。

@Component
public class TracingFilter implements Filter {
    private final AppConfig appConfig;
    private final OpenTelemetry openTelemetry;

    @Autowired
    public TracingFilter(AppConfig appConfig, OpenTelemetry openTelemetry) {
        this.appConfig = appConfig;
        this.openTelemetry = openTelemetry;
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest)servletRequest;
        Span span = getServerSpan(openTelemetry.getTracer(appConfig.getApplicationName()), httpServletRequest);
        try (Scope scope = span.makeCurrent()) {
            filterChain.doFilter(servletRequest, servletResponse);
        } catch (Exception ex) {
            span.setStatus(StatusCode.ERROR, "HTTP Code: " + ((HttpServletResponse)servletResponse).getStatus());
            span.recordException(ex);
            throw ex;
        } finally {
            span.end();
        }
    }

    private Span getServerSpan(Tracer tracer, HttpServletRequest httpServletRequest) {

        TextMapPropagator textMapPropagator = openTelemetry.getPropagators().getTextMapPropagator();
        Context context = textMapPropagator.extract(Context.current(), httpServletRequest, new TextMapGetter<HttpServletRequest>() {
            @Override
            public Iterable<String> keys(HttpServletRequest request) {
                List<String> headers = new ArrayList();
                for (Enumeration names = request.getHeaderNames(); names.hasMoreElements();) {
                    String name = (String)names.nextElement();
                    headers.add(name);
                }
                return headers;
            }

            @Override
            public String get(HttpServletRequest request, String s) {
                return request.getHeader(s);
            }
        });


        return tracer.spanBuilder(httpServletRequest.getRequestURI()).setParent(context).setSpanKind(SpanKind.SERVER).setAttribute(SemanticAttributes.HTTP_METHOD, httpServletRequest.getMethod()).startSpan();
    }
}

在示例代码中,我们实现了一个匿名类来从HttpServletRequest中解析tracing上下文信息。

在创建Span的同时,我们在Span中写入了Http请求的一些关键属性,并且为所有的异常做了跟踪记录。

4.4、编写服务代码

接下来我们通过一段简单的代码来模拟查询用户以及抛出异常

@GetMapping("/{id}")
public ResponseEntity<User> get(@PathVariable("id") Long id) {
    if (0 >= id) {
        throw new IllegalArgumentException("Illegal argument value");
    }
    return ResponseEntity.ok(userService.get(id));
}

4.5、配置gateway-service

我们使用和cloud-user-service同样的配置来配置gateway-service。

4.6、在gateway-service中,集成opentelemetry

这里和cloud-user-service有些不同,由于gateway-service是基于webflux构建的。我们这次使用WebFilter和GlobalFilter来拦截网关上的http请求。

在WebFilter中,添加opentelemetry来记录收到的http请求

@Override
public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
    ServerHttpRequest serverHttpRequest = serverWebExchange.getRequest();
    Span span = getServerSpan(openTelemetry.getTracer(appConfig.getApplicationName()), serverHttpRequest);
    Scope scope = span.makeCurrent();

    serverWebExchange.getResponse().getHeaders().add("traceId", span.getSpanContext().getTraceId());
    span.setAttribute("params", serverHttpRequest.getQueryParams().toString());

    return webFilterChain.filter(serverWebExchange)
            .doFinally((signalType) -> {
                scope.close();
                span.end();
            })
            .doOnError(span::recordException);
}

private Span getServerSpan(Tracer tracer, ServerHttpRequest serverHttpRequest) {
    return tracer.spanBuilder(serverHttpRequest.getPath().toString()).setNoParent().setSpanKind(SpanKind.SERVER).setAttribute(SemanticAttributes.HTTP_METHOD, serverHttpRequest.getMethod().name()).startSpan();
}
接下来在GlobalFilter中,记录路由到微服务的http请求

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain gatewayFilterChain) {
    Span span = getClientSpan(openTelemetry.getTracer(appConfig.getApplicationName()), exchange);
    Scope scope = span.makeCurrent();
    inject(exchange);
    return gatewayFilterChain.filter(exchange)
            .then(Mono.fromRunnable(() -> {
                        scope.close();
                        span.end();
                    })
            );
}

private void inject(ServerWebExchange serverWebExchange) {
    HttpHeaders httpHeaders = new HttpHeaders();
    TextMapPropagator textMapPropagator = openTelemetry.getPropagators().getTextMapPropagator();
    textMapPropagator.inject(Context.current(), httpHeaders, HttpHeaders::add);
    ServerHttpRequest request = serverWebExchange.getRequest().mutate()
            .headers(headers -> headers.addAll(httpHeaders))
            .build();
    serverWebExchange.mutate().request(request).build();
}

private Span getClientSpan(Tracer tracer, ServerWebExchange serverWebExchange) {
    ServerHttpRequest serverHttpRequest = serverWebExchange.getRequest();
    URI routeUri = serverWebExchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
    return tracer.spanBuilder(routeUri.getPath()).setSpanKind(SpanKind.CLIENT).setAttribute(SemanticAttributes.HTTP_METHOD, serverHttpRequest.getMethod().name()).startSpan();
}

为了传递tracing的上下文信息,我们需要调用inject方法,把tracing上下文信息写入到路由请求的头信息里面。

5、运行服务

现在,让我们访问网关http://localhost:8080/user/0 来观察Zipkin对于服务访问和异常的记录情况。

可以看到在Tracing方面,Zikin整体表现还不错,有异常的链路也使用红色做了标记。Zipkin没有打印出异常的堆栈信息,我们需要为此做额外的处理才行。

6、使用Jaeger对接opentelemetry

使用otlp exporter来替换之前使用的zipkin exporter。

<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>

在配置类中,使用otlp processor替换之前的zipkin processor。这样就完成了Zipkin到Jaeger的切换。

private SpanProcessor getOtlpProcessor(){
    OtlpGrpcSpanExporter spanExporter = OtlpGrpcSpanExporter.builder().setTimeout(2, TimeUnit.SECONDS).build();
    return BatchSpanProcessor.builder(spanExporter)
            .setScheduleDelay(100, TimeUnit.MILLISECONDS)
            .build();
}

7、再次运行服务

我们再次运行服务并访问网关http://localhost:8080/user/0 来观察Jaeger对于服务访问和异常的记录情况。

首先看主界面,Jaeger直接标记了请求中包含异常。

 

再看下访问的详情,Jaeger记录并显示了异常的堆栈信息。这对我们分析线上异常非常有帮助。

 

Jaeger提供的DAG图

 

对比Zipkin,Jaeger提供了更加丰富的功能和更美观的可视化界面。

8、总结

本文介绍了使用opentelemetry 来搭建监控系统,以及如何集成到Zipkin和Jaeger。

利用opentelemetry的标准化能力,我们可以方便地记录更加详细的链路监控信息。

opentelemetry自推出以来,得到了越来越多厂商的关注和支持。对于分布式监控系统这个新生事物,opentelemetry是否能成为最终的事实标准,让我们拭目以待。

References

opentelemetry官网

Metrics, tracing, and logging

5 Reasons why OpenTelemetry will boost Observability and Monitoring

Exporting Open Telemetry Data to Jaeger

©著作权归作者所有,转载或内容合作请联系作者

  • 人面猴

    序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...

    沈念sama阅读 89,919评论 1赞 192

  • 死咒

    序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...

    沈念sama阅读 34,228评论 1赞 163

  • 救了他两次的神仙让他今天三更去死

    文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...

    开封第一讲书人阅读 41,705评论 0赞 113

  • 道士缉凶录:失踪的卖姜人

    文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...

    开封第一讲书人阅读 22,572评论 0赞 92

  • 港岛之恋(遗憾婚礼)

    正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...

    茶点故事阅读 28,221评论 0赞 163

  • 恶毒庶女顶嫁案:这布局不是一般人想出来的

    文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...

    开封第一讲书人阅读 23,330评论 1赞 95

  • 城市分裂传说

    那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...

    沈念sama阅读 15,932评论 2赞 173

  • 双鸳鸯连环套:你想象不到人心有多黑

    文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...

    开封第一讲书人阅读 15,253评论 0赞 92

  • 父亲被人害死了,幕后凶手却是我最亲的人!

    想象着我的养父在大火中拼命挣扎,窒息,最后皮肤化为焦炭。我心中就已经是抑制不住地欢快,这就叫做以其人之道,还治其人...

    爱写小说的胖达阅读 13,817评论 5赞 126

  • 万荣杀人案实录

    序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...

    沈念sama阅读 17,308评论 0赞 140

  • 护林员之死

    正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...

    茶点故事阅读 15,670评论 1赞 132

  • 白月光启示录

    正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...

    茶点故事阅读 16,646评论 0赞 145

  • 惨遭霸总抛弃后,我靠赚来的钱成了富豪榜第一名

    白月光回国,霸总把我这个替身辞退。还一脸阴沉的警告我。[不要出现在思思面前, 不然我有一百种方法让你生不如死。]我...

    爱写小说的胖达阅读 11,254评论 0赞 19

  • 活死人

    序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...

    沈念sama阅读 14,190评论 2赞 132

  • 日本核电站爆炸内幕

    正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...

    茶点故事阅读 17,439评论 3赞 137

  • 男人毒药:我在死后第九天来索命

    文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...

    开封第一讲书人阅读 12,761评论 0赞 4

  • 一桩弑父案,背后竟有这般阴谋

    文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...

    开封第一讲书人阅读 13,162评论 0赞 87

  • 情欲美人皮

    我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...

    沈念sama阅读 18,177评论 2赞 154

  • 代替公主和亲

    正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...

    茶点故事阅读 18,507评论 2赞 146

推荐阅读更多精彩内容

本文来自:简书

感谢作者:简书

查看原文:使用opentelemetry 搭建新一代可视化分布式监控系统

1525 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传