OceanBase 0.4 自舉時庫表創建過程

OceanBase 0.4 自舉時庫表創建過程

傳說,在OceanBase 0.4之前,表結構定義,包括系統內部元數據表,都從Schema文件中讀取。0.4版本之後,通過SQL語句創建表結構(《OceanBase 0.4.2 SQL 參考指南》《OceanBase 0.5 SQL 參考指南》)。

在開源的OceanBase 0.4版本(OB唯一開源版本)的RootServer代碼中,是可以看到從文件中讀取Schema配置的代碼段,在src/rootserver/ob_root_server2.cpp:340: ()

else if (!local_schema_manager_->parse_from_file(config_.schema_filename, config))
{
    TBSYS_LOG(ERROR, "parse schema error chema file is %s ", config_.schema_filename.str());
    res = false;
}

其中,config_.schema_filename的默認值是”etc/schema.ini“,但,這個文件安裝以後的內容很簡單(源碼構建時由src/rootserver/schema.ini拷貝至${prefix}/etc/schema.ini):

[app_name]
name=obtest         # edit the app name when you deploy OceanBase for the first time
max_table_id=2000    # do not edit

說明,從0.4版本開始,至少應用的庫表Schema結構定義不再從配置文件中讀取,而是通過SQL來定義。

Schema配置文件格式

通過一個示例來大致瞭解Schema配置文件格式:

[app_name]       
name=collect
max_table_id=1003

[collect_item_id]      
table_id=1001
table_type=2
column_info=1,16,item_name,varchar,20
column_info=1,17,new_price,int
rowkey=item_name
rowkey_max_length=9
max_column_id=17

[collect_info]
table_id=1002
table_type=2
colume_info=1,16,uid,int
column_info=1,17,item_name,varchar,20
column_info=1,18,item_price,int
column_info=1,19,collect_time,create_time
rowkey=uid,item_name
join=[item_name$item_name]%collect_item_id:item_name$item_name,item_price$new_price
rowkey_max_length=17
max_column_id=19

應用的信息

[app_name]
name=collect
max_table_id=1003

應用的信息都寫在 app_name 這個section 中. 目前主要有兩個配置項:

  • name 用來配置應用的名稱, 是一個長度不超過128位的字符串.
  • max_table_id 用來記錄當前已經使用的最大的table_id.
    在OceanBase中, 每個表都由table_id唯一標識, 且table_id不可以被重複使用. max_table_id 這個配置項, 主要是爲了方便 schema 生成程序記錄已經使用過的table_id.

代碼入口:src/common/ob_schema.cpp:1970

表的信息

[collect_info]
table_id=1002
table_type=2
...
rowkey=uid,item_name
join=rowkey[8,16]%collect_item_id:item_name$item_name,item_price$new_price
rowkey_max_length=17
max_column_id=19
...

每張表的信息都存放在以表名命名的section中。

  • table_id

    配置了這張表在OceanBase系統中的唯一id,在OceanBase系統中, id的取值範圍是0-65535.系統會保留0-1000的table_id供系統自身使用.

  • table_type
    配置表是內存表還是磁盤表,取值爲 1 的時候, 表示靜態部分放到磁盤上, 爲2的時候, 表示全部數據放到內存中.

  • max_column_id
    配置本表中已經使用過的最大的列id, 由schema 生成程序維護並使用, 防止對列id的重用.

  • compress_func_name
    可選項, 配置這個表在存儲時使用的壓縮算法名字.

  • block_size 可選項
    配置表在存儲成sstable時,採用的block大小.

  • use_bloomfilter 可選項
    配置表是否使用布隆過濾器, 非零值爲使用.

  • rowkey_max_length
    配置表中主鍵的最大長度.

  • rowkey_split
    配置表數據拆分是根據表主鍵值前多少位分佈,把數據存儲到多個tablet上存儲。這個值告訴ChunkServer, 在分拆數據到不同tablet時哪些數據是不應該被分開的, 比如, 當這個值爲9的時候, 表示主鍵前9個字節完全相同的記錄放置到同一個tablet中.

