背景说明

公司的整个电商系统搭建在华为云上,根据老总的估计,上线3个月之后日订单量会达到百万级别,保守估计3个月之后总订单个数预计会有5千万。MySQL单表达到千万级别,就会出现明显的性能问题。根据如此规模的数据,当时考虑了2套解决方案:

方案一:在业务上根据用户ID做拆分,将数据打散放在5台32U128G的华为云RDS上边

方案二:直接使用华为云的分布式数据库中间件DDM

方案一的好处是,分片算法全部在业务上实现,整个方案都在自己的控制下。后续问题定位,方案修改都会好很多;坏处是,整个方案需要业务代码支撑,访问到做了拆分的数据都需要做特殊处理,代价还是比较大的,而且对开发人员的能力要求很高。后续运维的工作也比较大。

方案二的好处是,直接使用云服务后续不需要担心运维的事情,另外DDM从中间件层屏蔽了分库分表的具体实现,业务可以当做单库来操作,易用性以及对代码的要求、对开发人员的要求都会低了很多。缺点就是,使用了DDM之后,对华为云的粘性会大很多。

综合考虑了两个方案的优缺点,最终选择了方案二,主要是基于对华为云技术能力和后续蓬勃发展的信心。

对DDM做了一定的调研,的确是一个非常不错的分库分表服务。支持超大规模数据,10备于单机数据库的超强性能,百万并发,读写分离,支持平滑扩容等等。。。优点数不胜数~

搭建到华为云之后,一直平稳运行,但是前阵子出了个奇怪的问题,在DDM技术专家的协助下,很快定位了出来,结果是MySQL-JDBC的一个bug导致。作为一个具有打破砂锅问到底、不破楼兰誓不还的程序员,决定对MySQL的相关参数做个详细的分析,免得从一个坑里边爬出来又进了另外一个。

Loadbalance模式说明

为了提供高性能,百万并发,DDM自身是以无状态的集群形式对外提供的。内部怎么做的我们不清楚,能看到的是,每个DDM提供了多个访问地址,每个库的访问url类似于:jdbc:mysql:loadbalance://192.168.0.35:5066,192.168.0.192:5066,192.168.0.175:5066,192.168.0.139:5066/orderdb?loadBalanceAutoCommitStatementThreshold=5

从访问的url看,内部应该是多台DDM节点的,实际上从我们测试的情况看,访问任何一台的效果都是一样的。猜测,内部的交互应该是类似如下图的:

跟DDM的技术专家求证,的确是如此的,心里有点小得意~~

我们的代码全部是java的代码,连接池用的是druid,根据DDM的指导,将url配置好就能正常访问了。感觉关健的就在loadbalance这个,应该是告诉了驱动,通过负载均衡方式访问DDM。在网上查了下,这种方式是直接在驱动层面做的负载均衡,相比通过负载均衡器的方式,少了一次网络转发,怪不得效率会这么高。不过,APP到底是访问哪个DDM,内部机制是什么样子的?这些在网上查了下,都是语焉不详,没办法只好从MySQL JDBC的源码入手了。

驱动的源码是托管在github上,我们当前用的是DDM推荐的5.1.44版本的:https://github.com/mysql/mysql-connector-j/tree/5.1.44

核心的就是几个Loadbalance开头的类:

代码比较多,其他的就不多说了,最关键的就是下边这块代码:

LoadBalancedConnectionProxy.java类的pickNewConnection()函数

这个函数在创建连接对象、一个事务提交或者回滚都会调用,作用就是轮换下一个DDM节点。这块代码的逻辑就是,根据一定的负载均衡策略挑选一个节点的连接,做个基本的连接有效性探测,然后将当前连接的状态同步到新连接(见 Table 2 MultiHostConnectionProxy.syncSessionState())。同步完毕,就把当前使用的连接设置为新挑选的连接。如果所有的连接都不可用,就把当前状态设置为了Closed状态。看着快代码,感觉MySQL的有些代码也不严谨,比如如果在获取新连接的时候,如果抛了SQLException出来,这个异常就直接被吃掉了,不会抛出去,也不会有任何信息记录下来,这个对后续的问题定位还是很不方便的,不知道是出于什么考虑的。

Table 1 LoadBalancedConnectionProxy.pickNewConnection()
synchronized void pickNewConnection() throws SQLException {
if (this.isClosed && this.closedExplicitly) {
return;
}
if (this.currentConnection == null) { // startup
this.currentConnection = this.balancer.pickConnection(this, Collections.unmodifiableList(this.hostList),
Collections.unmodifiableMap(this.liveConnections), this.responseTimes.clone(), this.retriesAllDown);
return;
}
if (this.currentConnection.isClosed()) {
invalidateCurrentConnection();
}
int pingTimeout = this.currentConnection.getLoadBalancePingTimeout();
boolean pingBeforeReturn = this.currentConnection.getLoadBalanceValidateConnectionOnSwapServer();
for (int hostsTried = 0, hostsToTry = this.hostList.size(); hostsTried < hostsToTry; hostsTried++) {
ConnectionImpl newConn = null;
try {
newConn = this.balancer.pickConnection(this, Collections.unmodifiableList(this.hostList), Collections.unmodifiableMap(this.liveConnections), this.responseTimes.clone(), this.retriesAllDown);
if (this.currentConnection != null) {
if (pingBeforeReturn) {
if (pingTimeout == 0) {
newConn.ping();
} else {
newConn.pingInternal(true, pingTimeout);
}
}
syncSessionState(this.currentConnection, newConn); }
this.currentConnection = newConn;
return;
} catch (SQLException e) {
if (shouldExceptionTriggerConnectionSwitch(e) && newConn != null) {
// connection error, close up shop on current connection
invalidateConnection(newConn);
}
}
}
// no hosts available to swap connection to, close up.
this.isClosed = true;
this.closedReason = "Connection closed after inability to pick valid new connection during load-balance.";
} Table 2 MultiHostConnectionProxy.syncSessionState()
static void syncSessionState(Connection source, Connection target, boolean readOnly) throws SQLException {
if (target != null) {
target.setReadOnly(readOnly);
}
if (source == null || target == null) {
return;
}
target.setAutoCommit(source.getAutoCommit());
target.setCatalog(source.getCatalog());
target.setTransactionIsolation(source.getTransactionIsolation());
target.setSessionMaxRows(source.getSessionMaxRows());
}

  

