热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

基于Dubbo与Zipkin的微服务调用链路监控解决方案

本文提出了一种基于Dubbo与Zipkin的微服务调用链路监控解决方案。通过抽象配置层,支持HTTP和Kafka两种数据上报方式,实现了灵活且高效的调用链路追踪。该方案不仅提升了系统的可维护性和扩展性,还为故障排查提供了强大的支持。

dubbo+zipkin调用链监控

图片描述(最多50字)
收集器抽象

由于zipkin支持http以及kafka两种方式上报数据,所以在配置上需要做下抽象。

AbstractZipkinCollectorConfiguration

主要是针对下面两种收集方式的一些配置上的定义,最核心的是Sender接口的定义,http与kafka是两类完全不同的实现。

public abstract Sender getSender();
其次是协助性的构造函数,主要是配合构建收集器所需要的一些参数。

zipkinUrl
如果是http收集,那么对应的是zipkin api域名,如果是kafka,对应的是kafka集群的地址

topic
仅在收集方式为kafka是有效,http时传空值即可。

public AbstractZipkinCollectorConfiguration(String serviceName,String zipkinUrl,String topic){
this.zipkinUrl=zipkinUrl;
this.serviceName=serviceName;
this.topic=topic;
this.tracing=this.tracing();
}
配置上报方式,这里统一采用异常上传,并且配置上报的超时时间。

protected AsyncReporter spanReporter() {
return AsyncReporter
.builder(getSender())
.closeTimeout(500, TimeUnit.MILLISECONDS)
.build(SpanBytesEncoder.JSON_V2);
}
下面这两方法,是配合应用构建span使用的。

注意那个sampler()方法,默认是什么也不做的意思,我们要想看到数据就需要配置成Sampler.ALWAYS_SAMPLE,这样才能真正将数据上报到zipkin服务器。
protected Tracing tracing() {
this.tracing= Tracing
.newBuilder()
.localServiceName(this.serviceName)
.sampler(Sampler.ALWAYS_SAMPLE)
.spanReporter(spanReporter())
.build();
return this.tracing;
}
protected Tracing getTracing(){
return this.tracing;
}
HttpZipkinCollectorConfiguration

主要是实现getSender方法,可以借用OkHttpSender这个对象来快速构建,api版本采用v2。

public class HttpZipkinCollectorConfiguration extends AbstractZipkinCollectorConfiguration {
public HttpZipkinCollectorConfiguration(String serviceName,String zipkinUrl) {super(serviceName,zipkinUrl,null);
}
@Override
br/>super(serviceName,zipkinUrl,null);
}
@Override
public Sender getSender() {
return OkHttpSender.create(super.getZipkinUrl()+"/api/v2/spans");
}
}
OkHttpSender这个类需要引用这个包


io.zipkin.reporter2
zipkin-sender-okhttp3
${zipkin-reporter2.version}

KafkaZipkinCollectorConfiguration

同样也是实现getSender方法

public class KafkaZipkinCollectorConfiguration extends AbstractZipkinCollectorConfiguration {
public KafkaZipkinCollectorConfiguration(String serviceName,String zipkinUrl,String topic) {br/>super(serviceName,zipkinUrl,topic);
}
@Override
public Sender getSender() {
return KafkaSender
.newBuilder()
.bootstrapServers(super.getZipkinUrl())
.topic(super.getTopic())
.encoding(Encoding.JSON)
.build();
}
}
KafkaSender这个类需要引用这个包:


io.zipkin.reporter2
zipkin-sender-kafka11
${zipkin-reporter2.version}

收集器工厂

由于上面创建了两个收集器配置类,使用时只能是其中之一,所以实际运行的实例需要根据配置来动态生成。ZipkinCollectorConfigurationFactory就是负责生成收集器实例的。

