mycat 源码分析

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

作者思路写比较清晰,完全可以参考,对整体基本流程有个认识了解。

整体思路总结学习:

  1. mycat接收请求,根据mysql二进制协议,把特定字节数组,解析 SQL
  2. 获得路由结果
    // 路由到后端数据库,执行 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);
		}
		
 	}
  1. 获得 MySQL 连接,执行 SQL
  2. 响应执行 SQL 结果

问题整理

  1. \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

  1. 测试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());
    }
}
  1. 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条件。

  1. 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分别是二元操作符的左值和右值。

  1. 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;
    }

  1. 语法分析和词法分析的类 SQLParser 和 Lexer
    Druid 的代码里,代表语法分析和词法分析的类分别是 SQLParser 和 Lexer。并且, Parser 拥有一个 Lexer。Lexer子类 MySqlLexer等等。

而 MySqlLexer 类,除了沿用其父类的 Keywords 外,自己还有自己的 Keywords。可以理解为 Lexer 所维护的关键字集合,是通用的;而 MySqlLexer 除了有通用的关键字集合,也有属于 MySQL 数据库 SQL 方言的关键字集合。

  1. 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())进行心跳检测。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

西京刀客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值