1. 引言
雲原生數據庫跟分布式mpp數據庫是有差異的,雖然兩者都是計算與存儲分離,但是在資源的占用上有所不同。雲原生數據庫是shard everything架構,其依賴的存儲資源、內存資源、事務資源在雲中都是共享、彈性伸縮的。由分布式文件系統提供按需分配、租戶隔離的塊存儲,由分布式內存池提供buffer pool占用的大塊內存。分布式mpp數據庫則是shard nothing架構,其依賴的存儲資源、內存資源、事務資源是單個物理節點上的資源,在SQL計算層進行了分布式計算邏輯的分發。
本文重點介紹共享存儲,如果分布式文件系統的iops、每秒刷盤數能夠比單個物理節點上的性能線性提升,那么雲原生數據庫mysql的tps也會隨之提升,而且mysql的原生SQL語法都是支持的,包括嵌套子查詢、存儲過程、函數等。分布式mpp在SQL計算層做分布式計算邏輯的分發,這些可能會被裁減掉。
單機mysql的事務型存儲引擎innodb的表空間數據存儲依賴於單個linux節點的VFS提供的posix標准的文件操作接口。VFS作為各個具體文件系統(ext4、xfs、ext3等)的抽象層,屏蔽了各個具體文件系統的實現差異,對應用層提供了統一、標准的posix文件操作接口。類似於阿里雲的polardb對polarfs的依賴,其實polardb就是mysql的內核源碼的二次開發而成的。本文重點羅列雲原生數據庫mysql在各個場景下對posix文件操作接口需求。
分布式彈性文件系統要具體地實現這些接口,各個接口的語義要完全符合posix標准。並提供mount掛載的功能,將其實現的具體接口注冊到VFS內部,mysql將表空間放置到掛載了分布式彈性文件系統的路徑下,innodb內部對表空間文件操作時候,實際上就調用了分布式文件系統提供的文件操作的api。
innodb的表空間有用戶表空間、系統表空間、Redo日志、Undo表空間。本文重點分析用戶表空間對文件操作接口的需求,應該也涵蓋了其余的表空間對文件操作接口的需求。
用戶對用戶表空間的操作主要有兩類,一類是表空間數據的讀寫,另一類是表空間DDL操作,對應到posix標准的文件操作接口,一類是文件數據讀寫IO,另一類是文件的元數據操作。
2. 表空間數據的讀寫操作
2.1 同步IO
同步IO會阻塞調用線程,直到IO完成,調用線程才返回,pwrite/pread函數是線程安全的同步IO,lseek+read/write函數非線程安全,需要加互斥鎖,並發量大的時候,對性能有一定的影響。以下幾種場景下,會使用同步IO。
場景一. linux不支持native aio, Page cleaner線程刷臟,同步寫
從buffer pool中刷臟頁時候,如果不支持native aio,則通過simulated aio模擬異步寫進行dirty page的寫入表空間操作,本質上是同步寫
調用棧:
1 buf_flush_page_cleaner_worker → pc_flush_slot → buf_flush_do_batch → buf_do_flush_list_batch → buf_flush_page_and_try_neighbors → buf_flush_try_neighbors → buf_flush_page → buf_flush_write_block_low → fil_io(sync=false) → os_aio → os_aio_func →AIO::wake_simulated_handler_thread
場景二. 刷臟時,如果double write buffer寫滿,將double write buffer中數據寫入系統表空間ibdata文件,同步寫
調用棧:
buf_flush_page → buf_dblwr_flush_buffered_writes → fil_io(sync=true)
場景三. 事務buffer中數據寫入Redo log file,同步寫
調用棧:
1 innobase_commit_low → trx_commit_for_mysql → trx_commit → trx_commit_in_memory → trx_flush_log_if_needed_low → log_write_up_to → log_group_write_buf → log_group_write_buf → fil_io(sync=true)
場景四,用戶線程觸發的數據塊請求讀寫,同步讀寫
調用棧:
1 ha_innobase::index_read → row_search_mvcc → row_sel_get_clust_rec_for_mysql→ buf_page_get_gen → buf_read_page → buf_read_page_low → fil_io(sync=true)
2.2異步IO
異步IO不會阻塞調用線程,提交IO請求后,調用線程就返回,可以做其余的操作,后台線程會輪詢IO的完成情況,如果執行完成可以調用相關的回調函數。
在支持native aio的情況下,innodb的后台 Page cleaner線程刷臟,預讀走的就是異步IO流程,主要以下兩個場景。
場景一. linux支持native aio ,Page cleaner線程刷臟,異步寫
從buffer pool中刷臟頁時候,如果支持native aio,則通過 io_submit異步io接口進行dirty page的表空間寫入操作。
1 buf_flush_page_cleaner_worker → pc_flush_slot → buf_flush_do_batch → buf_do_flush_list_batch → buf_flush_page_and_try_neighbors → buf_flush_try_neighbors →buf_flush_page → buf_flush_write_block_low → fil_io(sync=false)→ os_aio → os_aio_func → AIO::linux_dispatch → io_submit
場景二. 線性或者邏輯預讀,異步讀
邏輯預讀調用棧:
1 ha_innobase::index_read → row_search_mvcc → row_sel_get_clust_rec_for_mysql → buf_page_get_gen → buf_read_ahead_random → fil_io(sync=false)
線性預讀調用棧:
1 ha_innobase::index_read → row_search_mvcc → row_sel_get_clust_rec_for_mysql → buf_page_get_gen → buf_read_ahead_linear→ fil_io(sync=false)
2.3 刷盤
如果innodb_flush_method設置了O_DSYNC,日志文件(ib_logfileXXX)使用O_SYNC打開,因此寫完數據不需要調用函數fsync刷盤,數據文件(ibd)使用default模式打開,因此寫完數據需要調用fsync刷盤。
如果innodb_flush_method設置了fsync或者不設置,數據文件和日志文件都使用default模式打開,寫完數據都需要使用fsync來刷盤。
如果innodb_flush_method設置了O_DIRECT,日志文件(ib_logfileXXX)使用default模式打開,寫完數據需要調用fsync函數刷盤,數據文件(ibd)使用O_DIRECT模式打開,寫完數據需要調用fsync刷盤。
如果innodb_flush_method設置為O_DIRECT_NO_FSYNC,文件打開方式與O_DIRECT模式類似,區別是,數據文件寫完后,不調用fsync來刷盤,主要針對O_DIRECT能保證文件的元數據也落盤的FS
如果使用linux native aio,innodb_flush_method一定要配置成O_DIRECT,否則會退化成同步IO。
3. 表空間DDL操作
3.1 create table
創建表時候調用,調用流程如下:
1 ha_innobase::create → dict_build_tablespace_for_table → fil_idb_create
依次依賴於 os_file_create 、os_file_flush、os_file_set_size、os_file_close、os_file_delete, 這些函數依次依賴於open\ fsync\lseek\close\unlink posix文件標准接口。
3.2 drop table
刪除表的時候調用,調用棧如下。
1 ha_innobase::delete_table → row_drop_table_for_mysql → row_drop_single_table_tablespace → fil_delete_tablespace → unlink
3.3 rename table
重命名表的時候調用,調用棧如下。
1 ha_innobase::rename_table → row_rename_table_for_mysql → row_rename_table_for_mysql → dict_table_rename_in_cache→ fil_rename_tablespace → rename
3.4 truncate table
截斷表時候調用,默認表空間截留4個page的大小。調用棧如下。
1 ha_innobase::truncate → row_truncate_table_for_mysql → row_truncate_complete → truncate_t::truncate → os_file_truncate_posix → ftruncate
3.5 extend tablespace
innodb表空間文件大小是動態擴展的,如果表空間中的數據頁不夠,則需要對表空間文件進行預擴展,比如往聚集索引中大量插入數據的時候。調用棧如下
1 row_ins_clust_index_entry_low → btr_cur_pessimistic_insert → fsp_try_extend_data_file → fsp_try_extend_data_file → fil_space_extend → posix_fallocate
4.posix標准的文件操作接口列表
4.1 文件元數據操作
1 open(const char *__file, int __oflag, …) 2 close (int __fd); 3 rename (const char *__old, const char *__new) 4 fcntl(int __fd, int __cmd, ...) 5 unlink(const char *__name) 6 mkdir(const char *__path) 7 rmdir(const char *__path) 8 ftruncate(int __fd, __off64_t __length) 9 posix_fallocate(int __fd, __off64_t __offset,__off64_t __len)
4.2 同步IO接口
1 lseek(int __fd, __off64_t __offset, int __whence) 2 read(int __fd, void *__buf, size_t __nbytes) 3 write(int __fd, const void *__buf, size_t __n) 4 pread(int __fd, void *__buf, size_t __nbytes, __off64_t __offset) 5 pwrite(int __fd, const void *__buf, size_t __nbytes, __off64_t __offset) 6 fsync(int __fd)
4.3 異步IO接口
1 io_setup(int maxevents, io_context_t *ctxp); 2 io_destroy(io_context_t ctx); 3 io_submit(io_context_t ctx, long nr, struct iocb *ios[]); 4 io_cancel(io_context_t ctx, struct iocb *iocb, struct io_event *evt); 5 io_getevents(io_context_t ctx, long min_nr, long nr, struct io_event *events, struct timespec *timeout);
4.4 掛載
mount
umount
4.5 雜項
其余文件屬性、權限類的操作,就不一一列舉了。