Common load balancing schemes include F5, HAProxy, and LOADBalance in JDBC. F5 is not something that developers can play on their own. HAProxy needs to be deployed independently and there is additional network overhead if deployed on machines outside the database cluster. JDBC loadBalance is built into the program, directly configure the URL, simple configuration, but also saves the extra network overhead. Usage:
jdbc:mysql:loadbalance://[host1][:port],[host2][:port][,[host3][:port]]... [/[database]] [?propertyName1=propertyValue1[&propertyName2=propertyValue2]...]Copy the code
Connector/J has long provided an effective means to distribute read/write load across multiple MySQL server instances for Cluster or source-source replication deployments. You can 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.
The trial
Using JDBC LoadBalance in TiDB, write a simple program that sets the number of threads to 40 to access the database. Insert while true, so it should be 40 long connections. However, monitoring shows that in a cluster with 4 TiDB nodes, each instance has 40 Connections, which is somewhat counterintuitive.
Using HAProxy, the number of connections was not very balanced, but the number was as expected.
Create a link
Try to see the JDBC source, loadbalance logic is roughly in LoadBalancedConnectionProxy. In Java,
A proxy for a dynamic com.mysql.cj.jdbc.JdbcConnection implementation that load balances requests across a series of MySQL JDBC connections
There are three load balancing modes in JDBC, and loadBalanceStrategy needs to be configured in URL. There are three optional parameters, which are random, bestResponseTime, and serverAffinity. They both implement the BalanceStrategy interface, with only one abstract method, pickConnection. The default is random mode, so I’m only looking at the logic here.
abstract JdbcConnection pickConnection(InvocationHandler proxy, List<String> configuredHosts, Map<String, JdbcConnection> liveConnections,long[] responseTimes, int numRetries) throws SQLException;
Copy the code
Just as its name implies is to realize the load balance which one to choose the host open connection point, corresponding equalizer is: RandomBalanceStrategy, BestResponseTimeBalanceStrategy, ServerAffinityStrategy.
In [email protected] will case different load balancing mode
switch (strategy) {
case "random":
this.balancer = new RandomBalanceStrategy();
break;
case "bestResponseTime":
this.balancer = new BestResponseTimeBalanceStrategy();
break;
case "serverAffinity":
this.balancer = new ServerAffinityStrategy(props.getProperty(PropertyKey.serverAffinityOrder.getKeyName(), null));
break;
default:
this.balancer = (BalanceStrategy) Class.forName(strategy).newInstance();
}
Copy the code
PickConnection () is executed. If this.currentConnection == null, the connection has just been initialized and the host connection needs to be obtained by the selected mechanism.
this.currentConnection = this.balancer.pickConnection(this, hostPortList, Collections.unmodifiableMap(this.liveConnections),this.responseTimes.clone(), this.retriesAllDown);
Copy the code
Came to RandomBalanceStrategy. Java, you can see the JDBC will get a random number, random range is the hostList can now use the size of size, according to the index to randomly access to create the connection after the host, Then go to the liveConnections map to obtain the existing connection, reuse if there is, create a new.
int random = (int) Math.floor((Math.random() * whiteList.size()));
String hostPortSpec = whiteList.get(random);
ConnectionImpl conn = (ConnectionImpl) liveConnections.get(hostPortSpec);
if (conn == null) {
conn = ((LoadBalancedConnectionProxy) proxy).createConnectionForHost(hostPortSpec);
}
return conn;
Copy the code
This logic seems silly, not considering whether there are already many connections on the selected node. At this point, the loadBalance logic is not finished.
Release the link
In the case of AutoCOMMIT =false, JDBC causes the current client thread to re-select Connection after each COMMIT or rollback. If autoCOMMIT =true, the original connection will always be used and will not be re-selected.
if ("commit".equals(methodName) || "rollback".equals(methodName)) {
this.inTransaction = false;
pickNewConnection();
}
Copy the code
So keep executing pickNewConnection(), again picking connection logic above, randomly picking host to create or reuse conneciton. This is why the number of connections will multiply. Assuming that there are host1 and host2, because the host is re-selected at the end of each commit, no matter how many hosts there are, as long as the program runs long enough, they will always be selected at least once. Connection count = thread * host; most connections are idle. The puzzle here is that, in fact, this is not a complete load balancing. After all, it is random. Only when there are enough threads, it will tend to be balanced. Another problem is that re-selecting Connection each time may incur additional overhead. Ask this question at Connector/J’s official Slack, and the answer is yes
This mechanism is very reasonable. If there are three nodes and only one thread accesses the database, it should also be allowed to randomly select hosts from the three nodes to access the database. It should not be tied to a node to access the database.
Reform attempt
A connection multiplier created may result in a multiplier increase in TiDB node resource utilization, which in some cases may have a performance cost. Decided to give it a try. We fork the JDBC repO and simply implement a new balanced pattern lasting, which is selected based on the number of connections on each host. That is, select the host with the fewest connections each time. No longer re-pickConneciton after commit.
The test results
Accessing the database with 1000 concurrent requests: Random mode first, Lasting mode second.
As you can see, in chevy mode, the number of goroutines and memory usage of TiDB nodes are reduced by a large percentage. Connection Idle Duration has also dropped considerably. It seems to be having some effect.
Appendix – Build Connector/J 8
Install Ant
brew install ant
Copy the code
Third-party libraries
Download the following third-party libraries and place them in a file,
Search.maven.org/artifact/or… Search.maven.org/artifact/or… Search.maven.org/artifact/or… Search.maven.org/artifact/or… Search.maven.org/artifact/or… Search.maven.org/artifact/or… Search.maven.org/artifact/or… Search.maven.org/artifact/or… Search.maven.org/artifact/or… Search.maven.org/artifact/co… Search.maven.org/artifact/co… Search.maven.org/artifact/or… Search.maven.org/artifact/or…
Clone jdbc
Git clone - branch in release 8.0 / https://github.com/mysql/mysql-connector-j.gitCopy the code
To configure the build properties
Notice that the official website says the following, but you don’t really need to add path_to, just write the following
In the directory, create a file named build.properties to indicate to Ant the locations of the root directories for your JDK 1.8.x installation, as well as the location of the extra libraries. The file should contain the following property settings, with the “path_to_*” parts replaced by the appropriate file paths:
The default JDK directory on the MAC OS is /usr/libexec/java_home -v.
com.mysql.cj.build.jdk=8 jdk_1.
com.mysql.cj.extra.libs=folder_for_extra_libraries
Copy the code
Build
Buildfile: /Users/yuyang/IdeaProjects/mysql-connector-j/build.xml -extra-libs-check: [taskdef] Could not load definitions from resource org/jacoco/ant/antlib.xml. It could not be found. -jdk-check: -compiler-check: clean: [delete] Deleting directory /Users/yuyang/IdeaProjects/mysql-connector-j/build -load-info-properties: -init-copy-common: [mkdir] Created dir: /Users/yuyang/IdeaProjects/mysql-connector-j/build [copy] Copying 532 files to / Users/yuyang IdeaProjects j/build/mysql/mysql - connector - - connector - Java - 8.0.22 - the SNAPSHOT - init - filter - license: -init-no-crypto: -init-license-headers: -init-copy: -init-notices-commercial: -init-notices-gpl: [get] Getting: file:./LICENSE [get] To: / Users/yuyang IdeaProjects j/build/mysql/mysql - connector - - connector - Java - 8.0.22 - the SNAPSHOT/LICENSE [get]. [copy] Copying the file to/Users/yuyang IdeaProjects j/build/mysql/mysql - connector - - connector - Java - 8.0.22 - the SNAPSHOT -init-info-files: [echo] ## INFO_BIN ## [echo] build-date: 2021-04-26 16:29:27 +0800 [echo] os-info: Mac OS X x86_64 11.2.3 [echo] Compiler: Javac 1.8.0_251 [echo] build-tool: Compiled on April 12 2021 [echo] ## INFO_SRC ## [echo] version: 8.0.22-SNAPSHOT [echo] branch: release/8.0 [echo] Date: 2020-08-07 22:42:18 +0100 [echo] commit: d64b664fa93e81296a377de031b8123a67e6def2 [echo] short: d64b664f init: -clean-output: compile-driver: [echo] Compiling MySQL Connector/J JDBC implementation with '/ Library/Java/JavaVirtualMachines jdk1.8.0 _251. JDK/Contents/Home' to 'build/mysql connector - Java - 8.0.22 - the SNAPSHOT' [javac] Compiling 510 source files to / Users/yuyang IdeaProjects j/build/mysql/mysql - connector - - connector - Java - 8.0.22 - the SNAPSHOT (javac) Creating the empty / Users/yuyang IdeaProjects j/build/mysql/mysql - connector - - connector - Java - 8.0.22 - the SNAPSHOT/com/mysql/cj/xdevapi/package - inf o.class [javac] Creating empty / Users/yuyang IdeaProjects j/build/mysql/mysql - connector - - connector - Java - 8.0.22 - the SNAPSHOT/com/mysql/cj/x/protobuf package - info.class [java] Applying CommonChecks. [java] Applying TranslateExceptions. [java] Applying AddMethods. -compile-integration-c3p0: [echo] Compiling MySQL Connector/J-c3p0 integration with '/ Library/Java/JavaVirtualMachines jdk1.8.0 _251. JDK/Contents/Home' to 'build/mysql connector - Java - 8.0.22 - the SNAPSHOT' [javac] Compiling 1 source file to / Users/yuyang IdeaProjects j/build/mysql/mysql - connector - - connector - Java - 8.0.22 - the SNAPSHOT compile - integration: the compile: dist: [mkdir] Created dir: / Users/yuyang IdeaProjects j/build/mysql/mysql - connector - - connector - Java - 8.0.22 - the SNAPSHOT/meta-inf/services/copy Copying 4 files to/Users/yuyang IdeaProjects j/build/mysql/mysql - connector - - connector - Java - 8.0.22 - the SNAPSHOT/meta-inf/jar Building jar: / Users/yuyang IdeaProjects j/build/mysql/mysql - connector - - connector - Java - 8.0.22 - the SNAPSHOT/mysql connector - Java - 8.0.22 - SNAP SHOT.jar BUILD SUCCESSFUL Total time: 17 secondsCopy the code