mycat 源码分析
参考URL: http://www.iocoder.cn/MyCAT
一、调试环境搭建
数据库中间件 MyCAT 源码分析 —— 调试环境搭建
参考URL: http://www.iocoder.cn/MyCAT/build-debugging-environment/?mp
可完全仿照上面参考连接,测试通过。
1. 基本流程跟踪
MyCAT 基本流程源码分析
MyCAT 源码分析 —— 【单库单表】插入
参考URL: http://www.iocoder.cn/MyCAT/single-db-single-table-insert/
MyCAT 源码分析 —— 【单库单表】查询
参考URL: http://www.iocoder.cn/MyCAT/single-db-single-table-select/?self
作者思路写比较清晰,完全可以参考,对整体基本流程有个认识了解。
整体思路总结学习:
- mycat接收请求,根据mysql二进制协议,把特定字节数组,解析 SQL
- 获得路由结果
// 路由到后端数据库,执行 SQL
routeEndExecuteSQL(sql, type, schema);
这里的type,是个int 类型,具体指不同的 sql 类型,这里重点关注ServerParse类,这个类定义了 sql类型,并且parse方法,传入sql字符串,返回这个sql的类型。
int rs = ServerParse.parse(sql);
int sqlType = rs & 0xff; //&0xff:取这个数低8位的值
这里的sql类型是指,是insert、delete、update、ddl等,具体可查看ServerParse成员变量。
public void routeEndExecuteSQL(String sql, final int type, final SchemaConfig schema) {
// 路由计算
RouteResultset rrs = null;
try {
rrs = MycatServer
.getInstance()
.getRouterservice()
.route(MycatServer.getInstance().getConfig().getSystem(),
schema, type, sql, this.charset, this);
} catch (Exception e) {
StringBuilder s = new StringBuilder();
LOGGER.warn(s.append(this).append(sql).toString() + " err:" + e.toString(),e);
String msg = e.getMessage();
writeErrMessage(ErrorCode.ER_PARSE_ERROR, msg == null ? e.getClass().getSimpleName() : msg);
return;
}
if (rrs != null) {
// session执行
session.execute(rrs, rrs.isSelectForUpdate()?ServerParse.UPDATE:type);
}
}
- 获得 MySQL 连接,执行 SQL
- 响应执行 SQL 结果
问题整理
- \target\test-classes\index_to_charset.properties (系统找不到指定的文件。)
原因分析,因为我们是在 test 目录下进行mycat测试,mycat用到这个文件,因此把项目resource目录下的index_to_charset.properties 拷贝一份到 test下的resource。
index_to_charset.properties 这个文件存储的是索引到字符编码对应关系的配置。
1=big5
2=latin2
3=dec8
4=cp850
5=latin1
6=hp8
...
一、mycat 使用Druid SQL 解析器的解析过程
Druid SQL 解析器的解析过程
参考URL: https://segmentfault.com/a/1190000008120254
[推荐]Druid SqlParser理解及使用入门
参考URL: https://www.cnblogs.com/lay2017/p/9840394.html
源码分析MyCat之SQL解析篇-----sql解析引擎druid之抽象语法树(mycat1.6)
参考URL: https://blog.csdn.net/prestigeding/article/details/72318482
mycat源码路径
\Mycat-Server-1.6\src\main\java\io\mycat\route\parser
- 测试demo
public class ParserMain {
public static void main(String[] args) {
String sql = "select * from user order by id";
// 新建 MySQL Parser
SQLStatementParser parser = new MySqlStatementParser(sql);
// 使用Parser解析生成AST,这里SQLStatement就是AST
SQLStatement statement = parser.parseStatement();
// 使用visitor来访问AST
MySqlSchemaStatVisitor visitor = new MySqlSchemaStatVisitor();
statement.accept(visitor);
System.out.println(visitor.getColumns());
System.out.println(visitor.getOrderByColumns());
}
}
- SQLStatement 抽象语法树
一开始,需要初始化一个 Parser,在这里 SQLStatementParser 是一个父类,真正解析 SQL 语句的 Parser 实现是 MySqlStatementParser。
Parser 的解析结果是一个 SQLStatement,这是一个内部维护了树状逻辑结构的类。这个树就是抽象语法树。
SQLStatement表示一条SQL语句,我们知道常见的SQL语句有CRUD四种操作,所以SQLStatement会有四种主要实现类,如:
class SQLSelectStatement implements SQLStatement {
SQLSelect select;
}
class SQLUpdateStatement implements SQLStatement {
SQLExprTableSource tableSource;
List<SQLUpdateSetItem> items;
SQLExpr where;
}
class SQLDeleteStatement implements SQLStatement {
SQLTableSource tableSource;
SQLExpr where;
}
class SQLInsertStatement implements SQLStatement {
SQLExprTableSource tableSource;
List<SQLExpr> columns;
SQLSelect query;
}
这里我们以SQLSelectStatement来说明,ast既然是SQL的语法结构表示,我们先看一下ast和SQL select语法的主要对应结构
SQLSelectStatement包含一个SQLSelect,SQLSelect包含一个SQLSelectQuery,都是组成的关系。SQLSelectQuery有主要的两个派生类,分别是SQLSelectQueryBlock和SQLUnionQuery。
以下是SQLSelectQueryBlock中包含
public class SQLSelectQueryBlock extends SQLObjectImpl implements SQLSelectQuery, SQLReplaceable {
private boolean bracket = false;
protected int distionOption;
protected final List<SQLSelectItem> selectList = new ArrayList();
protected SQLTableSource from;
protected SQLExprTableSource into;
protected SQLExpr where;
protected SQLExpr startWith;
protected SQLExpr connectBy;
protected boolean prior = false;
protected boolean noCycle = false;
protected SQLOrderBy orderBySiblings;
protected SQLSelectGroupByClause groupBy;
protected List<SQLWindow> windows;
protected SQLOrderBy orderBy;
protected boolean parenthesized = false;
protected boolean forUpdate = false;
protected boolean noWait = false;
protected SQLExpr waitTime;
protected SQLLimit limit;
protected List<SQLExpr> forUpdateOf;
protected List<SQLExpr> distributeBy;
protected List<SQLSelectOrderByItem> sortBy;
protected String cachedSelectList;
protected long cachedSelectListHash;
protected List<SQLCommentHint> hints;
protected String dbType;
SQLExpr出现的地方也比较多,比如where语句,join条件,SQLSelectItem中等。
// SQLName是一种的SQLExpr的Expr,包括SQLIdentifierExpr、SQLPropertyExpr等
public interface SQLName extends SQLExpr {}
// 例如 ID = 3 这里的ID是一个SQLIdentifierExpr
class SQLIdentifierExpr implements SQLExpr, SQLName {
String name;
}
// 例如 A.ID = 3 这里的A.ID是一个SQLPropertyExpr
class SQLPropertyExpr implements SQLExpr, SQLName {
SQLExpr owner;
String name;
}
// 例如 ID = 3 这是一个SQLBinaryOpExpr
// left是ID (SQLIdentifierExpr)
// right是3 (SQLIntegerExpr)
class SQLBinaryOpExpr implements SQLExpr {
SQLExpr left;
SQLExpr right;
SQLBinaryOperator operator;
}
// 例如 select * from where id = ?,这里的?是一个SQLVariantRefExpr,name是'?'
class SQLVariantRefExpr extends SQLExprImpl {
String name;
}
// 例如 ID = 3 这里的3是一个SQLIntegerExpr
public class SQLIntegerExpr extends SQLNumericLiteralExpr implements SQLValuableExpr {
Number number;
// 所有实现了SQLValuableExpr接口的SQLExpr都可以直接调用这个方法求值
@Override
public Object getValue() {
return this.number;
}
}
// 例如 NAME = 'jobs' 这里的'jobs'是一个SQLCharExpr
public class SQLCharExpr extends SQLTextLiteralExpr implements SQLValuableExpr{
String text;
}
完整测试demo如下,加强对SQLBinaryOpExpr的认识!
package druid;
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLLimit;
import com.alibaba.druid.sql.ast.SQLOrderBy;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator;
import com.alibaba.druid.sql.ast.expr.SQLInSubQueryExpr;
import com.alibaba.druid.sql.ast.statement.*;
import com.alibaba.druid.util.JdbcConstants;
import java.util.List;
public class ParserMain {
public static void enhanceSql(String sql) {
// 解析
List<SQLStatement> statements = SQLUtils.parseStatements(sql, JdbcConstants.MYSQL);
// 只考虑一条语句
SQLStatement statement = statements.get(0);
// 只考虑查询语句
SQLSelectStatement sqlSelectStatement = (SQLSelectStatement) statement;
SQLSelectQuery sqlSelectQuery = sqlSelectStatement.getSelect().getQuery();
// 非union的查询语句
if (sqlSelectQuery instanceof SQLSelectQueryBlock) {
SQLSelectQueryBlock sqlSelectQueryBlock = (SQLSelectQueryBlock) sqlSelectQuery;
// 获取字段列表
List<SQLSelectItem> selectItems = sqlSelectQueryBlock.getSelectList();
selectItems.forEach(x -> {
// 处理---------------------
});
// 获取表
SQLTableSource table = sqlSelectQueryBlock.getFrom();
// 普通单表
if (table instanceof SQLExprTableSource) {
// 处理---------------------
// join多表
} else if (table instanceof SQLJoinTableSource) {
// 处理---------------------
// 子查询作为表
} else if (table instanceof SQLSubqueryTableSource) {
// 处理---------------------
}
// 获取where条件
SQLExpr where = sqlSelectQueryBlock.getWhere();
// 如果是二元表达式
if (where instanceof SQLBinaryOpExpr) {
SQLBinaryOpExpr sqlBinaryOpExpr = (SQLBinaryOpExpr) where;
SQLExpr left = sqlBinaryOpExpr.getLeft();
SQLBinaryOperator operator = sqlBinaryOpExpr.getOperator();
SQLExpr right = sqlBinaryOpExpr.getRight();
// 处理---------------------
// 如果是子查询
} else if (where instanceof SQLInSubQueryExpr) {
SQLInSubQueryExpr sqlInSubQueryExpr = (SQLInSubQueryExpr) where;
// 处理---------------------
}
// 获取分组
SQLSelectGroupByClause groupBy = sqlSelectQueryBlock.getGroupBy();
// 处理---------------------
// 获取排序
SQLOrderBy orderBy = sqlSelectQueryBlock.getOrderBy();
// 处理---------------------
// 获取分页
SQLLimit limit = sqlSelectQueryBlock.getLimit();
// 处理---------------------
// union的查询语句
} else if (sqlSelectQuery instanceof SQLUnionQuery) {
// 处理---------------------
}
}
public static void main(String[] args) {
//String sql = "select * from user where id=1 order by id";
String sql = "select user from emp_table where id=3 and sex=1 and name='hello'";
enhanceSql(sql);
}
}
sql如下:
String sql = "select user from emp_table where id=3 and sex=1 and name='hello'";
如上例sqlBinaryOpExpr封装了 where语句
sqlBinaryOpExpr.getLeft(); 是最后一个and前面语句,如下图
sqlBinaryOpExpr.getLeft().getChildren()又可以把里面的两个SqlBinaryOpExpr实例。
总结: druid会把多个 and条件,存储在sqlBinaryOpExpr实例中,因为这个实例,有个left参数、有个right参数,它会把and左边的字符串放入left,右边的字符串放入right,把and放到operate中。如果有多个and,同样,它会把最后一个and前面的所有字符串放入left,left里面又会切分成不同的sqlBinaryOpExpr实例。sqlBinaryOpExpr以成员变量child的形式,可以切分所有的where条件。
- SQLBinaryOpExpr sql二元操作表达式 类
重点是理解类SQLBinaryOpExpr
public class SQLBinaryOpExpr extends SQLExprImpl implements SQLReplaceable, Serializable {
private static final long serialVersionUID = 1L;
protected SQLExpr left;
protected SQLExpr right;
protected SQLBinaryOperator operator;
protected String dbType;
private boolean bracket;
protected transient List<SQLObject> mergedList;`
所有sql的二元操作表达式,都被封装到类SQLBinaryOpExpr中,其中SQLBinaryOperator 是个枚举类,代表支持哪些二元操作符,left和right分别是二元操作符的左值和右值。
- SQLBinaryOperator 二分操作符枚举类
它把操作符如下,所示分成isRelational关系型操作符,和逻辑型操作符isLogical
public boolean isRelational() {
switch (this) {
case Equality:
case Like:
case NotEqual:
case GreaterThan:
case GreaterThanOrEqual:
case LessThan:
case LessThanOrEqual:
case LessThanOrGreater:
case NotLike:
case NotLessThan:
case NotGreaterThan:
case RLike:
case NotRLike:
case RegExp:
case NotRegExp:
case Is:
case IsNot:
return true;
default:
return false;
}
}
public boolean isLogical() {
return this == BooleanAnd || this == BooleanOr || this == BooleanXor;
}
- 语法分析和词法分析的类 SQLParser 和 Lexer
Druid 的代码里,代表语法分析和词法分析的类分别是 SQLParser 和 Lexer。并且, Parser 拥有一个 Lexer。Lexer子类 MySqlLexer等等。
而 MySqlLexer 类,除了沿用其父类的 Keywords 外,自己还有自己的 Keywords。可以理解为 Lexer 所维护的关键字集合,是通用的;而 MySqlLexer 除了有通用的关键字集合,也有属于 MySQL 数据库 SQL 方言的关键字集合。
- Visitor
从 demo 代码中可以看到,有了 AST 语法树后,则需要一个 visitor 来访问它。
statement 调用 accept 方法,以 visitor 作为参数,开始了访问之旅。在这里 statement 的实际类型是 SQLSelectStatement。
在 Druid 中,一条 SQL 语句中的元素,无论是高层次还是低层次的元素,都是一个 SQLObject,statement 是一种 SQLObject,表达式 expr 也是一种 SQLObject,函数、字段、条件等等,这些都是一种 SQLObject,SQLObject 是一个接口,accept 方法便是它定义的,目的是为了让访问者在访问 SQLObject 时,告知访问者一些事情,好让访问者在访问的过程中能够收集到关于该 SQLObject 的一些信息。
二、mycat心跳机制
mycat心跳任务流程图
参考URL: https://blog.csdn.net/John_Chang11/article/details/72639559
数据库中间件Mycat源码解析(四):Mycat的心跳管理
参考URL: https://blog.csdn.net/flashflight/article/details/52374644
heartbeatScheduler.scheduleAtFixedRate(dataNodeHeartbeat(), 0L, system.getDataNodeHeartbeatPeriod(), TimeUnit.MILLISECONDS);
MyCat的服务端在启动时会执行 node.startHeartbeat() 去设置每个node的heartbeat的状态为DBHeartbeat.OK_STATUS,然后针对每个node节点启动一个线程去对对应的node发送heartbeat sql(比如select user())进行心跳检测。