Java连接proxysql管理端口

Java连接proxysql管理端口

为什么不使用JDBC

总所周知,proxysql管理端口实现了mysql协议,可以使用mysql client连接并使用类似SQL的语法查询和修改配置。很多人基于此就大胆地说proxysql管理端口可以使用诸如navicat等工具去连接并管理,我试过之后严重怀疑他们并没有真的用naviacat操作过,因为根本就无法执行sql,只能尝试连接而已:
在这里插入图片描述
在这里插入图片描述
确实可连接,但是操作就别想了,因为任何展示和执行的操作都会报错[Err] 1045 - ProxySQL Admin Error: near "SHOW": syntax error,问题很明显:navicat做了一些额外操作,除了执行正真的sql,还会执行额外的sql来查询一些展示需要的信息,但是proxysql admin是基于sqlite的,很多语法都不支持。

同样地,如果使用JDBC连接proxysql admin,一样会报错:[Err] 1045 - ProxySQL Admin Error: near "@": syntax error,因为JDBC会额外查询一些系统变量(比如SELECT @@report_host),这种语法一样也不是sqllite支持的,导致JDBC createConnection的时候报错。

解决方案

如果有一个纯的java mysql client就能解决这个问题了,它的功能只需要像mysql shell那样简单,不要任何额外的增强功能。自己google了一阵儿,github也搜索过一阵儿,但是都不理想。最后一个同事推荐了vertx社区使用的异步非阻塞式mysql client——jasync-sql,试用了一下效果很好。

代码示例:proxysqlclient

package github.clyoudu.proxysqlclient;

import com.alibaba.fastjson.JSON;
import com.github.jasync.sql.db.Connection;
import com.github.jasync.sql.db.QueryResult;
import com.github.jasync.sql.db.ResultSet;
import com.github.jasync.sql.db.RowData;
import com.github.jasync.sql.db.mysql.MySQLConnectionBuilder;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * Create by IntelliJ IDEA
 *
 * @author chenlei
 * @dateTime 2019/7/26 17:39
 * @description ProxysqlClient
 */
public class ProxysqlClient {

    private final static Logger log = LoggerFactory.getLogger(ProxysqlClient.class);

    /**
     * mysql 默认连接的schema
     * proxysql 虽然没有这个schema,但并不会报错
     */
    public static final String DEFAULT_SCHEMA = "information_schema";
    private static final String EXECUTE_SQL_ERROR = "执行sql[%s]异常";
    private static final String EXECUTE_SQL_TIMEOUT = "执行sql[%s]超时";

    /**
     * 禁止用new实例化
     */
    private ProxysqlClient() {
    }

    /**
     * 执行proxysql语句,但不需要执行结果
     * 可以多条语句顺序执行,所有语句在同一个事务中执行
     * 注意:proxysql不支持事务,但并不会报错
     * 测试通过:mysql、proxysql
     *
     * @param ip          必填,服务IP
     * @param port        必填,服务端口
     * @param database    选填,数据库名称,默认 information_schema
     * @param user        必填,用户名
     * @param pass        必填,密码
     * @param sqlList     必填,待执行的sql列表
     * @param timeout     选填,超时时间,单位s,默认不设超时时间
     * @param transaction 是否开启事务
     * @return 成功or失败
     * @throws ProxysqlException 所有本方法可处理的异常都包装成 github.clyoudu.proxysqlclient.ProxysqlException,外层可单独catch此异常做处理
     */
    public static boolean execute(String ip, Integer port, String database, String user, String pass, List<String> sqlList, Integer timeout, boolean transaction) {
        //参数检查
        if (StringUtils.isBlank(ip) || port == null || StringUtils.isBlank(user) || StringUtils.isBlank(pass)) {
            throw new ProxysqlException("参数错误:ip/port/user/pass不能为空");
        }

        //默认连接information_schema
        if (StringUtils.isBlank(database)) {
            database = DEFAULT_SCHEMA;
        }

        if (sqlList == null || sqlList.isEmpty()) {
            throw new ProxysqlException("执行sql为空");
        }

        Connection connection = MySQLConnectionBuilder.createConnectionPool(
                String.format("jdbc:mysql://%s:%s/%s?user=%s&password=%s", ip, port, database, user, pass)
        );

        CompletableFuture<QueryResult> result = null;
        if (transaction) {
            result = connection.inTransaction(c -> {
                CompletableFuture<QueryResult> future = null;
                for (String sql : sqlList) {
                    if (future == null) {
                        future = c.sendQuery(sql);
                    } else {
                        future = future.thenCompose(r -> c.sendQuery(sql));
                    }
                }
                return future;
            });
        } else {
            for (String sql : sqlList) {
                if (result == null) {
                    result = connection.sendQuery(sql);
                } else {
                    result = result.thenCompose(r -> connection.sendQuery(sql));
                }
            }
        }

        if (result == null) {
            log.error("执行sql失败!");
            return false;
        }

        try {
            if (timeout != null && timeout > 0) {
                result.get(timeout, TimeUnit.SECONDS);
            } else {
                result.get();
            }
        } catch (InterruptedException e) {
            log.error(String.format(EXECUTE_SQL_ERROR, StringUtils.join(sqlList, ";")), e);
            Thread.currentThread().interrupt();
            throw new ProxysqlException(String.format(EXECUTE_SQL_ERROR, StringUtils.join(sqlList, ";")) + ": InterruptedException");
        } catch (ExecutionException e) {
            log.error(String.format(EXECUTE_SQL_ERROR, StringUtils.join(sqlList, ";")), e);
            throw new ProxysqlException(String.format(EXECUTE_SQL_ERROR, StringUtils.join(sqlList, ";")) + ": " + e.getMessage());
        } catch (TimeoutException e) {
            log.error(String.format(EXECUTE_SQL_ERROR, StringUtils.join(sqlList, ";")), e);
            throw new ProxysqlException(String.format(EXECUTE_SQL_TIMEOUT, StringUtils.join(sqlList, ";")) + ":" + timeout + "s");
        }

        connection.disconnect();

        return true;
    }

