最近项目中用Vert.X做了一层数据库代理,用来转发mysql的Query请求,并在代理中拦截sql,对sql进行改写,控制查询返回数据。
这里面就遇到了Vert.X的拆包问题,这个问题是Socket自带的问题,但并未找到Vert.X中对于拆包问题的较好解决方案。尝试过官方文档及各类博客中的方式,并未有效解决。
问题情景:
查询SQL太长,超过了Vert.X的缓存空间,导致一个SQL通过2+个包扔过来了。
解决方式:
1.增大Vert.X的缓存空间 【但其实不太理想,假如SQL无限大,你品品】
NetServerOptions serverOptions = new NetServerOptions()
.setRegisterWriteHandler(true)
.setLogActivity(true)
.setSendBufferSize(1024 * 30)
.setReceiveBufferSize(1024 * 30);
NetClientOptions clientOptions = new NetClientOptions()
.setRegisterWriteHandler(true)
.setLogActivity(true)
.setSendBufferSize(1024 * 30)
.setReceiveBufferSize(1024 * 30);
2.所以最好是不管缓存多大,解决拆包后的合并最好,代码如下。
// 定义个缓存空间,用来存放拆包后的多个包
final ByteArrayOutputStream cacheByteArray = new ByteArrayOutputStream();
clientSocket.handler(buffer -> {
try {
log.info("接收的信息:\n" + buffer.toString(Charset.forName("UTF-8")));
log.info("原始数据包长度:" + buffer.length());
byte[] bufferBytes = buffer.getBytes();
// 只判断接收到的是不是mysql查询sql
if (isMysqlProtocolQuery(bufferBytes) || cacheByteArray.size() > 0) {
log.info("是mysql Query查询");
// 处理拆包 begin
// 判断缓存空间中是否已经有部分包存在
if (cacheByteArray.size() > 0) {
byte[] cacheBufferBytes = cacheByteArray.toByteArray();
// 得到Mysql SQL的完整长度(这个其实要解析mysql的协议)
int inSqlBytesLength = NativeUtils.decodeMysqlThreeByteInteger(bufferBytes);
// SQL完整长度减去已经缓存好的上一个包的长度得到剩余长度
int lastSqlLength = inSqlBytesLength - cacheBufferBytes.length + 4;
// 如果剩余长度大于0,就接着往cacheByteArray缓存里面扔
if (lastSqlLength > 0) {
cacheByteArray.write(bufferBytes);
buffer = Buffer.buffer(cacheByteArray.toByteArray());
}
} else {
if (isUnpacking(bufferBytes)) { // 如果是拆包
log.info("是拆包");
cacheByteArray.write(bufferBytes);
// 如果是拆包,且不是最后一个包,直接return,接续接下面的包
return;
}
}
// 处理拆包 end
if (proxyLog != null) proxyLog.setDatasource_query_time_consumimg(System.currentTimeMillis());
String noHeadBufferSql = handleDorisSqlPacketHeader(buffer);
String handleSql = handleSqlCondition(noHeadBufferSql);
byte[] handleSqlBytes = handleSqlPacket(handleSql);
buffer = Buffer.buffer(handleSqlBytes);
log.info("修改数据包长度:" + buffer.length());
log.info("更改的信息:\n" + buffer.toString(Charset.forName("UTF-8")));
serverSocket.write(buffer);
// 接完所有包之后,清除自定义的缓存空间
cacheByteArray.reset();
} else {
serverSocket.write(buffer);
}
} catch (Exception e) {
// 为了避免代理在解析sql时报错,造成的整个链路失常,出现报错时候,直接返回固定错误查询,也为了出现错误时好排查
log.error("write error: ", e);
if (proxyLog != null) proxyLog.setSql_execute_status("ERROR");
long errorTime = System.currentTimeMillis();
String msg = "proxy error:解析sql异常-"+ errorTime;
log.error("query error: ", msg);
String errorSql = "select '" + msg + "' as error_msg";
Buffer errorQueryBuffer = Buffer.buffer(errorSql);
serverSocket.write(errorQueryBuffer);
}
}
);
/**
* 判断是否是拆包
* 这是mysql协议解析是否拆包,需要根据自己的协议情况,判断是否是拆包状态
*/
private boolean isUnpacking(byte[] bufferBytes) {
int inSqlBytesLength = NativeUtils.decodeMysqlThreeByteInteger(bufferBytes);
int size = inSqlBytesLength + 4;
log.info("判断是否是拆包,接收buffer size:" + bufferBytes.length + ",完整sql size:" + size);
return bufferBytes.length < size;
}
Vert.X貌似不用处理粘包,所以把拆包处理好后,不管扔的数据多长,也不管Vert.X的缓存空间多大,也不管拆了多少包,都能在handler(Buffer)中接收到完整的,再往Mysql Server中扔。
上面的内容写的不是很细,如果有需要可以留言沟通。