Doris源码分析-01-建表语句的执行

1,151 阅读6分钟

Doris(原来叫POLO)是百度开源的一款高性能实时分析的MPP数据库,个人一直对MPP数据库的底层执行原理不是很了解,通过对Doris源码的学习,来熟悉底层的实现机制。Doris官方出了一个Apache Doris源码阅读与解析的视频课程,个人写这个系列文章,一是加深自己对这块内容的理解,二个人也会结合着自己的理解补充一些其他的内容,如一些原理类的内容,另外对读者而言相对于视频来说读文章会节省一些时间。 第一篇从一个建表语句的执行的流程来学习Doris的主要组件、SQL的整体执行过程以及元数据管理的内容。下面我们从Doris的整体结构开始

Doris架构

首先我们来了解下Doris的整体架构,通过官方文档,其主要有2个组件

image.png

  • Frontend(FE): 前端部分,类似于Master节点,会有多个,主要负责用户请求、查询解析和生成计划任务、管理元数据和节点管理相关的工作
  • Backend(BE): 后端节点,类似于Worker节点,负责数据的存储和查询计划的执行

相关的源代码分别有个fe(java)和be(c++),fe的主类是PaloFe.java,be的主类是doris_main.cpp。 接下来我们看看PaloFe的主要组成

    public static void start(String dorisHomeDir, String pidDir, String[] args) {
        ...
        // 1. HttpServer for HTTP Server
        // 2. FeServer for Thrift Server
        // 3. QeService for MySQL Server
        QeService qeService = new QeService(Config.query_port, Config.mysql_service_nio_enabled, ExecuteEnv.getInstance().getScheduler());
        FeServer feServer = new FeServer(Config.rpc_port);

        feServer.start();

        HttpServer httpServer = new HttpServer();
        ...
    }
  • HttpServer: 提供各类http接口功能,如集群管理、各类状态信息以及其他的一些操作功能,如上传文件等
  • FeServer: thrift协议的各类功能接口,如获取各类元数据信息,状态信息和导入数据等,具体的接口功能见FrontendServiceImpl.java实现
  • QeService: 实现了MySQL的协议,这样可以使用MySQL的客户端来连接Doris,同样也可以无缝的支持BI工具的连接。QeService里面实际是通过MysqlServer类来支持MySQL协议的网络连接

建表语句的执行

接下来我们通过一条建表语句的执行流程来学习Doris的各个组件是如何配合来完成此过程的。具体的调用过程如下图

image.png

下面重点介绍其中的几个主要的类

  • ConnectProcessor: 负责一个mysql连接的请求,接收一个请求,处理请求,然后返回结果。其接收的相关请求方法有
方法名说明
handleInitDb改变当前session的默认数据库
handleQuit退出
handlePingping处理,直接返回ok
handleQuery这里名字叫Query,实际是处理所有SQL语句
handleFieldList处理获取一个表的所有字段信息的请求
这里建表语句是通过handleQuery来处理的,其中主要有2步,第一步将原始的sql字符串解析成StatementBase对象(即一个语法树,学过编译原理的同学应该比较清楚,这个是一个词法和语法解析的过程,这块等后续介绍查询语句执行的时候再详细介绍); 第二步是将解析好的语法树(Doris是支持一次提交多个sql语句的,所以前面第一步可能会产生多个StatementBase对象)分别提交到StmtExecutor去执行
  • StmtExecutor:负责处理一个sql语句的处理,这里定义了各种不同的handle**Stmt的方法来处理各种不同的stmt,如handleQueryStmt、handleSetStmt、handleDdlStmt。由于本篇重点介绍建表语句的流程,所以这里只介绍handleDdlStmt的处理,这个处理比较简单,直接是交给DdlExecutor的execute()方法来处理。
  • DdlExecutor: 负责一个ddl语句的处理,ddl是做数据定义的,所以基本上是调用了Catalog类的相关方法来处理,里面实际是一个超长的if else if 语句,下面贴了部分代码
    public static void execute(Catalog catalog, DdlStmt ddlStmt) throws Exception {
        if (ddlStmt instanceof CreateClusterStmt) {
            CreateClusterStmt stmt = (CreateClusterStmt) ddlStmt;
            catalog.createCluster(stmt);
        } else if (ddlStmt instanceof CreateTableStmt) {
            catalog.createTable((CreateTableStmt) ddlStmt);
        } else if (ddlStmt instanceof CreateTableLikeStmt) {
            catalog.createTableLike((CreateTableLikeStmt) ddlStmt);
        } else if (ddlStmt instanceof CreateTableAsSelectStmt) {
            catalog.createTableAsSelect((CreateTableAsSelectStmt) ddlStmt);
        } else if (ddlStmt instanceof DropTableStmt) {
            catalog.dropTable((DropTableStmt) ddlStmt);
        }
        ...
    }
  • Catalog: 负责doris的各种元数据操作处理,是一个超级大的类,这里只介绍createTable方法的部分,doris里面支持了多个不同的引擎,除Doris本身的olap外,还支持odbc、mysql、broker、elasticsearch、hive和iceberg。这里重点介绍下olap表的创建,具体分为如下几步
    1. 创建表信息,如字段、分区、分布、压缩、副本等。并做一些验证处理,如验证字段信息,包括表必须有字段、key字段需在value前面和表必须有key字段
    2. 根据创建的分区、MaterializedIndex、tablet(分桶)和副本数等信息,按tablet数*副本数闯将对应的CreateReplicaTask任务,然后提交到各个Backend上去执行(这块的具体处理后续再介绍)
    3. 将该表信息添加到内存的元数据信息中,具体的元数据结构下一节介绍
    4. 最后把这个信息记录到EditLog里面,用于出错的恢复

元数据管理

本节我们来了解下Doris中元数据的部分,元数据即存储数据库及表的定义信息的内容

元数据结构

首先我们看看元数据的结构,这个就常见的数据库层次结构Catalog->Database->Table->Column

  • Catalog: Catalog下有多个Database,里面通过Map来记录相应的数据
    private ConcurrentHashMap<Long, Database> idToDb;
    private ConcurrentHashMap<String, Database> fullNameToDb;
  • DataBase: 数据库,下面有多个Table, 除了记录这个外还有如用户自定义函数,数据库属性等
    private Map<Long, Table> idToTable;
    private Map<String, Table> nameToTable;
  • Table: 表信息,里面记录了字段信息,Table有多种不同的类型,如前面介绍的olap、mysql、hive、odbc还有view等等,不同类型的表分别对应有一个子类的实现,如olap表有个OlapTable子类。
    protected List<Column> fullSchema;
    protected Map<String, Column> nameToColumn;
  • Column: 字段信息,里面记录如字段名、类型等,Doris里面有个特殊的叫AggregateType,聚合类型,记录该字段聚合的方法,如SUM、MIN、MAX等。

元数据持久化

元数据是保存在内存中的,如果正常的服务停机,那需要将元数据进行持久化保存,后续启动服务的时候可以再加载。我们来看看Doris的元数据持久化处理。这里在Catalog类中有提供了loadImage()和saveImage()。具体保存的路径是metadir目录下的image子目录。另外为了保证机器出错导致元数据丢失的问题,这个也是通过EditLog来进行保障的。

总结

本篇我们介绍了Doris的整体架构,并通过一个建表语句的执行了解了sql提交到具体执行的整个过程,最后我们也介绍了一下Doris里面的元数据管理的部分。

参考资料:

  1. Apache Doris源码阅读与解析(官方)