Description:
During use of LoadbalanceProxyConnection with multi-host, a quick switch may cause normal sever still kept in blacklist during loadBalanceBlacklistTimeout.
As to TPS reduction situation, consider a simple example:
Two hosts, located at two seperately machine marked as A and B serve for jdbc loadbalance.
Machine A shutdown, witch is added into blacklist. When machine A recovers, machine B shutdown in no time.
Now machine A can serve normally, but it's still kept in blacklist with long delay until loadBalanceBlacklistTimeout reaches, leading to low TPS.
Test result describe:
```
loadBalanceBlacklistTimeout=60000
Time          Network Action             MySQL Cluster Status        TPS&Client Status               Blacklist   
...           -                          mysql#A=Ok,mysql#B=Ok       TPS=5, Normal                   None
time0         Drop mysql#A               mysql#A=Drop,mysql#B=Ok     TPS=5, Normal                   mysql#A
time0+5s      Recover mysql#A            mysql#A=Ok,mysql#B=Ok       TPS=5, Normal                   mysql#A
time0+10s     Drop mysql#B               mysql#A=Ok,mysql#B=Drop     TPS=0 or 1, Abnormal            mysql#A,mysql#B
...
time0+59s     Drop mysql#B               mysql#A=Ok,mysql#B=Drop     TPS=0 or 1, Abnormal            mysql#A,mysql#B
time0+60s~    Drop mysql#B               mysql#A=Ok,mysql#B=Drop     TPS=5, Normal                   mysql#B
```
From [time0+10s] to  [time0+59s], client status is abnormal, mysql#A kept in blacklist can serve normally indeed.
How to repeat:
1. Test code:
    
    ```
    private static String driver = "com.mysql.jdbc.Driver";
    private static String url = "jdbc:mysql:loadbalance://172.21.34.87:40991,172.21.34.87:40992/schema_test_jdbc?useSSL=false&connectTimeout=3000&socketTimeout=3000&loadBalanceBlacklistTimeout=60000";
    private static String user = "test";
    private static String password = "test";
    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private static Connection c;
    static void executeSimpleTransaction(String name, Connection c){
        try { 
            c.setAutoCommit(false); 
            Statement s = c.createStatement();
            String row_res;
            ResultSet rs = s.executeQuery("SELECT SLEEP(0.2)"); 
            while(rs.next()){
                row_res = rs.getString("SLEEP(0.2)");
            }
            rs.close();
            c.commit(); 
        } catch (SQLException e) {
            System.out.println(name + " execute Query fail at " + sdf.format(new Date()));
            e.printStackTrace(); 
        } 
    } 
     static Connection getNewConnection() throws SQLException, ClassNotFoundException {
        Class.forName("com.mysql.jdbc.Driver");
        return DriverManager.getConnection(url, user, password);
     }
     
     public static void main(String[] args) throws Exception {
        long tx_count = 0;
        long pre_tx_count = 0;
        long current_time = new Date().getTime();
        long previous_time = current_time;
        int re_connect = -1;
        int i = 0;
        String thd_name="TEST-JDBC ";
        while(true) {
            try {
                current_time = new Date().getTime();
                if (current_time - previous_time >= 3000) {
                    System.out.println(thd_name + sdf.format(new Date()) + " transaction TPS:" + (tx_count-pre_tx_count)/((current_time - previous_time)/1000)); /* Print current TPS */
                    pre_tx_count = tx_count;
                    previous_time = current_time;
                }
                if (re_connect != 0) {
                    c = getNewConnection();
                    i = i + 1;
                    re_connect = 0;
                }
                executeSimpleTransaction(thd_name, c);
                tx_count = tx_count+1;
            } catch(SQLException e) {
                System.out.println(thd_name + " connect fail at " + sdf.format(new Date()));
                e.printStackTrace();
            } catch (Exception e) {
                System.out.println(thd_name + " throw exception at " + sdf.format(new Date()));
                e.printStackTrace();
            }
        }
     }
     ```
     
