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>