一、配置初见
源码里提供的控制台截图如下:(怎么搭建自己去百度)
从中取出对应的配置如下:
json
{
"degrade": false, //阻断能力
"exceptionThreshold": 1000, //异常采样率
"httpEntrancePatterns": [
"^/app/v1/order/.*$"
],
"javaEntranceBehaviors": [ //主调用配置
{
"classPattern": "com.test.order.service.pay.PayService",
"includeSubClasses": false, //是否对内部类生效
"methodPatterns": [
"payCallback"
]
}
],
"javaSubInvokeBehaviors": [ //子调用配置
{
"classPattern": "com.test.backend.util.RedisUtil",
"includeSubClasses": false,
"methodPatterns": [
"*"
]
}
],
"pluginIdentities": [ //加载的插件
"redis",
"http",
"java-entrance",
"java-subInvoke",
"mybatis",
"ibatis",
"dubbo-provider",
"dubbo-consumer"
],
"repeatIdentities": [ //回放插件
"java",
"http",
"dubbo"
],
"sampleRate": 10000, //采样率,最高10000
"useTtl": true //开启ttl主子线程变量同步,主要应对多线程场景下的采集
}
接下来,我们通过源码,一步一步来看下具体的配置过程
二、启动过程
repeater插件的启动入口见com.alibaba.jvm.sandbox.repeater.module.RepeaterModule#loadCompleted, 基本流程就是:
- 拉取配置
- 初始化配置
- 发起心跳
java
@Override
public void loadCompleted() {
//这里启动一个线程去进行做加载,主要是为了不阻断sandbox的模块加载流程
ExecutorInner.execute(new Runnable() {
@Override
public void run() {
//standalone模式 我们在项目应用过程中是不需要考虑的,所以configManager就是DefaultConfigManager
configManager = StandaloneSwitch.instance().getConfigManager();
broadcaster = StandaloneSwitch.instance().getBroadcaster();
invocationListener = new DefaultInvocationListener(broadcaster);
RepeaterResult<RepeaterConfig> pr = configManager.pullConfig();
if (pr.isSuccess()) {
log.info("pull repeater config success,config={}", pr.getData());
ClassloaderBridge.init(loadedClassDataSource);
//启动的核心逻辑就在这里
initialize(pr.getData());
}
}
});
//心跳机制
heartbeatHandler = new HeartbeatHandler(configInfo, moduleManager);
heartbeatHandler.start();
}
所以,配置相关的核心使用逻辑在 com.alibaba.jvm.sandbox.repeater.module.RepeaterModule#initialize里
- 读取配置
- 初始化invokePlugin插件列表
- 初始化repeater插件列表(目前实现的有http、java、dubbo)
- SubscribeSupporter插件初始化(默认实现 RepeatSubscribeSupporter )
java
private synchronized void initialize(RepeaterConfig config) {
//确保一个周期里面,只初始化一次
if (initialized.compareAndSet(false, true)) {
try {
ApplicationModel.instance().setConfig(config);
// 特殊路由表; 这个特殊路由表是方便插件需要访问repeater不支持的类
PluginClassLoader.Routing[] routingArray = PluginClassRouting.wellKnownRouting(configInfo.getMode() == Mode.AGENT, 20L);
String pluginsPath;
if (StringUtils.isEmpty(config.getPluginsPath())) {
pluginsPath = PathUtils.getPluginPath();
} else {
pluginsPath = config.getPluginsPath();
}
//这个其实是一个类加载器
lifecycleManager = new JarFileLifeCycleManager(pluginsPath, routingArray);
// 装载插件
invokePlugins = lifecycleManager.loadInvokePlugins();
for (InvokePlugin invokePlugin : invokePlugins) {
try {
// 根据配置中的pluginIdentities,判断是否要注入相关拦截机制
if (invokePlugin.enable(config)) {
log.info("enable plugin {} success", invokePlugin.identity());
invokePlugin.watch(eventWatcher, invocationListener);
invokePlugin.onConfigChange(config);
}
} catch (PluginLifeCycleException e) {
log.info("watch plugin occurred error", e);
}
}
// 装载回放器
List<Repeater> repeaters = lifecycleManager.loadRepeaters();
for (Repeater repeater : repeaters) {
if (repeater.enable(config)) {
repeater.setBroadcast(broadcaster);
}
}
RepeaterBridge.instance().build(repeaters);
// 装载消息订阅器
List<SubscribeSupporter> subscribes = lifecycleManager.loadSubscribes();
for (SubscribeSupporter subscribe : subscribes) {
subscribe.register();
}
//用于开启ttl的
TtlConcurrentAdvice.watcher(eventWatcher).watch(config);
} catch (Throwable throwable) {
initialized.compareAndSet(true, false);
log.error("error occurred when initialize module", throwable);
}
}
}
repeater配置生效过程
所以回过头再看配置信息里,启动配置用到的是pluginIdentities、repeatIdentities;
三、java插件启动读取配置javaEntranceBehaviors、javaSubInvokeBehaviors
我们发现配置里还有javaEntranceBehaviors 和 javaSubInvokeBehaviors,这2个配置顾名思义就是java主子调用的意思;
我们去看代码com.alibaba.jvm.sandbox.repeater.plugin.java.JavaEntrancePlugin的实现,发现JavaEntrancePlugin重写了getEnhanceModels
java
@MetaInfServices(InvokePlugin.class)
public class JavaEntrancePlugin extends AbstractInvokePluginAdapter {
private RepeaterConfig config;
@Override
protected List<EnhanceModel> getEnhanceModels() {
//在这个地方读取对应配置的
if (config == null || CollectionUtils.isEmpty(config.getJavaEntranceBehaviors())) { return null;}
List<EnhanceModel> ems = Lists.newArrayList();
for (Behavior behavior : config.getJavaEntranceBehaviors()) {
ems.add(EnhanceModel.convert(behavior));
}
return ems;
}
... 其余代码省略
}
同理 JavaSubInvokePlugin 也重写了getEnhanceModels()方法; 这里关注一个点,java主子调用配置的生效,最终是在 com.alibaba.jvm.sandbox.repeater.plugin.core.impl.AbstractInvokePluginAdapter#watchIfNecessary里,通过调用sandbox的接口com.alibaba.jvm.sandbox.api.listener.ext.EventWatchBuilder.IBuildingForBehavior#onWatch()来生效的,也就意味着需要真正字节码注入之后才会生效,所以本质上它的配置,也是启动配置的一部分;
四、http插件的配置 httpEntrancePatterns
首先肯定是看http插件关于 植入点的实现, 我们发现它的注入点是固定的javax.servlet.http.HttpServlet#service()方法,那有关httpEntrancePatterns是在哪里生效的呢?
java
@Override
protected List<EnhanceModel> getEnhanceModels() {
// 拦截javax.servlet.http.HttpServlet#service(HttpServletRequest req, HttpServletResponse resp)
EnhanceModel.MethodPattern mp = EnhanceModel.MethodPattern.builder()
.methodName("service")
.parameterType(new String[]{"javax.servlet.http.HttpServletRequest", "javax.servlet.http.HttpServletResponse"})
.build();
EnhanceModel em = EnhanceModel.builder()
.classPattern("javax.servlet.http.HttpServlet")
.methodPatterns(new EnhanceModel.MethodPattern[]{mp})
.watchTypes(Event.Type.BEFORE, Event.Type.RETURN, Event.Type.THROWS)
.build();
return Lists.newArrayList(em);
}
通过代码最终,我们最后发现是在com.alibaba.jvm.sandbox.repater.plugin.http.HttpStandaloneListener#doBefore里,也就是真正的 切点通知逻辑做的, 在拦截http的每次请求后判断是否需要采集;
java
@Override
protected void doBefore(BeforeEvent event) throws ProcessControlException {
... 冗余代码省略
Object request = event.argumentArray[0];
Object response = event.argumentArray[1];
if (!(request instanceof HttpServletRequest && response instanceof HttpServletResponse)) {
return;
}
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
// 根据 requestURI 进行采样匹配, 这里取的配置
List<String> patterns = ApplicationModel.instance().getConfig().getHttpEntrancePatterns();
if (!matchRequestURI(patterns, req.getRequestURI())) {
LogUtil.debug("current uri {} can't match any httpEntrancePatterns, ignore this request", req.getRequestURI());
Tracer.getContext().setSampled(false);
return;
}
...冗余代码省略
}
四、可优化的点
通过上述源码阅读,配置这块我觉得有以下几个可优化的点:
- 像sampleRate采样率配置,是经常需要手动调整的,这类配置可以跟启动配置pluginIdentities等区分开;启动配置的修改是需要重启生效或者重新attach生效的
- http的配置 以及 java的配置也可以区分开; 以下是我这边优化后的界面, 有需要欢迎多交流