数据库路由中间件MyCat - 源代码篇(9)
此文已由作者张镐薪授权网易云社区发布。
欢迎访问网易云社区,了解更多网易技术产品运营经验。
3. 连接模块
3.5 后端连接
3.5.1 后端连接获取与负载均衡
上一节我们讲了后端连接的基本建立和响应处理,那么这些后端连接是什么时候建立的呢? 首先,MyCat配置文件中,DataHost标签中有minIdle这个属性。代表在MyCat初始化时,会在这个DataHost上初始化维护多少个连接(这些连接可以理解为连接池)。每个前端Client连接会创建Session,而Session会根据命令的不同而创建不同的Handler。每个Handler会从连接池中拿出所需要的连接并使用。在连接池大小不够时,RW线程会异步驱使新建所需的连接补充连接池,但是连接数最大不能超过配置的maxCon。同时,如之前所述,有定时线程检查并回收空闲后端连接。但池中最小不会小于minCon。 我们可以通过后端连接的工厂方法的调用链来理解: 看这个调用链,我们简述下大概的流程。
st=>start: MyCat接受客户端连接并为之建立唯一绑定的Session
e=>end: 将请求发送给对应连接,处理完之后归还连接
op1=>operation: MyCat接受客户端的请求,计算路由
op2=>operation: 根据请求和路由创建合适的handler,这里为SingleNodeHandler
op3=>operation: 从PhysicalDBNode中获取后端连接
cond=>condition: 尝试获取连接,连接够用?
op4=>operation: 尝试异步创建新的连接
op5=>operation: 通过DelegateResponseHandler将连接与之前的Handler,这里是SingleNodeHandler绑定
st->op1->op2->op3->condcond(yes)->econd(no)->op4->op5->e
我们先从Session看起,在MyCat中实现类为NonBlockingSession。在前端连接建立时,会创建绑定唯一的Session: ServerConnectionFactory.java:
protected FrontendConnection getConnection(NetworkChannel channel) throws IOException {
SystemConfig sys = MycatServer.getInstance().getConfig().getSystem();
ServerConnection c = new ServerConnection(channel);
MycatServer.getInstance().getConfig().setSocketParams(c, true);
c.setPrivileges(MycatPrivileges.instance());
c.setQueryHandler(new ServerQueryHandler(c));
c.setLoadDataInfileHandler(new ServerLoadDataInfileHandler(c)); // c.setPrepareHandler(new ServerPrepareHandler(c));
c.setTxIsolation(sys.getTxIsolation()); //创建绑定唯一Session
c.setSession2(new NonBlockingSession(c)); return c;
}
Session主要处理事务,多节点转发协调等,由不同的ResponseHandler实现; 这些ResponseHandler我们之后会在对应的模块去细细分析。这里先跳过。 查看SingleNodeHanlder的处理方法 SingleNodeHanlder.java:
public void execute() throws Exception { //从这里开始计算处理时间
startTime=System.currentTimeMillis();
ServerConnection sc = session.getSource(); this.isRunning = true; this.packetId = 0; final BackendConnection conn = session.getTarget(node); //之前是否获取过Connection并且Connection有效
if (session.tryExistsCon(conn, node)) {
_execute(conn);
} else { // create new connection
MycatConfig conf = MycatServer.getInstance().getConfig(); //从config中获取DataNode
PhysicalDBNode dn = conf.getDataNodes().get(node.getName()); //获取对应的数据库连接
dn.getConnection(dn.getDatabase(), sc.isAutocommit(), node, this,
node);
}
}
从PhysicalDBNode中获取合适的连接:
public void getConnection(String schema,boolean autoCommit, RouteResultsetNode rrs,
ResponseHandler handler, Object attachment) throws Exception {
checkRequest(schema); //检查数据库连接池是否初始化成功,因为有reload命令
if (dbPool.isInitSuccess()) { //根据是否能在读节点上运行获取连接,一般是判断是否为读请求,并且读请求不在事务中
if (rrs.canRunnINReadDB(autoCommit)) { //根据负载均衡策略选择合适的后端连接
dbPool.getRWBanlanceCon(schema,autoCommit, handler, attachment, this.database);
} else { //直接选择当前连接池中的的后端连接
dbPool.getSource().getConnection(schema,autoCommit, handler, attachment);
} } else { throw new IllegalArgumentException("Invalid DataSource:"
+ dbPool.getActivedIndex());
}
}
PhysicalDBPool类中有负载均衡,切换writeHost,控制write方式等(分别对应balance,writeType等标签)的实现。首先我们看如果有负载均衡策略(配置了balance)的获取连接的方式:
public void getRWBanlanceCon(String schema, boolean autocommit,
ResponseHandler handler, Object attachment, String database) throws Exception { PhysicalDatasource theNode = null;
ArrayList<PhysicalDatasource> okSources = null; switch (banlance) { //所有读写节点参与read请求的负载均衡,除了当前活跃的写节点,balance=1
case BALANCE_ALL_BACK: { //返回所有写节点和符合条件的读节点,不包括当前的写节点
okSources = getAllActiveRWSources(true, false, checkSlaveSynStatus()); if (okSources.isEmpty()) { //如果结果即为空,返回当前写节点
theNode = this.getSource();
} else { //不为空,随机选一个
theNode = randomSelect(okSources);
} break;
} //所有读写节点参与read请求的负载均衡,balance=2
case BALANCE_ALL: { //返回所有写节点和符合条件的读节点
okSources = getAllActiveRWSources(true, true, checkSlaveSynStatus()); //随机选一个
theNode = randomSelect(okSources); break;
} case BALANCE_ALL_READ: { //返回所有符合条件的读节点
okSources = getAllActiveRWSources(false, false, checkSlaveSynStatus()); //随机取一个
theNode = randomSelect(okSources); break;
} //不做负载均衡,balance=0或其他不为以上的值
case BALANCE_NONE: default:
// return default write data source
theNode = this.getSource();
} if (LOGGER.isDebugEnabled()) {
LOGGER.debug("select read source " + theNode.getName() + " for dataHost:" + this.getHostName());
}
theNode.getConnection(schema, autocommit, handler, attachment);
}
其中涉及到的方法:
返回符合条件节点集:
private ArrayList<PhysicalDatasource> getAllActiveRWSources( boolean includeWriteNode, boolean includeCurWriteNode, boolean filterWithSlaveThreshold) { int curActive = activedIndex;
ArrayList<PhysicalDatasource> okSources = new ArrayList<PhysicalDatasource>(this.allDs.size()); //判断写节点
for (int i = 0; i < this.writeSources.length; i++) {
PhysicalDatasource theSource = writeSources[i]; //判断写节点是否是active,可能reload会置为inactive,可能多个写节点但是只有一个是活跃在用的(writeType=0)
if (isAlive(theSource)) { //负载均衡策略是否包含写节点
if (includeWriteNode) { //判断是否包含当前活跃的写入节点
if (i == curActive && includeCurWriteNode == false) { // not include cur active source
} else if (filterWithSlaveThreshold) { //如果包含从节点同步延迟限制,检查同步状态
if (canSelectAsReadNode(theSource)) {
okSources.add(theSource);
} else { //如果同步状态不对,则不添加这个写节点
continue;
}
} else {
okSources.add(theSource);
}
} //检查theSource对应的读节点
if (!readSources.isEmpty()) { // 检查theSource对应的读节点(从节点)
PhysicalDatasource[] allSlaves = this.readSources.get(i); if (allSlaves != null) { for (PhysicalDatasource slave : allSlaves) { if (isAlive(slave)) { //如果包含从节点同步延迟限制,检查同步状态
if (filterWithSlaveThreshold) { if (canSelectAsReadNode(slave)) { //如果同步状态正确,则把读节点加入
okSources.add(slave);
} else { continue;
}
} else {
okSources.add(slave);
}
}
}
}
}
} else { // TODO : add by zhuam
// 如果写节点不OK, 也要保证临时的读服务正常
if (this.dataHostConfig.isTempReadHostAvailable()) { if (!readSources.isEmpty()) { // check all slave nodes
PhysicalDatasource[] allSlaves = this.readSources.get(i); if (allSlaves != null) { for (PhysicalDatasource slave : allSlaves) { if (isAlive(slave)) { if (filterWithSlaveThreshold) { if (canSelectAsReadNode(slave)) {
okSources.add(slave);
} else { continue;
}
} else {
okSources.add(slave);
}
}
}
}
}
}
}
} return okSources;
}
检查是否判断主从延迟:
private boolean checkSlaveSynStatus() { return (dataHostConfig.getSlaveThreshold() != -1)
&& (dataHostConfig.getSwitchType() == DataHostConfig.SYN_STATUS_SWITCH_DS);
}
随机选择节点:
/**
* TODO: modify by zhuam
* <p/>
* 随机选择,按权重设置随机概率。
* 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
*
* @param okSources
* @return
*/
public PhysicalDatasource randomSelect(ArrayList<PhysicalDatasource> okSources) { if (okSources.isEmpty()) { return this.getSource(); } else { int length = okSources.size(); // 总个数
int totalWeight = 0; // 总权重
boolean sameWeight = true; // 权重是否都一样
for (int i = 0; i < length; i++) { int weight = okSources.get(i).getConfig().getWeight();
totalWeight += weight; // 累计总权重
if (sameWeight && i > 0
&& weight != okSources.get(i - 1).getConfig().getWeight()) { // 计算所有权重是否一样
sameWeight = false;
}
} if (totalWeight > 0 && !sameWeight) { // 如果权重不相同且权重大于0则按总权重数随机
int offset = random.nextInt(totalWeight); // 并确定随机值落在哪个片断上
for (int i = 0; i < length; i++) {
offset -= okSources.get(i).getConfig().getWeight(); if (offset < 0) { return okSources.get(i);
}
}
} // 如果权重相同或权重为0则均等随机
return okSources.get(random.nextInt(length)); //int index = Math.abs(random.nextInt()) % okSources.size();
//return okSources.get(index);
}
}
根据writeType获取当前writeHost方法:
public PhysicalDatasource getSource() { switch (writeType) { //writeType=0,返回当前active的writeHost
case WRITE_ONLYONE_NODE: { return writeSources[activedIndex];
} //writeType=1,随机发到一个writeHost
case WRITE_RANDOM_NODE: { int index = Math.abs(wnrandom.nextInt()) % writeSources.length;
PhysicalDatasource result = writeSources[index]; if (!this.isAlive(result)) { // find all live nodes
ArrayList<Integer> alives = new ArrayList<Integer>(writeSources.length - 1); for (int i = 0; i < writeSources.length; i++) { if (i != index) { if (this.isAlive(writeSources[i])) {
alives.add(i);
}
}
} if (alives.isEmpty()) {
result = writeSources[0];
} else { // random select one
index = Math.abs(wnrandom.nextInt()) % alives.size();
result = writeSources[alives.get(index)];
}
} if (LOGGER.isDebugEnabled()) {
LOGGER.debug("select write source " + result.getName()
+ " for dataHost:" + this.getHostName());
} return result;
} //参数不正确
default: { throw new java.lang.IllegalArgumentException("writeType is "
+ writeType + " ,so can't return one write datasource ");
}
}
}
更多网易技术、产品、运营经验分享请点击。
相关文章:
【推荐】 InnoDB透明页压缩与稀疏文件
【推荐】 浅析电商防止恶意下单
数据库路由中间件MyCat - 源代码篇(9)的更多相关文章
- 数据库路由中间件MyCat - 源代码篇(1)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 进入了源代码篇,我们先从整体入手,之后拿一个简单流程前端连接建立与认证作为例子,理清代码思路和设计模式.然后 ...
- 数据库路由中间件MyCat - 源代码篇(13)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 4.配置模块 4.2 schema.xml 接上一篇,接下来载入每个schema的配置(也就是每个MyCat ...
- 数据库路由中间件MyCat - 源代码篇(7)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 3. 连接模块 3.4 FrontendConnection前端连接 构造方法: public Fronte ...
- 数据库路由中间件MyCat - 源代码篇(15)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. public static void handle(String stmt, ServerConnectio ...
- 数据库路由中间件MyCat - 源代码篇(17)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 调用processInsert(sc,schema,sqlType,origSQL,tableName,pr ...
- 数据库路由中间件MyCat - 源代码篇(14)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 对于表的dataNode对应关系,有个特殊配置即类似dataNode="distributed(d ...
- 数据库路由中间件MyCat - 源代码篇(4)
此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 2. 前端连接建立与认证 Title:MySql连接建立以及认证过程client->MySql:1.T ...
- 数据库路由中间件MyCat - 源代码篇(2)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 2. 前端连接建立与认证 Title:MySql连接建立以及认证过程client->MySql:1.T ...
- 数据库路由中间件MyCat - 源代码篇(16)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 5. 路由模块 真正取得RouteResultset的步骤:AbstractRouteStrategy的ro ...
- 数据库路由中间件MyCat - 源代码篇(10)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 3. 连接模块 3.5 后端连接 3.5.2 后端连接获取与维护管理 还是那之前的流程, st=>st ...
随机推荐
- 利用wxpython显示OpenCV图像
核心代码 import wx, cv2 import numpy as np # Start with a numpy array style image I'll call "source ...
- 剑指Offer:字符串排列【38】
剑指Offer:字符串排列[38] 题目描述 输入一个字符串,按字典序打印出该字符串中字符的所有排列.例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bc ...
- Android OpenGL 播放视频学习
1, 初步接触Open GL: http://www.cnblogs.com/TerryBlog/archive/2010/07/09/1774475.html 使用GLSurfaceView和Ren ...
- springcloud 研发规范
1) 程序结构规范 1: Facade-Stub:包含所有对外提供服务的借口定义,并对外提供 2: Façade:实现Façade-Stub里面定义的全部借口,可以调用Service模块和Commo ...
- Excel图表转成图片
关于excel 图表转成图片 知识点:excel 生成的图表不是图片 尝试. 通过Java调用POI接口挺难把excel生成的图表转成图片导出来 ps. 其它生成图表的工具,如jfre ...
- 1--单独使用jdbc开发问题总结
1.数据库连接,使用时就创建,不使用立即释放,对数据库进行频繁连接开启和关闭,造成数据库资源浪费,影响 数据库性能. 设想:使用数据库连接池管理数据库连接. 2.将sql语句硬编码到java代码中,如 ...
- RQNOJ 311 [NOIP2000]乘积最大:划分型dp
题目链接:https://www.rqnoj.cn/problem/311 题意: 给你一个长度为n的数字,用t个乘号分开,问你分开后乘积最大为多少.(6<=n<=40,1<=k&l ...
- frp支持httpIP地址加端口号访问
(一)安装就不再多说 传送门:https://blog.csdn.net/superljn/article/details/81289993 (二)vim frps.ini [common] bind ...
- 重新拾取:ASP.NET Core WebApi 使用Swagger支持授权认证
园子里已经有很多.NET Core 集成Swagger的文章,但对于使用授权的介绍蛮少的. public static class SwaggerServiceExtensions { public ...
- linux命令学习笔记-kill和killall命令详解
*杀死进程最安全的方法是单纯使用kill命令,不加修饰符,不带标志. 首先使用ps -ef命令确定要杀死进程的PID,然后输入以下命令: # kill -pid 注释:标准的kill命令通常都能达到目 ...