xiaoxiao
发布于 2024-12-14 / 6 阅读 / 0 评论 / 0 点赞

[java] javaagent改写sql案例

前言

默认查看此文章的人已经对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方法进行重写。

你可以学到以下内容:

  1. javaagent的编写

  2. 如何通过maven-shade-plugin将项目打包为fatjar

  3. 如何避免agent的依赖和增强项目的依赖冲突

  4. 使用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的。

运行效果


评论