探针:用来采集app的请求,及服务请求第三方的服务的信息。
平台后端:可观测性分析OAP, 支持数据聚合, 数据分析以及驱动数据流从探针到用户界面的流程。分析包括 Skywalking 原生追踪和性能指标以及第三方来源, 你甚至可以使用 Observability Analysis
Language 对原生度量指标 和 用于扩展度量的计量系统 自定义聚合分析。
存储:通过开放的插件化的接口存放 SkyWalking 数据. 你可以选择一个既有的存储系统, 如 ElasticSearch, H2 或 MySQL 集群(Sharding-Sphere 管理),及时序数据influxdb
java agent是java命令的一个参数。参数[-javaagent:]可以用于指定一个 jar 包,并且对该java包有2个要求:
1.public static void premain(String agentArgs, Instrumentation inst)
2.public static void premain(String agentArgs)
其实对我们实现一些需要通过字节码的形式隐式注入到业务代码中的中间件非常有用,APM系统中经常可见,比如国产的优秀APM组件Skywalking(http://skywalking.apache.org/),现在是Apache的顶级项目之一
;韩国Naver开源的应用性能管理工具Pinpoint(https://github.com/naver/pinpoint),当然,Java
Agent还能实现动态对运行的Java应用进行字节码注入,做到“窥探”运行时的信息,典型的代表有Java追踪工具BTrace(https://github.com/btraceio/btrace)、阿里开源的JVM-SANDBOX(https://github.com/alibaba/jvm-sandbox)、Java在线问题诊断工具Greys(https://github.com/oldmanpushcart/greys-anatomy)等。
3. arthas
public class TestTransformer implements ClassFileTransformer {
//目标类名称, .分隔
private String targetClassName;
//目标类名称, /分隔
private String targetVMClassName;
private String targetMethodName;
public TestTransformer(String className,String methodName){
this.targetVMClassName = new String(className).replaceAll("\\.","\\/");
this.targetMethodName = methodName;
this.targetClassName=className;
}
//类加载时会执行该函数,其中参数 classfileBuffer为类原始字节码,返回值为目标字节码,className为/分隔
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
//判断类名是否为目标类名
if(!className.equals(targetVMClassName)){
return classfileBuffer;
}
try {
ClassPool classPool = ClassPool.getDefault();
CtClass cls = classPool.get(this.targetClassName);
CtMethod ctMethod = cls.getDeclaredMethod(this.targetMethodName);
ctMethod.insertBefore("{ System.out.println(\"start\"); }");
ctMethod.insertAfter("{ System.out.println(\"end\"); }");
return cls.toBytecode();
} catch (Exception e) {
}
return classfileBuffer;
}
}
byte Buddy是一个字节码生成和操作库,用于在Java应用程序运行时创建和修改Java类,而无需编译器的帮助。agent就是采用byte
Buddy是实现各个中间件jar的采集。比起JDK动态代理、cglib、Javassist,Byte Buddy在性能上具有一定的优势。
2.skywalking-java提供proto协议作为通讯组件。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qRSCkkXd-1668244391371)(images/skywalking-1.png)]
通过为不同的组件及对应版本plugin插件,采集对应的信息,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J2j4lKH6-1668244391372)(images/agent-two-plugin.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nKFGP5OE-1668244391374)(images/plugin-3.png)]
通过httpclient-4的plugin 植入的是InternalHttpClient类doExecute方法。通过插件我们获取Method方式,url,响应代码,调用次数,响应时间。
protected CloseableHttpResponse doExecute(HttpHost target, HttpRequest request, HttpContext context) throws IOException, ClientProtocolException {
Args.notNull(request, "HTTP request");
HttpExecutionAware execAware = null;
if (request instanceof HttpExecutionAware) {
execAware = (HttpExecutionAware)request;
}
/**
* {@link AbstractHttpClientInstrumentation} presents that skywalking intercepts InternalHttpClient#doExecute by using
* {@link HttpClientInstrumentation#INTERCEPT_CLASS}.
*/
public class InternalHttpClientInstrumentation extends HttpClientInstrumentation {
private static final String ENHANCE_CLASS = "org.apache.http.impl.client.InternalHttpClient";
@Override
public ClassMatch enhanceClass() {
return NameMatch.byName(ENHANCE_CLASS);
}
@Override
public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
return new InstanceMethodsInterceptPoint[] {
new InstanceMethodsInterceptPoint() {
@Override
public ElementMatcher<MethodDescription> getMethodsMatcher() {
return named("doExecute");
}
@Override
public String getMethodsInterceptor() {
return getInstanceMethodsInterceptor();
}
@Override
public boolean isOverrideArgs() {
return false;
}
}
};
}
}
public class HttpClientExecuteInterceptor implements InstanceMethodsAroundInterceptor {
@Override
public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
MethodInterceptResult result) throws Throwable {
if (allArguments[0] == null || allArguments[1] == null) {
return;
}
final HttpHost httpHost = (HttpHost) allArguments[0];
HttpRequest httpRequest = (HttpRequest) allArguments[1];
final ContextCarrier contextCarrier = new ContextCarrier();
String remotePeer = httpHost.getHostName() + ":" + port(httpHost);
String uri = httpRequest.getRequestLine().getUri();
String requestURI = getRequestURI(uri);
String operationName = requestURI;
AbstractSpan span = ContextManager.createExitSpan(operationName, contextCarrier, remotePeer);
span.setComponent(ComponentsDefine.HTTPCLIENT);
Tags.URL.set(span, buildSpanValue(httpHost, uri));
Tags.HTTP.METHOD.set(span, httpRequest.getRequestLine().getMethod());
SpanLayer.asHttp(span);
CarrierItem next = contextCarrier.items();
while (next.hasNext()) {
next = next.next();
httpRequest.setHeader(next.getHeadKey(), next.getHeadValue());
}
if (HttpClientPluginConfig.Plugin.HttpClient.COLLECT_HTTP_PARAMS) {
collectHttpParam(httpRequest, span);
}
}
@Override
public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
Object ret) throws Throwable {
if (allArguments[0] == null || allArguments[1] == null) {
return ret;
}
if (ret != null) {
HttpResponse response = (HttpResponse) ret;
StatusLine responseStatusLine = response.getStatusLine();
if (responseStatusLine != null) {
int statusCode = responseStatusLine.getStatusCode();
AbstractSpan span = ContextManager.activeSpan();
Tags.HTTP_RESPONSE_STATUS_CODE.set(span, statusCode);
if (statusCode >= 400) {
span.errorOccurred();
}
HttpRequest httpRequest = (HttpRequest) allArguments[1];
// Active HTTP parameter collection automatically in the profiling context.
if (!HttpClientPluginConfig.Plugin.HttpClient.COLLECT_HTTP_PARAMS && span.isProfiling()) {
collectHttpParam(httpRequest, span);
}
}
}
ContextManager.stopSpan();
return ret;
}
@Override
public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
Class<?>[] argumentsTypes, Throwable t) {
AbstractSpan activeSpan = ContextManager.activeSpan();
activeSpan.log(t);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-18OVA4iQ-1668244391377)(trace/openTrace-1.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mrLGyDJw-1668244391379)(trace/OpenTranc-2.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ET2CYnUW-1668244391381)(trace/trace-two.png)]
Causal relationships between Spans in a single Trace
[Span A] ←←←(the root span)
|
+------+------+
| |
[Span B] [Span C] ←←←(Span C is a `ChildOf` Span A)
| |
[Span D] +---+-------+
| |
[Span E] [Span F] >>> [Span G] >>> [Span H]
↑
↑
↑
(Span G `FollowsFrom` Span F)
Temporal relationships between Spans in a single Trace
––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–> time
[Span A···················································]
[Span B··············································]
[Span D··········································]
[Span C········································]
[Span E·······] [Span F··] [Span G··] [Span H··]
OpenTracing 希望各个实现平台能够根据上述的核心概念来建模实现,不仅如此,OpenTracing 还提供了核心接口的描述,帮助开发人员更好的实现 OpenTracing 规范。
Span 接口
Span接口必须实现以下的功能:
获取关联的 SpanContext:通过 Span 获取关联的 SpanContext 对象。
关闭(Finish)Span:完成已经开始的 Span。
添加 Span Tag:为 Span 添加 Tag 键值对。
添加 Log:为 Span 增加一个 Log 事件。
添加 Baggage Item:向 Baggage 中添加一组键值对。
获取 Baggage Item:根据 Key 获取 Baggage 中的元素。
SpanContext 接口
SpanContext 接口必须实现以下功能,用户可以通过 Span 实例或者 Tracer 的 Extract 能力获取 SpanContext 接口实例。
遍历 Baggage 中全部的 KV。
Tracer 接口
Tracer 接口必须实现以下功能:
创建 Span:创建新的 Span。
注入 SpanContext:主要是将跨进程调用携带的 Baggage 数据记录到当前 SpanContext 中。
提取 SpanContext ,主要是将当前 SpanContext 中的全局信息提取出来,封装成 Baggage 用于后续的跨进程调用。
总结
演示MockTracerTest例子
OpenTracing的api介绍
OpenTracing 社区贡献 除了官方的API,也有一些苦在opentracing-contribe,保管通用的辅助类像TracerResolver和框架工具库,例如 Java Web Servlet Filter and Spring
Cloud,可以用于在使用这些框架工具的项目中方便的集成OpenTracing。
可以通过RestTemplate.setInterceptors注册拦截器。解析
public class TracingRestTemplateInterceptor implements ClientHttpRequestInterceptor {
private static final Log log = LogFactory.getLog(TracingRestTemplateInterceptor.class);
private Tracer tracer;
private List<RestTemplateSpanDecorator> spanDecorators;
public TracingRestTemplateInterceptor() {
this(GlobalTracer.get(), Collections.<RestTemplateSpanDecorator>singletonList(
new RestTemplateSpanDecorator.StandardTags()));
}
/**
* @param tracer tracer
*/
public TracingRestTemplateInterceptor(Tracer tracer) {
this(tracer, Collections.<RestTemplateSpanDecorator>singletonList(
new RestTemplateSpanDecorator.StandardTags()));
}
/**
* @param tracer tracer
* @param spanDecorators list of decorators
*/
public TracingRestTemplateInterceptor(Tracer tracer, List<RestTemplateSpanDecorator> spanDecorators) {
this.tracer = tracer;
this.spanDecorators = new ArrayList<>(spanDecorators);
}
@Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
ClientHttpResponse httpResponse;
//构建span,并开始
Span span = tracer.buildSpan(httpRequest.getMethod().toString())
.withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT)
.start();
tracer.inject(span.context(), Format.Builtin.HTTP_HEADERS,
new HttpHeadersCarrier(httpRequest.getHeaders()));
for (RestTemplateSpanDecorator spanDecorator : spanDecorators) {
try {
spanDecorator.onRequest(httpRequest, span);
} catch (RuntimeException exDecorator) {
log.error("Exception during decorating span", exDecorator);
}
}
try (Scope scope = tracer.activateSpan(span)) {
httpResponse = execution.execute(httpRequest, body);
for (RestTemplateSpanDecorator spanDecorator : spanDecorators) {
try {
spanDecorator.onResponse(httpRequest, httpResponse, span);
} catch (RuntimeException exDecorator) {
log.error("Exception during decorating span", exDecorator);
}
}
} catch (Exception ex) {
for (RestTemplateSpanDecorator spanDecorator : spanDecorators) {
try {
spanDecorator.onError(httpRequest, ex, span);
} catch (RuntimeException exDecorator) {
log.error("Exception during decorating span", exDecorator);
}
}
throw ex;
} finally {
span.finish();
}
return httpResponse;
}
}
通过框架的拦截器能力实现HTTP请求追踪 通过上文中的代码,我们知道了如何使用Tracer对象构建Span,如何在线程中激活Span,以及如何在异步环境的不同线程间传递Span。
在实际的业务开发中,我们很难使用这种侵入的方式来实现追踪,更多的是利用各种框架提供的拦截器机制,来对各种业务调用进行自动追踪,比如Spring AOP,Servlet Filter,等等。下面一段代码展示了如何通过Servlet
Filter来进行服务端的HTTP请求追踪。
public class TracingWebFilter implements WebFilter, Ordered {
private static final Log LOG = LogFactory.getLog(TracingWebFilter.class);
/**
* Used as a key of {@link ServerWebExchange#getAttributes()}} to inject server span context
*/
static final String SERVER_SPAN_CONTEXT = TracingWebFilter.class.getName() + ".activeSpanContext";
private final Tracer tracer;
private final int order;
@Nullable
private final Pattern skipPattern;
private final Set<PathPattern> urlPatterns;
private final List<WebFluxSpanDecorator> spanDecorators;
public TracingWebFilter(
final Tracer tracer,
final int order,
final Pattern skipPattern,
final List<String> urlPatterns,
final List<WebFluxSpanDecorator> spanDecorators
) {
this.tracer = tracer;
this.order = order;
this.skipPattern = (skipPattern != null && StringUtils.hasText(skipPattern.pattern())) ? skipPattern : null;
final PathPatternParser pathPatternParser = new PathPatternParser();
this.urlPatterns = urlPatterns.stream().map(pathPatternParser::parse).collect(Collectors.toSet());
this.spanDecorators = spanDecorators;
}
@Override
public Mono<Void> filter(final ServerWebExchange exchange, final WebFilterChain chain) {
final ServerHttpRequest request = exchange.getRequest();
if (!shouldBeTraced(request)) {
return chain.filter(exchange);
}
if (exchange.getAttribute(SERVER_SPAN_CONTEXT) != null) {
if (LOG.isTraceEnabled()) {
LOG.trace("Not tracing request " + request + " because it is already being traced");
}
return chain.filter(exchange);
}
return new TracingOperator(chain.filter(exchange), exchange, tracer, spanDecorators);
}
/**
* It checks whether a request should be traced or not.
*
* @return whether request should be traced or not
*/
protected boolean shouldBeTraced(final ServerHttpRequest request) {
final PathContainer pathWithinApplication = request.getPath().pathWithinApplication();
// skip URLs matching skip pattern
// e.g. pattern is defined as '/health|/status' then URL 'http://localhost:5000/context/health' won't be traced
if (skipPattern != null) {
final String url = pathWithinApplication.value();
if (skipPattern.matcher(url).matches()) {
if (LOG.isTraceEnabled()) {
LOG.trace("Not tracing request " + request + " because it matches skip pattern: " + skipPattern);
}
return false;
}
}
if (!urlPatterns.isEmpty() && urlPatterns.stream().noneMatch(urlPattern -> urlPattern.matches(pathWithinApplication))) {
if (LOG.isTraceEnabled()) {
LOG.trace("Not tracing request " + request + " because it does not match any URL pattern: " + urlPatterns);
}
return false;
}
return true;
}
@Override
public int getOrder() {
return order;
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MbKI56PE-1668244391383)(skywalking/sky2.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7dkTQWS2-1668244391384)(skywalking/send-xx.png)]