private final AbstractZipkinCollectorConfiguration zipkinCollectorConfiguration;br/>@Autowired
public ZipkinCollectorConfigurationFactory(TraceConfig traceConfig){
if(Objects.equal("kafka", traceConfig.getZipkinSendType())){
zipkinCollectorCOnfiguration=new KafkaZipkinCollectorConfiguration(
traceConfig.getApplicationName(),
traceConfig.getZipkinUrl(),
traceConfig.getZipkinKafkaTopic());
}
else {
zipkinCollectorCOnfiguration= new HttpZipkinCollectorConfiguration(
traceConfig.getApplicationName(),
traceConfig.getZipkinUrl());
}
}
通过构建函数将我们的配置类TraceConfig注入进来,然后根据发送方式来构建实例。另外提供一个辅助函数:

public Tracing getTracing(){
return this.zipkinCollectorConfiguration.getTracing();
}
过滤器

在dubbo的过滤器中实现数据上传的功能逻辑相对简单,一般都在invoke方法执行前记录数据,然后方法执行完成后再次记录数据。这个逻辑不变,有变化的是数据上报的实现,上一个版本是通过发http请求实现需要编码,现在可以直接借用brave所提供的span来帮助我们完成,有两重要的方法:

finish
方法源码如下,在完成的时候会填写上完成的时间并上报数据,这一般应用于同步调用场景。

public void finish(TraceContext context, long finishTimestamp) {
MutableSpan span = this.spanMap.remove(context);
if(span != null && !this.noop.get()) {
synchronized(span) {
span.finish(Long.valueOf(finishTimestamp));
this.reporter.report(span.toSpan());
}
}
}
flush 与上面finish方法的不同点在于,在报数据时没有完成时间,这应该是适用于一些异步调用但不关心结果的场景,比如dubbo所提供的oneway方式调用。
public void flush(TraceContext context) {
MutableSpan span = this.spanMap.remove(context);
if(span != null && !this.noop.get()) {
synchronized(span) {
span.finish((Long)null);
this.reporter.report(span.toSpan());
}
}
}
消费者

做为消费方,有一个核心功能就是将traceId以及spanId传递到服务提供方,这里还是通过dubbo提供的附加参数方式实现。

@Override
public Result invoke(Invoker invoker, Invocation invocation) throws RpcException {
if(!RpcTraceContext.getTraceConfig().isEnabled()){
return invoker.invoke(invocation);
}
ZipkinCollectorConfigurationFactory zipkinCollectorCOnfigurationFactory=
SpringContextUtils.getApplicationContext().getBean(ZipkinCollectorConfigurationFactory.class);
Tracer tracer= zipkinCollectorConfigurationFactory.getTracing().tracer();
if(null==RpcTraceContext.getTraceId()){
RpcTraceContext.start();
RpcTraceContext.setTraceId(IdUtils.get());
RpcTraceContext.setParentId(null);
RpcTraceContext.setSpanId(IdUtils.get());
}
else {
RpcTraceContext.setParentId(RpcTraceContext.getSpanId());
RpcTraceContext.setSpanId(IdUtils.get());
}
TraceContext traceCOntext= TraceContext.newBuilder()
.traceId(RpcTraceContext.getTraceId())
.parentId(RpcTraceContext.getParentId())
.spanId(RpcTraceContext.getSpanId())
.sampled(true)
.build();
Span span=tracer.toSpan(traceContext).start();
invocation.getAttachments().put(RpcTraceContext.TRACE_ID_KEY, String.valueOf(span.context().traceId()));
invocation.getAttachments().put(RpcTraceContext.SPAN_ID_KEY, String.valueOf(span.context().spanId()));
Result result = invoker.invoke(invocation);
span.finish();
return result;
}
提供者

@Override
public Result invoke(Invoker invoker, Invocation invocation) throws RpcException {
if(!RpcTraceContext.getTraceConfig().isEnabled()){
return invoker.invoke(invocation);
}
Map attaches = invocation.getAttachments();
if (!attaches.containsKey(RpcTraceContext.TRACE_ID_KEY)){
return invoker.invoke(invocation);
}
Long traceId = Long.valueOf(attaches.get(RpcTraceContext.TRACE_ID_KEY));
Long spanId = Long.valueOf(attaches.get(RpcTraceContext.SPAN_ID_KEY));
attaches.remove(RpcTraceContext.TRACE_ID_KEY);
attaches.remove(RpcTraceContext.SPAN_ID_KEY);
RpcTraceContext.start();
RpcTraceContext.setTraceId(traceId);
RpcTraceContext.setParentId(spanId);
RpcTraceContext.setSpanId(IdUtils.get());
ZipkinCollectorConfigurationFactory zipkinCollectorCOnfigurationFactory=
SpringContextUtils.getApplicationContext().getBean(ZipkinCollectorConfigurationFactory.class);
Tracer tracer= zipkinCollectorConfigurationFactory.getTracing().tracer();
TraceContext traceCOntext= TraceContext.newBuilder()
.traceId(RpcTraceContext.getTraceId())
.parentId(RpcTraceContext.getParentId())
.spanId(RpcTraceContext.getSpanId())
.sampled(true)
.build();
Span span = tracer.toSpan(traceContext).start();
Result result = invoker.invoke(invocation);
span.finish();
return result;
}
异常流程

