Connector/J has long provided an effective means to distribute read/write load across multiple MySQL server instances for Cluster or master-master replication deployments. Starting with Connector/J 5.1.3, you can now dynamically configure load-balanced connections, with no service outage. In-process transactions are not lost, and no application exceptions are generated if any application is trying to use that particular server instance.
There are two connection string options associated with this functionality:
loadBalanceConnectionGroup – This
provides the ability to group connections from different
sources. This allows you to manage these JDBC sources within
a single class loader in any combination you choose. If they
use the same configuration, and you want to manage them as a
logical single group, give them the same name. This is the
key property for management: if you do not define a name
(string) for loadBalanceConnectionGroup,
you cannot manage the connections. All load-balanced
connections sharing the same
loadBalanceConnectionGroup value,
regardless of how the application creates them, will be
managed together.
loadBalanceEnableJMX – The ability to
manage the connections is exposed when you define a
loadBalanceConnectionGroup, but if you
want to manage this externally, enable JMX by setting this
property to true. This enables a JMX
implementation, which exposes the management and monitoring
operations of a connection group. Further, start your
application with the
-Dcom.sun.management.jmxremote JVM flag.
You can then perform connect and perform operations using a
JMX client such as jconsole.
Once a connection has been made using the correct connection string options, a number of monitoring properties are available:
Current active host count.
Current active physical connection count.
Current active logical connection count.
Total logical connections created.
Total transaction count.
The following management operations can also be performed:
Add host.
Remove host.
The JMX interface,
com.mysql.jdbc.jmx.LoadBalanceConnectionGroupManagerMBean,
has the following methods:
int getActiveHostCount(String group);
int getTotalHostCount(String group);
long getTotalLogicalConnectionCount(String
group);
long getActiveLogicalConnectionCount(String
group);
long getActivePhysicalConnectionCount(String
group);
long getTotalPhysicalConnectionCount(String
group);
long getTotalTransactionCount(String
group);
void removeHost(String group, String host) throws
SQLException;
void stopNewConnectionsToHost(String group, String
host) throws SQLException;
void addHost(String group, String host, boolean
forExisting);
String getActiveHostsList(String group);
String getRegisteredConnectionGroups();
The getRegisteredConnectionGroups() method
returns the names of all connection groups defined in that class
loader.
You can test this setup with the following code:
public class Test {
private static String URL = "jdbc:mysql:loadbalance://" +
"localhost:3306,localhost:3310/test?" +
"loadBalanceConnectionGroup=first&loadBalanceEnableJMX=true";
public static void main(String[] args) throws Exception {
new Thread(new Repeater()).start();
new Thread(new Repeater()).start();
new Thread(new Repeater()).start();
}
static Connection getNewConnection() throws SQLException, ClassNotFoundException {
Class.forName("com.mysql.jdbc.Driver");
return DriverManager.getConnection(URL, "root", "");
}
static void executeSimpleTransaction(Connection c, int conn, int trans){
try {
c.setAutoCommit(false);
Statement s = c.createStatement();
s.executeQuery("SELECT SLEEP(1) /* Connection: " + conn + ", transaction: " + trans + " */");
c.commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
public static class Repeater implements Runnable {
public void run() {
for(int i=0; i < 100; i++){
try {
Connection c = getNewConnection();
for(int j=0; j < 10; j++){
executeSimpleTransaction(c, i, j);
Thread.sleep(Math.round(100 * Math.random()));
}
c.close();
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
After compiling, the application can be started with the
-Dcom.sun.management.jmxremote flag, to
enable remote management. jconsole can then
be started. The Test main class will be
listed by jconsole. Select this and click
Connect. You can then navigate to the
com.mysql.jdbc.jmx.LoadBalanceConnectionGroupManager
bean. At this point, you can click on various operations and
examine the returned result.
If you now had an additional instance of MySQL running on port
3309, you could ensure that Connector/J starts using it by using
the addHost(), which is exposed in
jconsole. Note that these operations can be
performed dynamically without having to stop the application
running.
Connector/J provides a useful load-balancing implementation
for Cluster or multi-master deployments. As of Connector/J
5.1.12, this same implementation is used for balancing load
between read-only slaves with
ReplicationDriver. When trying to balance
workload between multiple servers, the driver has to determine
when it is safe to swap servers, doing so in the middle of a
transaction, for example, could cause problems. It is
important not to lose state information. For this reason,
Connector/J will only try to pick a new server when one of the
following happens:
At transaction boundaries (transactions are explicitly committed or rolled back).
A communication exception (SQL State starting with "08") is encountered.
When a SQLException matches conditions
defined by user, using the extension points defined by the
loadBalanceSQLStateFailover,
loadBalanceSQLExceptionSubclassFailover
or loadBalanceExceptionChecker
properties.
The third condition revolves around three new properties
introduced with Connector/J 5.1.13. It allows you to control
which SQLExceptions trigger failover.
loadBalanceExceptionChecker - The
loadBalanceExceptionChecker property is
really the key. This takes a fully-qualified class name
which implements the new
com.mysql.jdbc.LoadBalanceExceptionChecker
interface. This interface is very simple, and you only
need to implement the following method:
public boolean shouldExceptionTriggerFailover(SQLException ex)
A SQLException is passed in, and a
boolean returned. A value of true
triggers a failover, false does not.
You can use this to implement your own custom logic. An example where this might be useful is when dealing with transient errors when using MySQL Cluster, where certain buffers may become overloaded. The following code snippet illustrates this:
public class NdbLoadBalanceExceptionChecker
extends StandardLoadBalanceExceptionChecker {
public boolean shouldExceptionTriggerFailover(SQLException ex) {
return super.shouldExceptionTriggerFailover(ex)
|| checkNdbException(ex);
}
private boolean checkNdbException(SQLException ex){
// Have to parse the message since most NDB errors
// are mapped to the same DEMC.
return (ex.getMessage().startsWith("Lock wait timeout exceeded") ||
(ex.getMessage().startsWith("Got temporary error")
&& ex.getMessage().endsWith("from NDB")));
}
}
The code above extends
com.mysql.jdbc.StandardLoadBalanceExceptionChecker,
which is the default implementation. There are a few
convenient shortcuts built into this, for those who want
to have some level of control using properties, without
writing Java code. This default implementation uses the
two remaining properties:
loadBalanceSQLStateFailover and
loadBalanceSQLExceptionSubclassFailover.
loadBalanceSQLStateFailover - allows
you to define a comma-delimited list of
SQLState code prefixes, against which a
SQLException is compared. If the prefix
matches, failover is triggered. So, for example, the
following would trigger a failover if a given
SQLException starts with "00", or is
"12345":
loadBalanceSQLStateFailover=00,12345
loadBalanceSQLExceptionSubclassFailover
- can be used in conjunction with
loadBalanceSQLStateFailover or on its
own. If you want certain subclasses of
SQLException to trigger failover,
simply provide a comma-delimited list of fully-qualified
class or interface names to check against. For example, if
you want all
SQLTransientConnectionExceptions to
trigger failover, you would specify:
loadBalanceSQLExceptionSubclassFailover=java.sql.SQLTransientConnectionException
While the three fail-over conditions enumerated earlier suit
most situations, if auto-commit is enabled,
Connector/J never re-balances, and continues using the same
physical connection. This can be problematic, particularly
when load-balancing is being used to distribute read-only load
across multiple slaves. However, Connector/J can be configured
to re-balance after a certain number of statements are
executed, when auto-commit is enabled. This
functionality is dependent upon the following properties:
loadBalanceAutoCommitStatementThreshold
– defines the number of matching statements which will
trigger the driver to potentially swap physical server
connections. The default value, 0, retains the behavior
that connections with auto-commit
enabled are never balanced.
loadBalanceAutoCommitStatementRegex –
the regular expression against which statements must
match. The default value, blank, matches all statements.
So, for example, using the following properties will cause
Connector/J to re-balance after every third statement that
contains the string “test”:
loadBalanceAutoCommitStatementThreshold=3 loadBalanceAutoCommitStatementRegex=.*test.*
loadBalanceAutoCommitStatementRegex can
prove useful in a number of situations. Your application
may use temporary tables, server-side session state
variables, or connection state, where letting the driver
arbitrarily swap physical connections before processing is
complete could cause data loss or other problems. This
allows you to identify a trigger statement that is only
executed when it is safe to swap physical connections.

User Comments
Add your own comment.