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,因爲它沒有任何額外的增強。

支持批量執行,支持事務。

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