    /**
     * 执行proxysql语句,返回执行结果
     *
     * @param ip        必填,服务IP
     * @param port      必填,服务端口
     * @param database  选填,数据库名称,默认 information_schema
     * @param user      必填,用户名
     * @param pass      必填,密码
     * @param sqlString 必填,待执行的sql
     * @param timeout   选填,超时时间,单位s,默认不设超时时间
     * @return List<Map> 所有字段名均为数据库原始字段名
     * @throws ProxysqlException 所有本方法可处理的异常都包装成 github.clyoudu.proxysqlclient.ProxysqlException,外层可单独catch此异常做处理
     */
    public static List<Map<String, Object>> executeQuery(String ip, Integer port, String database, String user, String pass, String sqlString, Integer timeout) {
        //参数检查
        if (StringUtils.isBlank(ip) || port == null || StringUtils.isBlank(user) || StringUtils.isBlank(pass)) {
            throw new ProxysqlException("参数错误:ip/port/user/pass不能为空");
        }

        //默认连接information_schema
        if (StringUtils.isBlank(database)) {
            database = DEFAULT_SCHEMA;
        }

        if (StringUtils.isBlank(sqlString)) {
            throw new ProxysqlException("执行sql为空");
        }

        Connection connection = MySQLConnectionBuilder.createConnectionPool(
                String.format("jdbc:mysql://%s:%s/%s?user=%s&password=%s", ip, port, database, user, pass)
        );

        CompletableFuture<QueryResult> future = connection.sendQuery(sqlString);

        QueryResult queryResult;
        try {
            if (timeout == null || timeout <= 0) {
                queryResult = future.get();
            } else {
                queryResult = future.get(timeout, TimeUnit.SECONDS);
            }
        } catch (InterruptedException e) {
            log.error(String.format(EXECUTE_SQL_ERROR, sqlString), e);
            Thread.currentThread().interrupt();
            throw new ProxysqlException(String.format(EXECUTE_SQL_ERROR, sqlString) + ": InterruptedException");
        } catch (ExecutionException e) {
            log.error(String.format(EXECUTE_SQL_ERROR, sqlString), e);
            throw new ProxysqlException(String.format(EXECUTE_SQL_ERROR, sqlString) + ": " + e.getMessage());
        } catch (TimeoutException e) {
            log.error(String.format(EXECUTE_SQL_ERROR, sqlString), e);
            throw new ProxysqlException(String.format(EXECUTE_SQL_TIMEOUT, sqlString) + ":" + timeout + "s");
        }

        ResultSet resultSet = queryResult.getRows();
        List<String> names = resultSet.columnNames();
        List<Map<String, Object>> resultList = new ArrayList<>();

        for (RowData rowData : resultSet) {
            Map<String, Object> map = new HashMap<>(16);
            for (String name : names) {
                map.put(name, rowData.get(name));
            }
            resultList.add(map);
        }

        connection.disconnect();

        return resultList;
    }

    public static void main(String[] args) {
        log.info(JSON.toJSONString(executeQuery("localhost", 3306, "mysql", "root", "root",
                "select * from user",
                null)));
    }

}

使用方式:

    public static void main(String[] args) {
        //select
        log.info(JSON.toJSONString(executeQuery("localhost", 6032, "main", "root", "root",
                "select * from mysql_users",
                null)));
        //ddl & batch execute
        log.info(JSON.toJSONString(execute("localhost", 6032, "main", "root", "root",
                Arrays.asList("INSERT INTO mysql_users(username,password,default_hostgroup) VALUES ('app','pass',2)", "load mysql user to runtime", "save mysql user to disk"),
                null, false)));
    }

当然,jasync-sql不仅支持proxysql,任何符合mysql协议的server都可以使用jasync-sql作为client,因为它没有任何额外的增强。

支持批量执行,支持事务。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章