标签搜索

目 录CONTENT

文章目录

apisix新特性,java开发postgresql日志插件

陈铭
2021-06-26 / 0 评论 / 0 点赞 / 554 阅读 / 1,796 字 / 正在检测是否收录...

拉取apisix-java-plugin-runner源码,编写java插件

拉取apisix-java-plugin-runner源码

可以直接在github上面下载源码,解压zip包,可以用idea打开(本身就是个springboot工程)。

github网址:https://github.com/apache/apisix-java-plugin-runner/

添加依赖

在runner-plungin模块下的pom文件增加依赖,也就是postgresql的jdbc

        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
        </dependency>

版本可以不用写,springboot管理了版本

编写插件

插件要在apisix-java-plugin-runner-main\runner-plugin\src\main\java\org\apache\apisix\plugin\runner\filter\下,我们写个PostgresqlLog类,必须实现PluginFilter接口。源码如下,就写了个简单的批处理逻辑,日志缓存满5条则发送到数据库。

package org.apache.apisix.plugin.runner.filter;

import com.google.gson.Gson;
import org.apache.apisix.plugin.runner.HttpRequest;
import org.apache.apisix.plugin.runner.HttpResponse;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class PostgresqlLog implements PluginFilter {
    public int retryNum = 3;
    public static final Gson gson = new Gson();
    public static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public static final Map logMap = new ConcurrentHashMap<Connection, String>();
    @Override
    public String name() {
        return "PostgresqlLog";
    }

    @Override
    public Mono<Void> filter(HttpRequest request, HttpResponse response, PluginFilterChain chain) {
        // 缓存日志
        saveLog(request);
        System.out.println("saveLog");
        return chain.filter(request, response);
    }

    /**
     * 缓存日志
     * 
     * @param request
     */
    private void saveLog(HttpRequest request) {
        // 获取插件的参数配置
        String configStr = request.getConfig(this);
        Map<String, Object> conf = new HashMap<>();
        conf = gson.fromJson(configStr, conf.getClass());

        Connection connection = getConnection(conf);
        System.out.println("getConnection");
        String log = getLog(request);
        logMap.put(connection, log);

        // 缓存超5条直接发送日志
        if (logMap.size() > 5) {
            System.out.println("logMap.size>5");
            sendLogBatcher();
        }
    }

    /**
     * 发送日志批处理器
     * 
     * @return
     */
    public void sendLogBatcher() {
        Set connections = logMap.keySet();
        for (Object connection : connections) {
            System.out.println("sendLog");
            sendLog((Connection) connection, retryNum);
        }
    }

    private void sendLog(Connection connection, int retryNum) {
        if (retryNum == 0) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            logMap.remove(connection);
            return;
        }
        String log = (String) logMap.get(connection);
        int modifideLine = 0;
        try {
            System.out.println("log:     "+log);
            PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO LOG (log) VALUES (?)");
            preparedStatement.setString(1, log);
            modifideLine = preparedStatement.executeUpdate();
            System.out.println("modifideLine:     "+modifideLine);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            if (modifideLine == 1) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
                logMap.remove(connection);
            } else {
                sendLog(connection, --retryNum);
            }
        }
    }

    private String getLog(HttpRequest request) {
        long requestId = request.getRequestId();
        String path = request.getPath();
        String method = request.getMethod().toString();
        Map<String, String> args = request.getArgs();
        Map<String, String> header = request.getHeader();
        String sourceIP = request.getSourceIP();
        String format = dateFormat.format(new Date());
        Map<String, Object> logMap = new HashMap<>();
        logMap.put("requestId", requestId);
        logMap.put("path", path);
        logMap.put("method", method);
        logMap.put("args", args);
        logMap.put("header", header);
        logMap.put("sourceIP", sourceIP);
        logMap.put("date", format);
        String log = gson.toJson(logMap);
        return log;
    }

    private Connection getConnection(Map<String, Object> conf) {
        Connection connection = null;
        String dbname = (String) conf.get("dbname");
        String host = (String) conf.get("host");
        String username = (String) conf.get("username");
        String password = (String) conf.get("password");
        String port = (String) conf.get("port");
        String url = "jdbc:postgresql://" + host + ":" + port + "/" + dbname;
        try {
            Class.forName("org.postgresql.Driver");
            connection = DriverManager.getConnection(url, username, password);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }

        return connection;
    }
}

