简单实现 dubbo 集成 zipkin 的小插件

1、实现小插件的初衷以及思路

相比较pinpoint,zipkin 通过brave是原生支持dubbo的调用链数据采集的,实现也比较简单,开发测试日常使用也比较简单。但是我这里要重点考虑几个问题:

(1)更加灵活,虽然可以通过spring cloud seluth zipkin 也可以实现,但是要添加一些更灵活的配置就没那么简单了。

(2)高度容错,允许通过分布式配置动态开启或关闭调用链的采集,还有就是即使zipkin服务端宕机了,也不能影响微服务的运行。

  (3)   支持自定义标签,有时候需要对不同的请求类型添加特定的标签,或者还是需要定制开发的

  (4)  动态采样,不同类型的服务可以定义不同的采样率

那么要满足这些需求,显然只通过barave原始的配置是不够的,这里提供一个相对简单的实现方法。先列下代码设计层面要考虑的问题

(1)自定义Matcher,用于过滤指定的函数或者服务,你设置可以使用正则表达式。如果要实现上面的功能,这一步必须要做。

 (2)  自定义配置项,既然要灵活,那就应该有自己的配置项吧。

 (3)自定义Tracing,这也是必须的,毕竟要通过自己的配置来控制采样

 (4)  如果觉得brave 自带Sampler,Reporter 不合适,可以自定义,我这里暂时没有这么做。

 (5)传播方式 propagated  还是使用的B3 ,但是可以定义系统特有的跟踪字段

 (6)还有一个很重要的方面就是Sender ,这个应该是自由选择吧,不属于深度定制的部分,如果系统并发量高,肯定是用Kafka的Sender。

 (7)其他一些技术点比如存储的选择、zipkin server的集群部署方式等,不在这次的讨论范围。

 

2、代码示例

这里提供部分代码的示例:

2.1、Matcher

函数适配,这里是样例

import brave.rpc.RpcRequest;
import brave.sampler.Matcher;
import brave.sampler.Matchers;

/**
 * 自定义匹配规则
 *
 * @see Matchers
 */
public final class DubboRpcRequestMatchers {

	/**
	 * 匹配所有的Rpc 请求 包括 DubboClientRequest 和 DubboServerRequest
	 * 
	 * @see brave.dubbo.rpc.DubboServerRequest
	 * @see brave.dubbo.rpc.DubboClientRequest
	 * @see RpcRequest#service()
	 */
	public static <Req extends RpcRequest> Matcher<Req> rpcEquals() {
		return new RpcEquals<>();
	}

	static final class RpcEquals<Req extends RpcRequest> implements Matcher<Req> {

		RpcEquals() {
		}

		@Override
		public boolean matches(Req request) {
			return request instanceof RpcRequest;
		}

		@Override
		public boolean equals(Object o) {
			return true;
		}

		@Override
		public int hashCode() {
			return super.hashCode();
		}

		@Override
		public String toString() {
			return "RpcEquals";
		}
	}

        /**
	 *
	 *  匹配函数列表
	 */
	public static <Req extends RpcRequest> Matcher<Req> methodEquals(List<String> method) {
		if (method == null)
			throw new NullPointerException("method == null");
		if (method.isEmpty())
			throw new NullPointerException("method is empty");
		return new RpcMethodEquals<>(method);
	}

	static final class RpcMethodEquals<Req extends RpcRequest> implements Matcher<Req> {
		final List<String> method;

		RpcMethodEquals(List<String> method) {
			this.method = method;
		}

		@Override
		public boolean matches(Req request) {
			return method.contains(request.method());
		}

		@Override
		public boolean equals(Object o) {
			if (o == this)
				return true;
			if (!(o instanceof RpcMethodEquals))
				return false;
			@SuppressWarnings("rawtypes")
			RpcMethodEquals<?> that = (RpcMethodEquals) o;
			return method.contains(that.method);
		}

		@Override
		public int hashCode() {
			return method.hashCode();
		}

		@Override
		public String toString() {
			return "method contains (" + method + ")";
		}
	}
}

2.2 自定义配置

	private String serviceName;
	// zipkin 地址
	private String url;
	private Integer connectTimeout;	
	private Integer readTimeout;	
	// 全局采样率
	private String probability;
	//服务
	private String serviceNames;
	//服务采样率
	private String serviceProbability;
	//函数
	private String methods;
	//函数采样率
	private String methodsProbability;
	

2.3 定义tracing


@Configuration
public class ZipkinConfiguration {

	private static final Logger LOG = LoggerFactory.getLogger(ZipkinConfiguration.class);

	@Autowired
	private ZipkinProperties zipkinProperties;

