微内核架构
skywalking-agent采用微内核架构,本身的core代码比较轻量级,对各种技术的监控支持是通过各个插件Plugin
实现的,例如:
-
dubbo-3.x-plugin
(针对dubbo3的支持) -
mvc-annotation-5.x-plugin
(针对spring-mvc5的支持) -
mysql-8.x-plugin
(针对mysql8的支持)
大部分的插件是skywalking内置的,可以支持绝大多数场景,还有一部分社区开发的各种插件,同时,可以开发自定以插件来支持特殊的场景
javaagent
jdk1.5后新增了类java.lang.instrument.Instrumentation
,它提供在运行时重新加载某个类的的class文件的api,有了它就可以实现在程序运行前加入切点,独立与目标程序,侵入性比普通的AOP
编程低很多
使用方式如下:
- 新建代理程序
- 代理程序入口微
premain
而不是原来的main,方法包含参数Instrumentation
- 代理程序加入自定义转换器(操作
Instrumentation
修改字节码) - 修改目标程序启动方式,加入-javaagent参数指向代理程序
premain写法类似如下
public static void premain(String agentArgs, Instrumentation inst) {
... // 使用inst修改字节码,实现自定义转换
}
由于Instrumentation写起来非常困难,所以有一个交互性更好的类库来实现切面编程,就是bytebuddy
bytebuddy
bytebuddy
提供了一套非常友好的修改java字节码的API,有了它就可以非常轻松的实现对方法切面编程,类似spring-aop其到的作用
javaagent
+bytebuddy
配合就可以轻松实现无侵入式的切面编程,写法类似如下(拦截spring的请求):
public static void premain(String agentArgs, Instrumentation inst) {
try {
// 拦截spring controller,使用bytebuddy的builder
AgentBuilder.Identified.Extendable agentBuilder = new AgentBuilder.Default()
// 拦截@Controller 和 @RestController的类
.type(ElementMatchers.isAnnotatedWith(ElementMatchers.named("org.springframework.stereotype.Controller")
.or(ElementMatchers.named("org.springframework.web.bind.annotation.RestController"))))
.transform((builder, typeDescription, classLoader, javaModule) ->
// 拦截 @RestMapping 或者 @Get/Post/Put/DeleteMapping
builder.method(ElementMatchers.isPublic().and(ElementMatchers.isAnnotatedWith(
ElementMatchers.nameStartsWith("org.springframework.web.bind.annotation")
.and(ElementMatchers.nameEndsWith("Mapping")))))
// 拦截后交给 SpringControllerInterceptor 处理
.intercept(MethodDelegation.to(SpringControllerInterceptor.class)));
// ByteBuddy只是帮我们更方便的操作Instrumentation
agentBuilder.installOn(inst);
} catch (Exception e) {
e.printStackTrace();
}
}
其中SpringControllerInterceptor
就是自定以的bytebuddy拦截器,比如要打印每个请求执行时间,写法类似如下(与AOP很像)
public class SpringControllerInterceptor {
@RuntimeType
public static Object intercept(@Origin Method method,
@AllArguments Object[] args,
@SuperCall Callable<?> callable) {
long start = System.currentTimeMillis(); // 开始时间
try {
Object res = callable.call(); // 调用原方法
return res;
} catch(Exception e) {
log.error("controller error: ", e);
return null;
} finally {
long end = System.currentTimeMillis();
log.info("after controller execute in {} ms", end - start); // 输出程序执行时间
}
}
}
skywalking-agent初始化
skywalking-agent就是使用javaagent+bytebuddy实现无侵入的切面编程,其premain
核心代码如下:
public static void premain(String agentArgs, Instrumentation instrumentation) throws PluginException {
final PluginFinder pluginFinder;
// 初始化配置,读取agent.config配置文件
SnifferConfigInitializer.initializeCoreConfig(agentArgs);
// 加载所有插件
pluginFinder = new PluginFinder(new PluginBootstrap().loadPlugins());
// 使用bytebuddy
final ByteBuddy byteBuddy = new ByteBuddy().with(TypeValidation.of(Config.Agent.IS_OPEN_DEBUGGING_CLASS));
// 使用bytebuddy的builder
AgentBuilder agentBuilder = new AgentBuilder.Default(byteBuddy).ignore(
nameStartsWith("net.bytebuddy.");
agentBuilder.type(pluginFinder.buildMatch()) // 根据插件生成类匹配规则
.transform(new Transformer(pluginFinder)) // 同样根据插件生成转换器
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.with(new RedefinitionListener())
.with(new Listener())
.installOn(instrumentation); // ByteBuddy只是帮我们更方便的操作Instrumentation
ServiceManager.INSTANCE.boot(); // 初始化所有服务类,类似spring的容器初始化
}
总结skywalking启动的核心逻辑:
读取所有插件,根据插件的定义来确定如何拦截,并使用ByteBuddy实际进行拦截
有关插件的具体介绍,参照skywalking-agent插件
网友评论