在Schema配置文件讀取程序源碼,已將屬性rowkey_max_length及rowkey_split相關代碼註解掉,但做爲表屬性還是存在的,在後面內部元數據表的屬性配置中可以看到在繼續使用

代碼入口:src/common/ob_schema.cpp:2122

列的定義

[collect_info]
...
colume_info=1,16,uid,int
column_info=1,17,item_name,varchar,20
column_info=1,18,item_price,int
column_info=1,19,collect_time,create_time
...

column_info 配置項中的內容是具體描述一列的, 用”,”分開, 其內容包含:

  • 列的屬性
    取值爲0或1. 0—表示該列只有動態數據(只存在於UpdateServer); 1—表示該列既有動態數據又有靜態數據(既存在於UpdateServer 又存在於 ChunkServer).
  • 列id
    列在表中的唯一標識, 不可以被重用. 列id必須大於1, 系統保留id爲1的用於表示主鍵.
  • 列名
    是一個長度不超過128位的字符串.
  • 類型
    列的數據類型.
  • 列長度

代碼入口:src/common/ob_schema.cpp:2303

保留列ID

src/common/ob_define.h:

static const uint64_t OB_ALL_MAX_COLUMN_ID = 65535;

// internal columns id
const uint64_t OB_NOT_EXIST_COLUMN_ID = 0;

const uint64_t OB_CREATE_TIME_COLUMN_ID = 2;
const uint64_t OB_MODIFY_TIME_COLUMN_ID = 3;

const int64_t OB_END_RESERVED_COLUMN_ID_NUM = 16;
const uint64_t OB_APP_MIN_COLUMN_ID = 16;

從上面定義可看到OceanBasew保留1-15列ID內部使用,應用自定義列的ID從16開始,最大65535。ID爲2和3的列對應記錄創建時間與記錄修改時間,這兩個列ID不能用於其它列,而且這兩個列的類型值爲8(ObCreateTimeType)與9(ObModifyTimeType)這個在代碼中有所檢查,比如:src/common/ob_schema.cpp:2410

if (parse_ok && type == ObCreateTimeType)
{
    if (has_create_time_column)
    {
        TBSYS_LOG(ERROR, "more than one column have create time type");
        parse_ok = false;
    }
    else
    {
        has_create_time_column = true;
        schema.set_create_time_column(id);
    }
}

if (parse_ok && type == ObModifyTimeType)
{
    if (has_modify_time_column)
    {
        TBSYS_LOG(ERROR, "more than one column have modify time type");
        parse_ok = false;
    }
    else
    {
        has_modify_time_column = true;
        schema.set_modify_time_column(id);
    }
}

與L2462開始處:

if (parse_ok)
{
    if (!has_create_time_column && has_create_time_id)
    {
        TBSYS_LOG(WARN, "schema file has no create_time column, and the default create_time column id is used");
        parse_ok = false;
    }
    if (!has_modify_time_column && has_modify_time_id)
    {
        TBSYS_LOG(WARN, "schema file has no modify_time column, and the default modify_time column id is used");
        parse_ok = false;
    }
}

RowKey

表主鍵配置項,大致格式:

column_name1(column_length%column_type),column_name2(column_length%column_type),...

其中,括號部分的內容是可選的。

從Schema定義文件中讀取RowKey定義的入口在src/common/ob_schema.cpp:2073

index = 0;
for(vector<string>::iterator it = sections.begin();
    it != sections.end() && parse_ok; ++it)
{
    if (strcmp(it->c_str(), STR_SECTION_APP_NAME) == 0)
    {
        continue;
    }
    if ( !parse_rowkey_info(it->c_str(), config, table_infos_[index]) )
    {
        parse_ok = false;
        table_nums_ = 0;
        break;
    }
    ++index;
}