上面无论是消费者的过滤器还是服务提供者的过滤器,均未考虑服务在调用invoker.invoke时出错的场景,如果出错,后面的span.finish方法将不会按预期执行,也就记录不了信息。所以需要针对此问题做优化:可以在finally块中执行finish方法。

try {
result = invoker.invoke(invocation);
}
finally {
span.finish();
}
消费者在调用服务时,异步调用问题

上面过滤器中调用span.finish都是基于同步模式,而由于dubbo除了同步调用外还提供了两种调用方式

异步调用 通过callback机制的异步
oneway
只发起请求并不等待结果的异步调用,无callback一说

针对上面两类异步再加上同步调用,我们要想准确记录服务真正的时间,需要在消费方的过滤器中做如下处理:
创建一个用于回调的处理类,它的主要目的是为了在回调成功时记录时间,这里无论是成功还是失败。

private class AsyncSpanCallback implements ResponseCallback{
private Span span;
public AsyncSpanCallback(Span span){this.span=span;
}
@Override
br/>this.span=span;
}
@Override
public void done(Object o) {
br/>span.finish();
}
@Override
public void caught(Throwable throwable) {
span.finish();
}
}
再在调用invoke方法时,如果是oneway方式,则调用flush方法结果,如果是同步则直接调用finish方法,如果是异步则在回调时调用finish方法。

Result result = null;
boolean isOneway= RpcUtils.isOneway(invoker.getUrl(), invocation);
try {
result = invoker.invoke(invocation);
}
finally {
if(isOneway) {
span.flush();
}
else if(!isAsync) {
span.finish();
}
}