MySQL-JDBC Loadbalance参数说明

明白了MySQL-JDBC的Loadbalance的相关机制,最重要的还是要对相关的参数有个详细的了解,并且设置有效的值,Loadbalance相关一共有十几个参数,几个比较关键的如下表所示:

其他还有几个参数,一般用不到,也就不罗列出来了。大家感兴趣的话可以关注公众号:中间件小哥(zhongjianjianxiaoge)了解更多哟~

MySQL-JDBC Loadbalance深入解析的更多相关文章

  1. yml中driver-class-name: com.mysql.jdbc.Driver 解析不到的问题

    当在idea中使用springboot的快捷创建方式时,选中了mysql 和jdbc 那么pom文件中会直接有 <dependency> <groupId>mysql</ ...

  2. Solr DIH JDBC 源码解析

    Solr DIH 源码解析 DataImportHandler.handleRequestBody()中的importer.runCmd(requestParams, sw) if (DataImpo ...

  3. java连接mysql数据库详细步骤解析

    java连接mysql数据库详细步骤解析      第一步:下载一个JDBC驱动包,例如我用的是:mysql-connector-java-5.1.17-bin.jar      第二步:导入下载的J ...

  4. Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException:

    ### Error updating database. Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You h ...

  5. 一次org.springframework.jdbc.BadSqlGrammarException ### Error querying database Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException问题排查过程

    先说结论: 因为在表设计中有一个商品描述字段被设置为desc,但desc是mysql中的关键字,如select id,name,desc,price from product;这条sql语句在查询时的 ...

  6. Cannot load JDBC driver class 'com.mysql.jdbc.Driver '

    最近在学JAVA, SSM, 照着网上的例子系统启动后总是报这个错(IDE :IEDA): HTTP Status 500 - Request processing failed; nested ex ...

  7. MySQL JDBC URL参数(转)

    MySQL的 JDBC URL格式: jdbc:mysql://[host][,failoverhost...][:port]/[database] » [?propertyName1][=prope ...

  8. Cannot find class: com.mysql.jdbc.Driver错误及解决办法。

    在刚刚开始搭建Mybatis源码解析,一步一步从浅入深 简单示例的时候,我使用的是mysql 5.1.12版本的驱动包.运行时出现如下错误: Cause: java.sql.SQLException: ...

  9. 一个MySQL JDBC驱动bug引起的血案

    1.1      问题背景 公司是做电商系统的,整个系统搭建在华为云上.系统设计的时候,考虑到后续的用户和订单数量比较大,需要使用一些大数据库的组件.关系型数据库这块,考虑到后续数据量的快速增长,不是 ...

随机推荐

  1. select a.no,a.name,b.subid,b.subname,c.score

    select a.no,a.name,b.subid,b.subname,c.score from a,b,c  where a.no = c.no and b.subid = c.subid ;

  2. hydra 中文文档

    hydra(九头蛇)是一款开源的协议爆破工具,功能十分强大!!! 具体使用如下: -R   继续从上一次进度接着破解 -I 忽略已破解的文件进行破解 -S 采用SSL链接 -s 端口 指定非默认服务端 ...

  3. python_112_断言

    #断言 如果满足断言的执行程序,如果不满足则抛错误 assert type(1) is int print('断言正确的话,就继续执行') # assert type('a') is int #Ass ...

  4. QT+动手设计一个登陆窗口+布局

    登陆窗口的样式如下: 这里面涉及着窗口的UI设计,重点是局部布局和整体布局, 首先在ui窗口上添加一个容器类(Widget),然后将需要添加的控件放置在容器中,进行局部布局(在进行局部布局的时候可以使 ...

  5. ios之UIActivityIndicatorView

    UIActivityIndicatorView和UIProgressView都继承自UIView,所以他们可以附属在其他视图上.UIActivityIndicatorView是一个进度提示器,显示一个 ...

  6. java面试宝典第四弹

    动态代理 1. 什么是代理 我们大家都知道微商代理,简单地说就是代替厂家卖商品,厂家“委托”代理为其销售商品.关于微商代理,首先我们从他们那里买东西时通常不知道背后的厂家究竟是谁,也就是说,“委托者” ...

  7. DOM事件总结

    1.DOM事件: DOM0: element.onclick=function(){} DOM2: element.addEventListener(‘click’,function(){}) add ...

  8. sscanf的使用

    sscanf的使用 语法 int ssanf(const char *buffer, const char *format,[argument]...); 参数 buffer 存储的数据 format ...

  9. Django项目部署:使用uwsgi和nginx的方式

    一.背景 前两天制作的个人博客网站基本完工,大致功能具备.但是在部署环节却也处处碰壁,这里也来总结以下,以备将来不时查看以及完善. 二.前提 2.1 需要的知识 django Django是一个基于p ...

  10. Linux基础学习-MariaDB数据库管理系统

    数据库管理系统 数据库是指按照某些特定结构来存储数据资料的数据仓库,数据库管理系统是一种能够对数据库中存放的数据进行建立.修改.删除.查找.维护等操作的软件程序. 初始化MariaDB服务 [root ...