方法parse_rowkey_info,從Schema定義文件中表列定義中讀取“rowkey”屬性值,其值由逗號分割的多個主鍵列組成:

bool ObSchemaManagerV2::parse_rowkey_info(const char* section_name, tbsys::CConfig&  config, ObTableSchema& schema)
{
    bool parse_ok = true;

    //parse rowkey column
    const char* rowkey_column_str = NULL;
    char* clone_column_str = NULL;
    ObRowkeyInfo  rowkey_info;
    vector<char*> column_list;

    if (NULL != section_name)
    {
        rowkey_column_str = config.getString(section_name, STR_ROWKEY);
    }
    if (NULL != rowkey_column_str)
    {
        clone_column_str = strdup(rowkey_column_str);
        tbsys::CStringUtil::split(clone_column_str, ",", column_list);
        int size = static_cast<int>(column_list.size());
        for (int index = 0; parse_ok && index < size; ++index)
        {
            ObRowkeyColumn column;
            parse_ok = parse_rowkey_column(column_list[index], column, schema);
            if (parse_ok)
            {
                schema.get_rowkey_info().add_column(column);
            }
        }
        free(clone_column_str);
    }

    return parse_ok;
}

每個主鍵列完整定義形式:column_name(column_length%column_type), 括號當中的被稱爲compatible rowkey descriptions(什麼鬼東西?),括號與括號的內容是可以省略的。先從表的列定義中根據colume_name查找列定義(ID、長度、類型),如果不存在compatible rowkey descriptions就直接使用列定義相關屬性,存在剛從compatible rowkey descriptions中解析列長度與類型,對應的方法是parse_rowkey_column

bool ObSchemaManagerV2::parse_rowkey_column(const char* column_str, ObRowkeyColumn& column, ObTableSchema& schema)
{
    // column str like
    // column_name(column_length%column_type)
    bool parse_ok = true;
    if (NULL != column_str)
    {
        char* str = strdup(column_str);
        str = str_trim(str);
        char* s = str;
        char* p = NULL;

        p = strchr(s, '(');
        if (NULL != p)
        {
            *p = '\0';
        }

        // no compatible rowkey descriptions, check if exist in table.
        const ObColumnSchemaV2* column_schema = get_column_schema(schema.get_table_name(), s);
        if (NULL == column_schema)
        {
            TBSYS_LOG(ERROR, "column (%s) not exist, cannot be part of rowkey.", s);
            parse_ok = false;
        }
        else
        {
            column.column_id_ = column_schema->get_id();
            column.length_ = column_schema->get_size();
            column.type_ = column_schema->get_type();
        }

        if (parse_ok && NULL != p)
        {
            // parse compatible rowkey descriptions. (8%int)
            s = p + 1;

            p = strchr(s, '%');
            if (NULL == p)
            {
                TBSYS_LOG(ERROR, "rowkey compatible format error:%s", s);
                parse_ok = false;
            }

            if (parse_ok)
            {
                *p = '\0';
                column.length_ = strtol(s, NULL, 10);

                s = p + 1;
                p = strchr(s, ')');
                if (NULL == p)
                {
                    TBSYS_LOG(ERROR, "rowkey compatible format error:%s", s);
                    parse_ok = false;
                }
            }

            if (parse_ok)
            {
                *p = '\0';
                column.type_  = ObColumnSchemaV2::convert_str_to_column_type(s);
            }

        }

        free(str);
    }

    return parse_ok;
}

聯表(Join)關係的配置

join配置項的格式大概是這個樣子:[r1$jr1,r2$jr2]%joined_table_name:f1$jf1,f2$jf2,...

  • joined_table_name 本表關聯的表名稱
  • r1/r2/../rn 本表參與關聯的列名稱,必須爲表主鍵組成列
  • jr1/jr2/…/jrn 被關聯表參與關聯的列名稱,必須爲表主鍵組成列
  • f1$jf1,f2$jf2,…
    參與join操作的左列和右列. join操作總是用右列的值合併到左列的值上, 然後將合併的結果返給用戶(左列和右列的值都不發生變化, 合併只體現在反給用戶的結果中).
    此處,講的合併,我理解上就是以右列值替代左列的值,相關代碼入口:src/chunkserver/ob_join_operator.cpp:515