推荐阅读
  • 在前文探讨了Spring如何为特定的bean选择合适的通知器后,本文将进一步深入分析Spring AOP框架中代理对象的生成机制。具体而言,我们将详细解析如何通过代理技术将通知器(Advisor)中包含的通知(Advice)应用到目标bean上,以实现切面编程的核心功能。 ... [详细]
  • Presto:高效即席查询引擎的深度解析与应用
    本文深入解析了Presto这一高效的即席查询引擎,详细探讨了其架构设计及其优缺点。Presto通过内存到内存的数据处理方式,显著提升了查询性能,相比传统的MapReduce查询,不仅减少了数据传输的延迟,还提高了查询的准确性和效率。然而,Presto在大规模数据处理和容错机制方面仍存在一定的局限性。本文还介绍了Presto在实际应用中的多种场景,展示了其在大数据分析领域的强大潜力。 ... [详细]
  • Android中将独立SO库封装进JAR包并实现SO库的加载与调用
    在Android开发中,将独立的SO库封装进JAR包并实现其加载与调用是一个常见的需求。本文详细介绍了如何将SO库嵌入到JAR包中,并确保在外部应用调用该JAR包时能够正确加载和使用这些SO库。通过这种方式,开发者可以更方便地管理和分发包含原生代码的库文件,提高开发效率和代码复用性。文章还探讨了常见的问题及其解决方案,帮助开发者避免在实际应用中遇到的坑。 ... [详细]
  • 在 CentOS 6.5 系统上部署 VNC 服务器的详细步骤与配置指南
    在 CentOS 6.5 系统上部署 VNC 服务器时,首先需要确认 VNC 服务是否已安装。通常情况下,VNC 服务默认未安装。可以通过运行特定的查询命令来检查其安装状态。如果查询结果为空,则表明 VNC 服务尚未安装,需进行手动安装。此外,建议在安装前确保系统的软件包管理器已更新至最新版本,以避免兼容性问题。 ... [详细]
  • 本指南从零开始介绍Scala编程语言的基础知识,重点讲解了Scala解释器REPL(读取-求值-打印-循环)的使用方法。REPL是Scala开发中的重要工具,能够帮助初学者快速理解和实践Scala的基本语法和特性。通过详细的示例和练习,读者将能够熟练掌握Scala的基础概念和编程技巧。 ... [详细]
  • 本文详细介绍了使用 Python 进行 MySQL 和 Redis 数据库操作的实战技巧。首先,针对 MySQL 数据库,通过 `pymysql` 模块展示了如何连接和操作数据库,包括建立连接、执行查询和更新等常见操作。接着,文章深入探讨了 Redis 的基本命令和高级功能,如键值存储、列表操作和事务处理。此外,还提供了多个实际案例,帮助读者更好地理解和应用这些技术。 ... [详细]
  • 本文探讨了如何利用 jQuery 的 JSONP 技术实现跨域调用外部 Web 服务。通过详细解析 JSONP 的工作原理及其在 jQuery 中的应用,本文提供了实用的代码示例和最佳实践,帮助开发者解决跨域请求中的常见问题。 ... [详细]
  • 手指触控|Android电容屏幕驱动调试指南
    手指触控|Android电容屏幕驱动调试指南 ... [详细]
  • 在Kohana 3框架中,实现最优的即时消息显示方法是许多开发者关注的问题。本文将探讨如何高效、优雅地展示flash消息,包括最佳实践和技术细节,以提升用户体验和代码可维护性。 ... [详细]
  • 本文探讨了 Kafka 集群的高效部署与优化策略。首先介绍了 Kafka 的下载与安装步骤,包括从官方网站获取最新版本的压缩包并进行解压。随后详细讨论了集群配置的最佳实践,涵盖节点选择、网络优化和性能调优等方面,旨在提升系统的稳定性和处理能力。此外,还提供了常见的故障排查方法和监控方案,帮助运维人员更好地管理和维护 Kafka 集群。 ... [详细]
  • 本文介绍了如何在iOS平台上使用GLSL着色器将YV12格式的视频帧数据转换为RGB格式,并展示了转换后的图像效果。通过详细的技术实现步骤和代码示例,读者可以轻松掌握这一过程,适用于需要进行视频处理的应用开发。 ... [详细]
  • MySQL数据库安装图文教程
    本文详细介绍了MySQL数据库的安装步骤。首先,用户需要打开已下载的MySQL安装文件,例如 `mysql-5.5.40-win32.msi`,并双击运行。接下来,在安装向导中选择安装类型,通常推荐选择“典型”安装选项,以确保大多数常用功能都能被正确安装。此外,文章还提供了详细的图文说明,帮助用户顺利完成整个安装过程,确保数据库系统能够稳定运行。 ... [详细]
  • 解析 /etc/pki/tls/certs/cabundle.crt 文件的主要功能与应用场景 ... [详细]
  • 本文详细介绍了如何安全地手动卸载Exchange Server 2003,以确保系统的稳定性和数据的完整性。根据微软官方支持文档(https://support.microsoft.com/kb833396/zh-cn),在进行卸载操作前,需要特别注意备份重要数据,并遵循一系列严格的步骤,以避免对现有网络环境造成不利影响。此外,文章还提供了详细的故障排除指南,帮助管理员在遇到问题时能够迅速解决,确保整个卸载过程顺利进行。 ... [详细]
  • 在腾讯云服务器上部署Nginx的详细指南中,首先需要确保安装必要的依赖包。如果这些依赖包已安装,可直接跳过此步骤。具体命令包括 `yum -y install gcc gcc-c++ wget net-tools pcre-devel zlib-devel`。接下来,本文将详细介绍如何下载、编译和配置Nginx,以确保其在腾讯云服务器上顺利运行。此外,还将提供一些优化建议,帮助用户提升Nginx的性能和安全性。 ... [详细]
author-avatar
手机用户2602925213
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有