前言
默认查看此文章的人已经对javaagent有简单的理解,此文章以代码为主。
本教程的代码目的是为了重写skywalking的sql,在oceanbase的mysql兼容模式下可以使用到oceanbase的列式储存以减少磁盘空间和加快分析效率。
实测在postgresql的columnar插件下segment表在切换到列式储存后磁盘储存空间从800M降到了200M。由于columnar有很多限制,所以最后选择了oceanbase。
参考文档
javaagent
https://blog.csdn.net/mfmfmfo/article/details/126689494
列式储存
https://blog.csdn.net/qq_14855971/article/details/105405369
oceanbase
https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000001573482
项目介绍
通过javaagent在启动时对skywalking的SQLBuilder类的toString方法进行重写。
你可以学到以下内容:
javaagent的编写
如何通过maven-shade-plugin将项目打包为fatjar
如何避免agent的依赖和增强项目的依赖冲突
使用javassist对SQLBuilder的toString方法进行重写
SQLBuilder源码(apache-skywalking-apm-10.1.0)
需要对toString进行重写
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.skywalking.oap.server.storage.plugin.jdbc;
/**
* SQLBuilder
*/
public class SQLBuilder {
private static final String LINE_END = System.lineSeparator();
private final StringBuilder text;
public SQLBuilder() {
text = new StringBuilder();
}
public SQLBuilder(String initText) {
text = new StringBuilder(initText);
}
public SQLBuilder append(String fragment) {
text.append(fragment);
return this;
}
public SQLBuilder appendLine(String line) {
text.append(line).append(LINE_END);
return this;
}
@Override
public String toString() {
return text.toString();
}
}
项目源码
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>space.xiaoxiao.skywalking</groupId>
<artifactId>sql-rewrite-agent</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 字节码修改工具 javassist -->
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.30.2-GA</version>
</dependency>
<!-- skywalking里面使用slf4j, 为了避免冲突, 这里排除需要配置provided -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.16</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<!-- 配置package阶段打包为fat jar -->
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>false</shadedArtifactAttached>
<transformers>
<!-- 配置Manifest文件,指定agent的入口类 -->
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Premain-Class>space.xiaoxiao.skywalking.agent.SqlRewriteAgent</Premain-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</transformer>
</transformers>
<relocations>
<!-- 重写javassist包名,避免冲突 -->
<relocation>
<pattern>javassist</pattern>
<shadedPattern>space.xiaoxiao.javassist</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
src/main/java/space/xiaoxiao/skywalking/agent/SqlRewriteAgent.java
package space.xiaoxiao.skywalking.agent;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
public class SqlRewriteAgent {
// 使用slf4j日志,和skywalking保持一致,就可以把日志输出到skywalking的日志文件中
private static final Logger logger = LoggerFactory.getLogger(SqlRewriteAgent.class);
// 要修改的类名
private static final String SQL_BUILDER_CLASS_NAME = "org/apache/skywalking/oap/server/storage/plugin/jdbc/SQLBuilder";
/**
* 启动时,jvm通过premain方法启动agent
*/
public static void premain(String agentArgs, Instrumentation inst) {
inst.addTransformer(new SqlRewriteTransformer());
}
/**
* SQLBuilder类文件的Transformer
*/
public static class SqlRewriteTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (!SQL_BUILDER_CLASS_NAME.equals(className)) {
return classfileBuffer;
}
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass;
try (InputStream is = new ByteArrayInputStream(classfileBuffer)) {
ctClass = classPool.makeClass(is);
} catch (IOException ignore) {
return classfileBuffer;
}
CtMethod[] methods = ctClass.getMethods();
for (CtMethod method : methods) {
if (method.getName().equals("toString")) {
try {
// org.apache.skywalking.oap.server.storage.plugin.jdbc.SQLBuilder#toString
// 使用全限定类名调用,要不然需要处理import
method.setBody("{return space.xiaoxiao.skywalking.agent.SqlRewriteAgent.rewrite(this.text.toString());}");
} catch (CannotCompileException ignore) {
}
try {
return ctClass.toBytecode();
} catch (IOException | CannotCompileException ignore) {
}
}
}
return classfileBuffer;
}
}
/**
* 把建表语句后面增加列式储存参数
*/
public static String rewrite(String sql) {
if (sql.contains("CREATE TABLE") && sql.contains(";")) {
sql = sql.substring(0, sql.lastIndexOf(";")) + " WITH column group(each column);";
logger.info("rewrite sql: " + sql);
}
return sql;
}
}
打包后的jar包自动添加了MANIFEST.MF,并重写了javassist的包名。slf4j的class没有打包进来,会使用skywalking的。