以上所有參與關聯的列,類型必須一致。

分析Schema文件中Join關係的分析代碼在這:src/common/ob_schema.cpp:3563

Schema文件分析的時序關係

RootServer啓動時,會去分析配置文件中指定路徑下的Schema定義文件。
這裏寫圖片描述

內部元數據表的建立

在開源的OceanBase 0.4代碼的rootserver目錄下,還可以看到內部元數據表的Schema配置文件:meta_table_schema.ini,但我沒發現有地方使用這個文件。在代碼中尋找了一番,發現OceanBase內部表至少從0.4版本開始是由程序自動創建的,不再使用外部Schema配置文件。

內部表Schema讀取路徑

由此可見,所有內部表Schema均在類ObExtraTablesSchema中定義,採用直接填寫結構TableSchema的方式。代碼中定義表結構的片斷如下所示:

  • 表屬性設置

    table_schema.init_as_inner_table();
    strcpy(table_schema.table_name_, OB_ALL_SYS_STAT_TABLE_NAME);
    table_schema.table_id_ = OB_ALL_SYS_STAT_TID;
    table_schema.rowkey_column_num_ = 2;
    table_schema.max_used_column_id_ = OB_APP_MIN_COLUMN_ID + 4;
    table_schema.max_rowkey_length_ = OB_MAX_TABLE_NAME_LENGTH + sizeof(int64_t);
    strcpy(table_schema.compress_func_name_, OB_DEFAULT_COMPRESS_FUNC_NAME);
    table_schema.rowkey_split_ = 0;  

    在表屬性的設置中,可以看到設置了rowkey_split屬性,但在爲核心表及Sys表建立空Tablet時並沒有使用此屬性,不知道此屬性是否起作用,何時起作用,需要在後續代碼觀看中關注。

  • 列及列屬性設置

    int column_id = OB_APP_MIN_COLUMN_ID;
    ADD_COLUMN_SCHEMA("cluster_id", //column_name
        column_id ++, //column_id
        1, //rowkey_id
        ObIntType,  //column_type
        sizeof(int64_t), //column length
        false); //is nullable
    ADD_COLUMN_SCHEMA("name", //column_name
        column_id ++, //column_id
        2, //rowkey_id
        ObVarcharType,  //column_type
        OB_MAX_TABLE_NAME_LENGTH, //column length
        false); //is nullable
    ADD_COLUMN_SCHEMA("data_type", //column_name
        column_id ++, //column_id
        0, //rowkey_id
        ObIntType,  //column_type
        sizeof(int64_t), //column length
        false); //is nullable

    上面增加表列及設置屬性的語句中,column_id後面的一個參數爲非0值的,是標明此列是rowkey(主鍵)組成列,此參數也同時標明瞭在rowkey組成中的前後順序。

內部表結構的大致說明請參見《OceanBase內部表定義

內部表自舉(BootStrap)創建流程

核心三表創建時序圖

這裏寫圖片描述

核心表在RootServer自舉(每回看到這個詞,總感覺比較邪惡,爲毛!?)時創建一次,創建完成後,會產生成一個first_meta.bin文件,RootServer再次啓動時,如果本地存在first_meta.bin文件,則表明已經自舉過,可直接進入服務階段。

Sys表創建時序圖

這裏寫圖片描述

Schema配置文件中數據表的建立

RootServer自舉過程中,在完成內部元數據表創建後,會依次創建Schema配置文件中定義的庫表,除不爲每個表建立空Tablet外,其餘過程基本與內部Sys表創建時序一致。

使用SQL創建表的過程

這個就先不在這裏分析了,整的有些累了,回頭再給補上。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章