修改checkstyle.xml

因为源码拉下来,里面用代码检查,小弟不才,代码写得垃圾,服务器上打包时候checkstyle通不过打不成tar.gz包。所以索性把apisix-java-plugin-runner-main\src\main\checkstyle\checkstyle.xml的module标签全注释了,如下所示

<!DOCTYPE module PUBLIC
        "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
        "http://checkstyle.org/dtds/configuration_1_3.dtd">
<module name="Checker">
</module>

打包

直接在项目根路径下压缩成zip文件,扔apisix容器里

配置apisix,启动apisix-java-plugin-runner

解压zip,构建jar包

解压上述操作传过来的zip文件,在文件根目录执行

./mvnw package

这个项目用了maven wrapper,可以不用额外安装maven即可下载依赖,帮助我们打包,具体可以看我另一篇博客。执行结束后会出现dist文件夹,解压该文件夹下的tar.gz文件,解压出的文件夹下有个jar包,就是我们要执行的java插件运行器。

配置apisix,执行jar包,修改runner.socket权限,重启apisix

测试apisix-java-plugin-runner,需要配置apisix,执行jar包,修改runner.socket权限,重启apisix。如果生产环境的话,可以省去执行jar包这一步骤。

在/usr/local/apisix/conf下配置config.yaml,增加

#测试下增加这个
ext-plugin:
  path_for_test: /tmp/runner.sock
#生产环境下增加这个,xxxx/apisix-java-plugin-runner.jar是上述jar包的位置
ext-plugin:
  cmd: ['java', '-jar', '-Xmx4g', '-Xms4g', '/xxxx/apisix-java-plugin-runner.jar']

执行jar包(仅测试时)

#/xxxx/apisix-java-plugin-runner.jar是上述jar包的位置
java -jar -DAPISIX_LISTEN_ADDRESS=unix:/tmp/runner.sock -DAPISIX_CONF_EXPIRE_TIME=3600 /xxxx/apisix-java-plugin-runner.jar

修改runner.socket权限,避免插件执行失败。这个文件是jar启动后生成的,插件运行器用它来和apisix通信,所以jar要先启动起来。

chmod 777 /tmp/runner.socket

重启apisix

apisix restart

配置路由,测试

配置路由,这里用的是ext-plugin-post-req插件,具体见下节“运行原理”的内容。

curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
    "uri":"/myinfo",
    "plugins":{
        "ext-plugin-post-req":{
            "conf":[
                {
                    "name":"PostgresqlLog",
                    # 必须有value属性,值可以为空
                    "value":"{\"dbname\":\"test\",\"host\":\"159.75.26.246\",\"port\":\"5432\",\"username\":\"postgres\",\"password\":\"xxxxxx\"}"
                }
            ]
        }
    },
    "upstream":{
        "nodes":{
            "159.75.26.246:80":1
        },
        "type":"roundrobin"
    }
}'

测试成功,数据库有数据插入,如图所示。
image

apisix-java-plugin-runner运行原理

运行原理

下图左边是 Apache APISIX 的工作流程,右边的 plugin runner 是指插件运行器,泛指多语言支持的项目。本文提到的 apisix-java-plugin-runner 项目就是支持 Java 语言的 plugin runner。

当你在 Apache APISIX 中配置一个 plugin runner 时,Apache APISIX 会启动一个子进程运行 plugin runner,该子进程与 Apache APISIX 进程属于同一个用户。当我们重启或重新加载 Apache APISIX 时,plugin runner 也将被重启。

如果你为一个给定的路由配置了 ext-plugin-* 插件,命中该路由的请求将触发 Apache APISIX,通过 unix socket 向 plugin runner 执行 RPC 调用。调用细分为两个阶段:

ext-plugin-pre-req: 在执行 Apache APISIX 内置插件(Lua 语言插件)之前;ext-plugin-post-req: 在执行 Apache APISIX 内置插件(Lua 语言插件)之后。根据需要配置 plugin runner 的执行时机。

plugin runner 会处理 RPC 调用,在其内部创建一个模拟请求,然后运行多语言编写的插件,并将结果返回给 Apache APISIX。

多语言插件的执行顺序是在 ext-plugin-* 插件配置项中定义的。像其他插件一样,它们可以被启用并在运行中重新定义。