3. Simulate step:
In test program machine, simulate server machine shutdown with root user:
root@dev3498:~> iptables -F
root@dev3498:~> date; iptables -A OUTPUT -p tcp -d 172.21.34.87 --dport 40991 -j DROP
Wed Jul 24 13:47:48 CST 2019
root@dev3498:~> date; iptables -D OUTPUT -p tcp -d 172.21.34.87 --dport 40991 -j DROP  
Wed Jul 24 13:47:52 CST 2019
root@dev3498:~> date; iptables -A OUTPUT -p tcp -d 172.21.34.87 --dport 40992 -j DROP 
Wed Jul 24 13:47:55 CST 2019
Then a significant response latency and TPS reduction will be observed, sustaining about 60s, which depends on loadBalanceBlacklistTimeout setting.
4. Corresponding log info:
TEST-JDBC 2019-07-24 13:47:45 transaction TPS:5
TEST-JDBC  execute Query fail at 2019-07-24 13:47:52
com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Communications link failure during commit(). Transaction resolution unknown.
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
        at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
        at com.mysql.jdbc.Util.getInstance(Util.java:408)
        at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:918)
        at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:897)
        at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:886)
        at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:860)
        at com.mysql.jdbc.ConnectionImpl.commit(ConnectionImpl.java:1559)
        at com.mysql.jdbc.MultiHostMySQLConnection.commit(MultiHostMySQLConnection.java:129)
        at sun.reflect.GeneratedMethodAccessor7.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at com.mysql.jdbc.LoadBalancedConnectionProxy.invokeMore(LoadBalancedConnectionProxy.java:495)
        at com.mysql.jdbc.MultiHostConnectionProxy.invoke(MultiHostConnectionProxy.java:457)
        at com.sun.proxy.$Proxy0.commit(Unknown Source)
        at com.companyname.bank.App.executeSimpleTransaction(App.java:149)
        at com.companyname.bank.App.main(App.java:121)