	@Bean
	public RpcTracing tracing() {
		boolean isChecked = configCheck();
		if (isChecked) {
			RpcTracing.Builder builder = RpcTracing.newBuilder(newTracing());
			SamplerFunction<RpcRequest> sampler = sampler();
			if (null != sampler) {
				builder.serverSampler(sampler);
				builder.clientSampler(sampler);
			}
			return builder.build();
		} else {
			LOG.info("tracing-end:不采样");
			SamplerFunction<RpcRequest> sampler = RpcRuleSampler.newBuilder().putRule(rpcEquals(), BoundarySampler.create(0f)).build();
			return RpcTracing.newBuilder(Tracing.newBuilder().build()).serverSampler(sampler)
					.clientSampler(sampler).build();
		}

	}


	private RpcRuleSampler sampler() {
		Builder builder = RpcRuleSampler.newBuilder();

		// 配置服务采样
		String serviceNames = zipkinProperties.getServiceNames();
		String serviceProbability = zipkinProperties.getServiceProbability();
		int count = 0;
		if (StringUtil.isNotEmpty(serviceNames) && null != serviceProbability) {
			float probability = Float.valueOf(serviceProbability);
			List<String> serviceNameList = Arrays.asList(serviceNames.split(","));
			for (String serviceName : serviceNameList) {
				builder.putRule(serviceEquals(serviceName), BoundarySampler.create(probability));
				count++;
			}
		}
		// 配置函数采样
		String methods = zipkinProperties.getMethods();
		String methodsProbability = zipkinProperties.getMethodsProbability();
		if (StringUtil.isNotEmpty(methods) && null != methodsProbability) {
			float probability = Float.valueOf(methodsProbability);
			List<String> methodList = Arrays.asList(methods.split(","));
			for (String method : methodList) {
				builder.putRule(methodEquals(method), BoundarySampler.create(probability));
				count++;
			}
		}

		if (count <= 0) {
			// 默认采样
			builder.putRule(rpcEquals(), BoundarySampler.create(Float.valueOf(zipkinProperties.getProbability())));
		}
		return builder.build();

	}

	private Tracing newTracing() {
		BaggageField dubboTraceId = BaggageField.create("x-dubbo-trace-id");
		ScopeDecorator decorator = MDCScopeDecorator.newBuilder().add(SingleCorrelationField.create(dubboTraceId))
				.build();

		return Tracing.newBuilder().localServiceName(zipkinProperties.getServiceName())
				.propagationFactory(BaggagePropagation.newFactoryBuilder(B3Propagation.FACTORY)
						.add(SingleBaggageField.remote(dubboTraceId)).build())
				.currentTraceContext(ThreadLocalCurrentTraceContext.newBuilder().addScopeDecorator(decorator).build())
				.addSpanHandler(spanHandler()).build();
	}

	private ZipkinSpanHandler spanHandler() {
		Sender sender = OkHttpSender.create(zipkinProperties.getUrl());
		// 这里只设置了errorTag,tag 也是可以自定义的
		return AsyncZipkinSpanHandler.newBuilder(sender).alwaysReportSpans(true).errorTag(Tags.ERROR)
				.closeTimeout(zipkinProperties.getConnectTimeout(), TimeUnit.MILLISECONDS)
				.messageTimeout(zipkinProperties.getReadTimeout(), TimeUnit.MILLISECONDS).build();
	}

	private boolean configCheck() {
		if (StringUtil.isEmpty(zipkinProperties.getServiceName()) || StringUtil.isEmpty(zipkinProperties.getUrl())
				|| Objects.isNull(zipkinProperties.getConnectTimeout())
				|| Objects.isNull(zipkinProperties.getReadTimeout())
				|| Objects.isNull(zipkinProperties.getProbability())) {
			return false;
		}
		String urlString = zipkinProperties.getUrl();
		if (null == urlString || urlString.length() <= 0) {
			return false;
		}
		try {
			URL url = new URL(urlString);
			URLConnection co = url.openConnection();
			co.setConnectTimeout(zipkinProperties.getConnectTimeout());
			co.connect();
			LOG.info("连接可用:{}", urlString);
			return true;
		} catch (Exception e1) {
			LOG.info("连接不可用:{}", urlString);
		}
		return true;
	}

}

3  引用的包以及不同类型的工程需要修改的地方

如果是dubbo service provider

需要在配置文件中添加

<!-- 服务提供方filter -->
<dubbo:provider delay="-1" filter="tracing"/>

<!-- 服务消费方filter -->
<dubbo:consumer filter="tracing"/>

如果是dubbo service consumer,web服务只需要配置consumer:

<!-- 服务消费方filter -->
<dubbo:consumer filter="tracing"/>

在所有的applicationContext中添加bean配置项:

<context:component-scan base-package="com.common.zipkin"/>
<bean id="zipkinProperties" class="com.common.zipkin.ZipkinProperties">
    <property name="serviceName" value ="amd-service"/>
    <property name="url" value ="${zipkin.sender.address}"/>
    <property name="probability" value ="${zipkin.sender.probability}"/>
    <property name="connectTimeout" value ="${zipkin.sender.connectTimeout}"/>
    <property name="readTimeout" value ="${zipkin.sender.readTimeout}"/>
</bean>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

October-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值