MyCat源码分析系列之——结果合并
更多MyCat源码分析,请戳MyCat源码分析系列
结果合并
在SQL下发流程和前后端验证流程中介绍过,通过用户验证的后端连接绑定的NIOHandler是MySQLConnectionHandler实例,在MySQL服务端返回执行结果时会调用到MySQLConnecionHandler.handleData(),用于不同类型的处理派发:
protected void handleData(byte[] data) {
switch (resultStatus) {
case RESULT_STATUS_INIT:
switch (data[4]) {
case OkPacket.FIELD_COUNT:
handleOkPacket(data);
break;
case ErrorPacket.FIELD_COUNT:
handleErrorPacket(data);
break;
case RequestFilePacket.FIELD_COUNT:
handleRequestPacket(data);
break;
default:
resultStatus = RESULT_STATUS_HEADER;
header = data;
fields = new ArrayList<byte[]>((int) ByteUtil.readLength(data,
4));
}
break;
case RESULT_STATUS_HEADER:
switch (data[4]) {
case ErrorPacket.FIELD_COUNT:
resultStatus = RESULT_STATUS_INIT;
handleErrorPacket(data);
break;
case EOFPacket.FIELD_COUNT:
resultStatus = RESULT_STATUS_FIELD_EOF;
handleFieldEofPacket(data);
break;
default:
fields.add(data);
}
break;
case RESULT_STATUS_FIELD_EOF:
switch (data[4]) {
case ErrorPacket.FIELD_COUNT:
resultStatus = RESULT_STATUS_INIT;
handleErrorPacket(data);
break;
case EOFPacket.FIELD_COUNT:
resultStatus = RESULT_STATUS_INIT;
handleRowEofPacket(data);
break;
default:
handleRowPacket(data);
}
break;
default:
throw new RuntimeException("unknown status!");
}
}
上述代码片段中用红色标注的几个方法是最为核心的,其中handleOkPacket()主要用于insert/update/delete和其余返回OK包的语句返回的执行结果,而handleFieldEofPacket()、handleRowPacket()和handleRowEofPacket()用于select语句返回的执行结果。这几个方法的内部的流程其实就是分别调用了其上绑定的ResponseHandler(SingleNodeHandler或MultiNodeQueryHandler)实例对应的这几个方法。
1. 先来看单节点操作的情况,SingleNodeHandler包含的这几个方法实现如下:
public void okResponse(byte[] data, BackendConnection conn) {
boolean executeResponse = conn.syncAndExcute();
if (executeResponse) {
session.releaseConnectionIfSafe(conn, LOGGER.isDebugEnabled(),
false);
endRunning();
ServerConnection source = session.getSource();
OkPacket ok = new OkPacket();
ok.read(data);
if (rrs.isLoadData()) {
byte lastPackId = source.getLoadDataInfileHandler()
.getLastPackId();
ok.packetId = ++lastPackId;// OK_PACKET
source.getLoadDataInfileHandler().clear();
} else {
ok.packetId = ++packetId;// OK_PACKET
}
ok.serverStatus = source.isAutocommit() ? 2 : 1;
recycleResources();
source.setLastInsertId(ok.insertId);
ok.write(source);
//TODO: add by zhuam
//查询结果派发
QueryResult queryResult = new QueryResult(session.getSource().getUser(),
rrs.getSqlType(), rrs.getStatement(), startTime);
QueryResultDispatcher.dispatchQuery( queryResult );
}
}
public void fieldEofResponse(byte[] header, List<byte[]> fields,
byte[] eof, BackendConnection conn) {
//TODO: add by zhuam
//查询结果派发
QueryResult queryResult = new QueryResult(session.getSource().getUser(),
rrs.getSqlType(), rrs.getStatement(), startTime);
QueryResultDispatcher.dispatchQuery( queryResult );
header[3] = ++packetId;
ServerConnection source = session.getSource();
buffer = source.writeToBuffer(header, allocBuffer());
for (int i = 0, len = fields.size(); i < len; ++i)
{
byte[] field = fields.get(i);
field[3] = ++packetId;
buffer = source.writeToBuffer(field, buffer);
}
eof[3] = ++packetId;
buffer = source.writeToBuffer(eof, buffer);
if (isDefaultNodeShowTable) {
for (String name : shardingTablesSet) {
RowDataPacket row = new RowDataPacket(1);
row.add(StringUtil.encode(name.toLowerCase(), source.getCharset()));
row.packetId = ++packetId;
buffer = row.write(buffer, source, true);
}
}
}
public void rowResponse(byte[] row, BackendConnection conn) {
if(isDefaultNodeShowTable)
{
RowDataPacket rowDataPacket =new RowDataPacket(1);
rowDataPacket.read(row);
String table= StringUtil.decode(rowDataPacket.fieldValues.get(0),conn.getCharset());
if(shardingTablesSet.contains(table.toUpperCase())) return;
}
row[3] = ++packetId;
buffer = session.getSource().writeToBuffer(row, allocBuffer());
}
public void rowEofResponse(byte[] eof, BackendConnection conn) {
ServerConnection source = session.getSource();
conn.recordSql(source.getHost(), source.getSchema(),
node.getStatement());
// 判断是调用存储过程的话不能在这里释放链接
if (!rrs.isCallStatement()) {
session.releaseConnectionIfSafe(conn, LOGGER.isDebugEnabled(),
false);
endRunning();
}
eof[3] = ++packetId;
buffer = source.writeToBuffer(eof, allocBuffer());
source.write(buffer);
}
在okResponse()方法中,首先调用了conn.syncAndExcute(),这个过程就解释了之前在SQL下发过程中提及的当某个连接现有的设置需要修改时并未等待这些修改成功返回,这儿才对此做出了判断:
public boolean syncAndExcute() {
StatusSync sync = this.statusSync;
if (sync == null) {
return true;
} else {
boolean executed = sync.synAndExecuted(this);
if (executed) {
statusSync = null;
}
return executed;
}
}
这里面又进一步依次调用了StatusSync.synAndExecuted()和updateConnectionInfo()方法:
public boolean synAndExecuted(MySQLConnection conn) {
int remains = synCmdCount.decrementAndGet();
if (remains == 0) {// syn command finished
this.updateConnectionInfo(conn);
conn.metaDataSyned = true;
return false;
} else if (remains < 0) {
return true;
}
return false;
}
private void updateConnectionInfo(MySQLConnection conn)
{
conn.xaStatus = (xaStarted == true) ? 1 : 0;
if (schema != null) {
conn.schema = schema;
conn.oldSchema = conn.schema;
}
if (charsetIndex != null) {
conn.setCharset(CharsetUtil.getCharset(charsetIndex));
}
if (txtIsolation != null) {
conn.txIsolation = txtIsolation;
}
if (autocommit != null) {
conn.autocommit = autocommit;
}
}
假如当前的连接与所需连接在数据库名和字符集上存在不同,那需同步的数量为2,如果这两个修改都成功,那应该分别返回2个OK包(即触发两次SingleNodeHandler.okResponse()),在synAndExecuted()中通过对于收到的OK包数量synCmdCount进行判断,若全部收到则调用updateConnectionInfo(),将该连接的这些设置都设置为新值。该同步过程完成之后,才会真正进入到SQL语句执行返回的结果处理阶段(select/insert/update/delete)。
1.1 insert/update/delete
- okResponse():读取data字节数组,组成一个OKPacket,并调用ok.write(source)将结果写入前端连接FrontendConnection的写缓冲队列writeQueue中,真正发送给应用是由对应的NIOSocketWR从写队列中读取ByteBuffer并返回的。
1.2 select
- fieldEofResponse():元数据返回时触发,将header和元数据内容依次写入缓冲区中;
- rowResponse():行数据返回时触发,将行数据写入缓冲区中;
- rowEofResponse():行结束标志返回时触发,将EOF标志写入缓冲区,最后调用source.write(buffer)将缓冲区放入前端连接的写缓冲队列中,等待NIOSocketWR将其发送给应用。
2. 再来看多节点操作的结果合并和返回过程,MultiNodeQueryHandler负责这一过程的执行。
多节点操作和单节点操作的不同之处就在于:
1)接收来自多个MySQL节点各自发送的结果数据,可能需要对所有得到的结果进行简单的合并(顺序是不确定的,满足FIFO);
2)如果本次操作涉及聚合函数、group by、order by和limit,还需要对所有结果进行一系列归并
针对第一种情况,insert/update/delete和select的区别如下:
- insert/update/delete:这三类语句都会返回一个OK包,里面包含了最为核心的affectedRows,因此每得到一个MySQL节点发送回的affectedRows,就将其累加,当收到最后一个节点的数据后(通过decrementOkCountBy()方法判断),将结果返回给前端;
- select:每一个MySQL节点都会依次返回元数据、行数据1、行数据2...、行数据n、行结束标志位(EOF),其中元数据和行结束标志位每个节点返回上来都是一样的,但MultiNodeQueryHandler负责合并所有数据,因此实际只需要一份元数据、所有节点的行数据以及一份行结束标志位,所以在fieldEofResponse()方法中通过boolean类型的fieldsReturned判断获取第一份收到的元数据包,后续的统统丢弃,并在rowEofResponse()方法中通过decrementCountBy()方法判断是否收到了所有节点的EOF包,将最后一份的EOF写入缓冲区返回前端。
针对第二种情况,insert/update/delete与第一种情况完全一致,但select的处理由MultiNodeQueryHandler上包含的DataMergeService实例负责归并结果集(limit其实是在MultiNodeQueryHandler中实现的),我们先来看DataMergeService中包含的核心变量:
private int fieldCount; // 字段数
private RouteResultset rrs; // 路由信息
private RowDataSorter sorter; // 排序器
private RowDataPacketGrouper grouper; // 分组器
private volatile boolean hasOrderBy = false;
private MultiNodeQueryHandler multiQueryHandler;
public PackWraper END_FLAG_PACK = new PackWraper(); // 结束包
private AtomicInteger areadyAdd = new AtomicInteger();
private List<RowDataPacket> result = new Vector<RowDataPacket>(); // 归并结果队列
private static Logger LOGGER = Logger.getLogger(DataMergeService.class);
private BlockingQueue<PackWraper> packs = new LinkedBlockingQueue<PackWraper>(); // 原始数据封装队列
private ConcurrentHashMap<String, Boolean> canDiscard = new ConcurrentHashMap<String, Boolean>(); // 是否可丢弃标志位map
其中,最为重要的就是用于排序的sorter和用于分组的grouper,以及最后存放归并结果的行数据包队列result,DataMergeService中核心的方法实现如下:
public void onRowMetaData(Map<String, ColMeta> columToIndx, int fieldCount) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("field metadata inf:" + columToIndx.entrySet());
}
int[] groupColumnIndexs = null;
this.fieldCount = fieldCount;
if (rrs.getGroupByCols() != null) {
groupColumnIndexs = toColumnIndex(rrs.getGroupByCols(), columToIndx);
}
if (rrs.getHavingCols() != null) {
ColMeta colMeta = columToIndx.get(rrs.getHavingCols().getLeft()
.toUpperCase());
if (colMeta != null) {
rrs.getHavingCols().setColMeta(colMeta);
}
}
if (rrs.isHasAggrColumn()) {
List<MergeCol> mergCols = new LinkedList<MergeCol>();
Map<String, Integer> mergeColsMap = rrs.getMergeCols();
if (mergeColsMap != null) {
for (Map.Entry<String, Integer> mergEntry : mergeColsMap
.entrySet()) {
String colName = mergEntry.getKey().toUpperCase();
int type = mergEntry.getValue();
if (MergeCol.MERGE_AVG == type) {
ColMeta sumColMeta = columToIndx.get(colName + "SUM");
ColMeta countColMeta = columToIndx.get(colName
+ "COUNT");
if (sumColMeta != null && countColMeta != null) {
ColMeta colMeta = new ColMeta(sumColMeta.colIndex,
countColMeta.colIndex,
sumColMeta.getColType());
mergCols.add(new MergeCol(colMeta, mergEntry
.getValue()));
}
} else {
ColMeta colMeta = columToIndx.get(colName);
mergCols.add(new MergeCol(colMeta, mergEntry.getValue()));
}
}
}
// add no alias merg column
for (Map.Entry<String, ColMeta> fieldEntry : columToIndx.entrySet()) {
String colName = fieldEntry.getKey();
int result = MergeCol.tryParseAggCol(colName);
if (result != MergeCol.MERGE_UNSUPPORT
&& result != MergeCol.MERGE_NOMERGE) {
mergCols.add(new MergeCol(fieldEntry.getValue(), result));
}
}
grouper = new RowDataPacketGrouper(groupColumnIndexs,
mergCols.toArray(new MergeCol[mergCols.size()]),
rrs.getHavingCols());
}
if (rrs.getOrderByCols() != null) {
LinkedHashMap<String, Integer> orders = rrs.getOrderByCols();
OrderCol[] orderCols = new OrderCol[orders.size()];
int i = 0;
for (Map.Entry<String, Integer> entry : orders.entrySet()) {
String key = StringUtil.removeBackquote(entry.getKey()
.toUpperCase());
ColMeta colMeta = columToIndx.get(key);
if (colMeta == null) {
throw new java.lang.IllegalArgumentException(
"all columns in order by clause should be in the selected column list!"
+ entry.getKey());
}
orderCols[i++] = new OrderCol(colMeta, entry.getValue());
}
// sorter = new RowDataPacketSorter(orderCols);
RowDataSorter tmp = new RowDataSorter(orderCols);
tmp.setLimit(rrs.getLimitStart(), rrs.getLimitSize());
hasOrderBy = true;
sorter = tmp;
} else {
hasOrderBy = false;
}
MycatServer.getInstance().getBusinessExecutor().execute(this);
}
public boolean onNewRecord(String dataNode, byte[] rowData) {
// 对于需要排序的数据,由于mysql传递过来的数据是有序的,
// 如果某个节点的当前数据已经不会进入,后续的数据也不会入堆
if (canDiscard.size() == rrs.getNodes().length) {
LOGGER.error("now we output to client");
packs.add(END_FLAG_PACK);
return true;
}
if (canDiscard.get(dataNode) != null) {
return true;
}
PackWraper data = new PackWraper();
data.node = dataNode;
data.data = rowData;
packs.add(data);
areadyAdd.getAndIncrement();
return false;
}
public void run() {
int warningCount = 0;
EOFPacket eofp = new EOFPacket();
ByteBuffer eof = ByteBuffer.allocate(9);
BufferUtil.writeUB3(eof, eofp.calcPacketSize());
eof.put(eofp.packetId);
eof.put(eofp.fieldCount);
BufferUtil.writeUB2(eof, warningCount);
BufferUtil.writeUB2(eof, eofp.status);
ServerConnection source = multiQueryHandler.getSession().getSource();
while (!Thread.interrupted()) {
try {
PackWraper pack = packs.take();
if (pack == END_FLAG_PACK) {
break;
}
RowDataPacket row = new RowDataPacket(fieldCount);
row.read(pack.data);
if (grouper != null) {
grouper.addRow(row);
} else if (sorter != null) {
if (!sorter.addRow(row)) {
canDiscard.put(pack.node, true);
}
} else {
result.add(row);
}
} catch (Exception e) {
LOGGER.error("Merge multi data error", e);
}
}
byte[] array = eof.array();
multiQueryHandler.outputMergeResult(source, array, getResults(array));
}
private List<RowDataPacket> getResults(byte[] eof) {
List<RowDataPacket> tmpResult = result;
if (this.grouper != null) {
tmpResult = grouper.getResult();
grouper = null;
}
if (sorter != null) {
// 处理grouper处理后的数据
if (tmpResult != null) {
Iterator<RowDataPacket> itor = tmpResult.iterator();
while (itor.hasNext()) {
sorter.addRow(itor.next());
itor.remove();
}
}
tmpResult = sorter.getSortedResult();
sorter = null;
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("prepare mpp merge result for " + rrs.getStatement());
}
return tmpResult;
}
接下来对这几个方法逐一解释:
- onRowMetaData():在MultiNodeQueryHandler.fieldEofResponse()中被调用,初始化grouper和sorter,随后利用线程池调用run()方法;
- onNewRecord():在MultiNodeQueryHandler.rowResponse()中被调用,首先判断canDiscard的长度是否等于下发的节点个数,如果是说明后续所有节点的数据都会被丢弃,往packs中放入END_FLAG_PACK终止run()中的循环(另外一处更为常规的终止循环是由MultiNodeQueryHandler.rowEofResponse()中收到全部节点的EOF包后触发的),如果当前节点在canDiscard队列中也同样忽略该节点的后续数据,随后将节点名和行数据封装成一个PackWraper实例,放入packs中;
- run():核心是一个循环,每次阻塞式地从packs中读取一个PackWraper实例并生成RowDataPacket实例,如果发现是END_FLAG_PACK就退出,接下来进行if-else判断:
1)如果需要分组则调用grouper.addRow()添加该行,由于分组是优先于排序的,因此一旦有分组需求,那排序就必须等到所有分组行为完成后才能开始(getResults()中);
2)反之,如果需要排序则调用sorter.addRow()尝试添加该行,若加入不成功说明该节点后续的数据都不可能加入成功(因为这里的排序是通过构建最大堆MaxHeap实现的,堆一旦满了就会执行元素淘汰,并且每个节点返回的数据又满足内部有序),将该节点放入canDiscard中忽略后续数据;
3)反之,直接将该行加入result中。
当循环退出时,调用MultiNodeQueryHandler.outputMergeResult(),其中会先调用getResults()获取分组数据/分组排序数据/排序数据/普通数据,MultiNodeQueryHandler.outputMergeResult()就是为了执行limit,随后将处理好的结果集依次写入缓冲区,最后返回前端,具体实现如下:
public void outputMergeResult(final ServerConnection source,
final byte[] eof, List<RowDataPacket> results) {
try {
lock.lock();
ByteBuffer buffer = session.getSource().allocate();
final RouteResultset rrs = this.dataMergeSvr.getRrs(); // 处理limit语句
int start = rrs.getLimitStart();
int end = start + rrs.getLimitSize(); if (start < 0)
start = 0; if (rrs.getLimitSize() < 0)
end = results.size(); if (end > results.size())
end = results.size(); for (int i = start; i < end; i++) {
RowDataPacket row = results.get(i);
row.packetId = ++packetId;
buffer = row.write(buffer, source, true);
} eof[3] = ++packetId;
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("last packet id:" + packetId);
}
source.write(source.writeToBuffer(eof, buffer)); } catch (Exception e) {
handleDataProcessException(e);
} finally {
lock.unlock();
dataMergeSvr.clear();
}
}
为尊重原创成果,如需转载烦请注明本文出处:
http://www.cnblogs.com/fernandolee24/p/5243258.html,特此感谢
MyCat源码分析系列之——结果合并的更多相关文章
- 开源分布式数据库中间件MyCat源码分析系列
MyCat是当下很火的开源分布式数据库中间件,特意花费了一些精力研究其实现方式与内部机制,在此针对某些较为重要的源码进行粗浅的分析,希望与感兴趣的朋友交流探讨. 本源码分析系列主要针对代码实现,配置. ...
- MyCat源码分析系列之——SQL下发
更多MyCat源码分析,请戳MyCat源码分析系列 SQL下发 SQL下发指的是MyCat将解析并改造完成的SQL语句依次发送至相应的MySQL节点(datanode)的过程,该执行过程由NonBlo ...
- MyCat源码分析系列之——BufferPool与缓存机制
更多MyCat源码分析,请戳MyCat源码分析系列 BufferPool MyCat的缓冲区采用的是java.nio.ByteBuffer,由BufferPool类统一管理,相关的设置在SystemC ...
- MyCat源码分析系列之——前后端验证
更多MyCat源码分析,请戳MyCat源码分析系列 MyCat前端验证 MyCat的前端验证指的是应用连接MyCat时进行的用户验证过程,如使用MySQL客户端时,$ mysql -uroot -pr ...
- MyCat源码分析系列之——配置信息和启动流程
更多MyCat源码分析,请戳MyCat源码分析系列 MyCat配置信息 除了一些默认的配置参数,大多数的MyCat配置信息是通过读取若干.xml/.properties文件获取的,主要包括: 1)se ...
- [转]数据库中间件 MyCAT源码分析——跨库两表Join
1. 概述 2. 主流程 3. ShareJoin 3.1 JoinParser 3.2 ShareJoin.processSQL(...) 3.3 BatchSQLJob 3.4 ShareDBJo ...
- spring源码分析系列 (5) spring BeanFactoryPostProcessor拓展类PropertyPlaceholderConfigurer、PropertySourcesPlaceholderConfigurer解析
更多文章点击--spring源码分析系列 主要分析内容: 1.拓展类简述: 拓展类使用demo和自定义替换符号 2.继承图UML解析和源码分析 (源码基于spring 5.1.3.RELEASE分析) ...
- Spring Ioc源码分析系列--Ioc的基础知识准备
Spring Ioc源码分析系列--Ioc的基础知识准备 本系列文章代码基于Spring Framework 5.2.x Ioc的概念 在Spring里,Ioc的定义为The IoC Containe ...
- Spring Ioc源码分析系列--Bean实例化过程(一)
Spring Ioc源码分析系列--Bean实例化过程(一) 前言 上一篇文章Spring Ioc源码分析系列--Ioc容器注册BeanPostProcessor后置处理器以及事件消息处理已经完成了对 ...
随机推荐
- Jquery 搭配 css 使用,简单有效
前几篇博客中讲了Jquery的基础和点击实际,下面来说一下和css搭配着来怎么做 还是和往常一样,举个例子 好几个方块,然后设置颜色 <!DOCTYPE html PUBLIC "-/ ...
- CSS浮动、定位
这几天有空,整理了关于CSS浮动和定位的一些知识点,有什么欠缺的地方,欢迎大家批评指正. 一.文档流的概念指什么?有哪种方式可以让元素脱离文档流? 文档流,指的是元素排版布局过程中,元素会自动从左往右 ...
- 简记某WebGIS项目的优化之路
文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/ 1. 背景 该项目为研究生时的老师牵头,个人已毕业数年,应老师要求协助其 ...
- golang struct扩展函数参数命名警告
今天在使用VSCode编写golang代码时,定义一个struct,扩展几个方法,如下: package storage import ( "fmt" "github.c ...
- 利用注册表在右键添加VS15的快捷方式打开文件夹
1.简介 最近安装VS15 Preview 5,本版本可以打开"文件夹" 是否可以向Visual Studio Code一样在文件夹或文件右键菜单添加"Open with ...
- Docker与CI持续集成/CD
背景 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化.容器是完全使用沙箱机制 ...
- 【HTML】Html页面跳转的5种方式
目录结构: // contents structure [-] html实现 javascript方式实现 结合了倒数的javascript实现(IE) 解决Firefox不支持innerText的问 ...
- Android—万能ListView适配器
ListView是开发中最常用的控件了,但是总是会写重复的代码,浪费时间又没有意义. 最近参考一些资料,发现一个万能ListView适配器,代码量少,节省时间,总结一下分享给大家. 首先有一个自定义的 ...
- android SystemServer.java启动的服务。
EntropyService:熵(shang)服务,用于产生随机数PowerManagerService:电源管理服务ActivityManagerService:最核心服务之一,Activity管理 ...
- (转)从0开始搭建SQL Server AlwaysOn 第一篇(配置域控+域用户DCADMIN)
原文地址: http://www.cnblogs.com/lyhabc/p/4678330.html 实验环境: 准备工作 软件准备 (1) SQL Server 2012 (2) Windows S ...