TEST-JDBC 2019-07-24 13:47:52 transaction TPS:2
TEST-JDBC 2019-07-24 13:47:55 transaction TPS:5
TEST-JDBC  execute Query fail at 2019-07-24 01:48:01
com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Communications link failure during commit(). Transaction resolution unknown.
......
TEST-JDBC 2019-07-24 13:48:01 transaction TPS:0
TEST-JDBC 2019-07-24 13:48:05 transaction TPS:1
TEST-JDBC 2019-07-24 13:48:08 transaction TPS:0
TEST-JDBC 2019-07-24 13:48:13 transaction TPS:2
TEST-JDBC 2019-07-24 13:48:17 transaction TPS:1
TEST-JDBC 2019-07-24 13:48:20 transaction TPS:1
TEST-JDBC 2019-07-24 13:48:23 transaction TPS:0
TEST-JDBC 2019-07-24 13:48:27 transaction TPS:0
TEST-JDBC 2019-07-24 13:48:30 transaction TPS:1
TEST-JDBC 2019-07-24 13:48:34 transaction TPS:1
TEST-JDBC 2019-07-24 13:48:37 transaction TPS:0
TEST-JDBC 2019-07-24 13:48:40 transaction TPS:0
TEST-JDBC 2019-07-24 13:48:44 transaction TPS:0
TEST-JDBC 2019-07-24 13:48:47 transaction TPS:1
TEST-JDBC 2019-07-24 13:48:50 transaction TPS:0
TEST-JDBC 2019-07-24 13:48:53 transaction TPS:5
Suggested fix:
When host serves normally, remove host from Blacklist.
```
diff --git a/src/com/mysql/jdbc/LoadBalancedConnectionProxy.java b/src/com/mysql/jdbc/LoadBalancedConnectionProxy.java
index 80ec7696..84cd130f 100644
--- a/src/com/mysql/jdbc/LoadBalancedConnectionProxy.java
+++ b/src/com/mysql/jdbc/LoadBalancedConnectionProxy.java
@@ -371,6 +371,8 @@ public class LoadBalancedConnectionProxy extends MultiHostConnectionProxy implem
     public synchronized ConnectionImpl createConnectionForHost(String hostPortSpec) throws SQLException {
         ConnectionImpl conn = super.createConnectionForHost(hostPortSpec);
 
+        this.removeFromGlobalBlacklist(hostPortSpec);
+
         this.liveConnections.put(hostPortSpec, conn);
         this.connectionsToHostsMap.put(conn, hostPortSpec);
 
@@ -639,6 +641,20 @@ public class LoadBalancedConnectionProxy extends MultiHostConnectionProxy implem
         addToGlobalBlacklist(host, System.currentTimeMillis() + this.globalBlacklistTimeout);
     }
 
+    /**
+     * Removes a host from the blacklist.
+     * 
+     * @param host
+     *            The host need remove from the blacklist.
+     */
+    public void removeFromGlobalBlacklist(String host) {
+        if (isGlobalBlacklistEnabled()) {
+            synchronized (globalBlacklist) {
+                globalBlacklist.remove(host);
+            }
+        }
+    }
+
     /**
      * Checks if host blacklist management was enabled.
      */
```
  
 
 
 
 
 
 
 
Description: During use of LoadbalanceProxyConnection with multi-host, a quick switch may cause normal sever still kept in blacklist during loadBalanceBlacklistTimeout. As to TPS reduction situation, consider a simple example: Two hosts, located at two seperately machine marked as A and B serve for jdbc loadbalance. Machine A shutdown, witch is added into blacklist. When machine A recovers, machine B shutdown in no time. Now machine A can serve normally, but it's still kept in blacklist with long delay until loadBalanceBlacklistTimeout reaches, leading to low TPS. Test result describe: ``` loadBalanceBlacklistTimeout=60000 Time Network Action MySQL Cluster Status TPS&Client Status Blacklist ... - mysql#A=Ok,mysql#B=Ok TPS=5, Normal None time0 Drop mysql#A mysql#A=Drop,mysql#B=Ok TPS=5, Normal mysql#A time0+5s Recover mysql#A mysql#A=Ok,mysql#B=Ok TPS=5, Normal mysql#A time0+10s Drop mysql#B mysql#A=Ok,mysql#B=Drop TPS=0 or 1, Abnormal mysql#A,mysql#B ... time0+59s Drop mysql#B mysql#A=Ok,mysql#B=Drop TPS=0 or 1, Abnormal mysql#A,mysql#B time0+60s~ Drop mysql#B mysql#A=Ok,mysql#B=Drop TPS=5, Normal mysql#B ``` From [time0+10s] to [time0+59s], client status is abnormal, mysql#A kept in blacklist can serve normally indeed. How to repeat: 1. Test code: ``` private static String driver = "com.mysql.jdbc.Driver"; private static String url = "jdbc:mysql:loadbalance://172.21.34.87:40991,172.21.34.87:40992/schema_test_jdbc?useSSL=false&connectTimeout=3000&socketTimeout=3000&loadBalanceBlacklistTimeout=60000"; private static String user = "test"; private static String password = "test"; private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); private static Connection c; static void executeSimpleTransaction(String name, Connection c){ try { c.setAutoCommit(false); Statement s = c.createStatement(); String row_res; ResultSet rs = s.executeQuery("SELECT SLEEP(0.2)"); while(rs.next()){ row_res = rs.getString("SLEEP(0.2)"); } rs.close(); c.commit(); } catch (SQLException e) { System.out.println(name + " execute Query fail at " + sdf.format(new Date())); e.printStackTrace(); } } static Connection getNewConnection() throws SQLException, ClassNotFoundException { Class.forName("com.mysql.jdbc.Driver"); return DriverManager.getConnection(url, user, password); } public static void main(String[] args) throws Exception { long tx_count = 0; long pre_tx_count = 0; long current_time = new Date().getTime(); long previous_time = current_time; int re_connect = -1; int i = 0; String thd_name="TEST-JDBC "; while(true) { try { current_time = new Date().getTime(); if (current_time - previous_time >= 3000) { System.out.println(thd_name + sdf.format(new Date()) + " transaction TPS:" + (tx_count-pre_tx_count)/((current_time - previous_time)/1000)); /* Print current TPS */ pre_tx_count = tx_count; previous_time = current_time; } if (re_connect != 0) { c = getNewConnection(); i = i + 1; re_connect = 0; } executeSimpleTransaction(thd_name, c); tx_count = tx_count+1; } catch(SQLException e) { System.out.println(thd_name + " connect fail at " + sdf.format(new Date())); e.printStackTrace(); } catch (Exception e) { System.out.println(thd_name + " throw exception at " + sdf.format(new Date())); e.printStackTrace(); } } } ``` 3. Simulate step: In test program machine, simulate server machine shutdown with root user: root@dev3498:~> iptables -F root@dev3498:~> date; iptables -A OUTPUT -p tcp -d 172.21.34.87 --dport 40991 -j DROP Wed Jul 24 13:47:48 CST 2019 root@dev3498:~> date; iptables -D OUTPUT -p tcp -d 172.21.34.87 --dport 40991 -j DROP Wed Jul 24 13:47:52 CST 2019 root@dev3498:~> date; iptables -A OUTPUT -p tcp -d 172.21.34.87 --dport 40992 -j DROP Wed Jul 24 13:47:55 CST 2019 Then a significant response latency and TPS reduction will be observed, sustaining about 60s, which depends on loadBalanceBlacklistTimeout setting. 4. Corresponding log info: TEST-JDBC 2019-07-24 13:47:45 transaction TPS:5 TEST-JDBC execute Query fail at 2019-07-24 13:47:52 com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Communications link failure during commit(). Transaction resolution unknown. at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at com.mysql.jdbc.Util.handleNewInstance(Util.java:425) at com.mysql.jdbc.Util.getInstance(Util.java:408) at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:918) at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:897) at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:886) at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:860) at com.mysql.jdbc.ConnectionImpl.commit(ConnectionImpl.java:1559) at com.mysql.jdbc.MultiHostMySQLConnection.commit(MultiHostMySQLConnection.java:129) at sun.reflect.GeneratedMethodAccessor7.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.mysql.jdbc.LoadBalancedConnectionProxy.invokeMore(LoadBalancedConnectionProxy.java:495) at com.mysql.jdbc.MultiHostConnectionProxy.invoke(MultiHostConnectionProxy.java:457) at com.sun.proxy.$Proxy0.commit(Unknown Source) at com.companyname.bank.App.executeSimpleTransaction(App.java:149) at com.companyname.bank.App.main(App.java:121) TEST-JDBC 2019-07-24 13:47:52 transaction TPS:2 TEST-JDBC 2019-07-24 13:47:55 transaction TPS:5 TEST-JDBC execute Query fail at 2019-07-24 01:48:01 com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Communications link failure during commit(). Transaction resolution unknown. ...... TEST-JDBC 2019-07-24 13:48:01 transaction TPS:0 TEST-JDBC 2019-07-24 13:48:05 transaction TPS:1 TEST-JDBC 2019-07-24 13:48:08 transaction TPS:0 TEST-JDBC 2019-07-24 13:48:13 transaction TPS:2 TEST-JDBC 2019-07-24 13:48:17 transaction TPS:1 TEST-JDBC 2019-07-24 13:48:20 transaction TPS:1 TEST-JDBC 2019-07-24 13:48:23 transaction TPS:0 TEST-JDBC 2019-07-24 13:48:27 transaction TPS:0 TEST-JDBC 2019-07-24 13:48:30 transaction TPS:1 TEST-JDBC 2019-07-24 13:48:34 transaction TPS:1 TEST-JDBC 2019-07-24 13:48:37 transaction TPS:0 TEST-JDBC 2019-07-24 13:48:40 transaction TPS:0 TEST-JDBC 2019-07-24 13:48:44 transaction TPS:0 TEST-JDBC 2019-07-24 13:48:47 transaction TPS:1 TEST-JDBC 2019-07-24 13:48:50 transaction TPS:0 TEST-JDBC 2019-07-24 13:48:53 transaction TPS:5 Suggested fix: When host serves normally, remove host from Blacklist. ``` diff --git a/src/com/mysql/jdbc/LoadBalancedConnectionProxy.java b/src/com/mysql/jdbc/LoadBalancedConnectionProxy.java index 80ec7696..84cd130f 100644 --- a/src/com/mysql/jdbc/LoadBalancedConnectionProxy.java +++ b/src/com/mysql/jdbc/LoadBalancedConnectionProxy.java @@ -371,6 +371,8 @@ public class LoadBalancedConnectionProxy extends MultiHostConnectionProxy implem public synchronized ConnectionImpl createConnectionForHost(String hostPortSpec) throws SQLException { ConnectionImpl conn = super.createConnectionForHost(hostPortSpec); + this.removeFromGlobalBlacklist(hostPortSpec); + this.liveConnections.put(hostPortSpec, conn); this.connectionsToHostsMap.put(conn, hostPortSpec); @@ -639,6 +641,20 @@ public class LoadBalancedConnectionProxy extends MultiHostConnectionProxy implem addToGlobalBlacklist(host, System.currentTimeMillis() + this.globalBlacklistTimeout); } + /** + * Removes a host from the blacklist. + * + * @param host + * The host need remove from the blacklist. + */ + public void removeFromGlobalBlacklist(String host) { + if (isGlobalBlacklistEnabled()) { + synchronized (globalBlacklist) { + globalBlacklist.remove(host); + } + } + } + /** * Checks if host blacklist management was enabled. */ ```