作者: Ti-Cool
上周我们推送了《让数据库运行在浏览器里? TiDB + webAssembly 告诉你答案》, 向大家展示了 TiDB-Wasm 的魅力: TiDB-Wasm 项目是 TiDB Hackathon 2019 中诞生的二等奖项目, 实现了将 TiDB 编译成 Wasm 运行在浏览器里, 让用户无需安装就可以使用 TiDB.
本文由 Ti-Cool 队成员主笔, 为大家详细介绍 TiDB-Wasm 设计与实现细节.
10 月 27 日, 为期两天的 Hackathon 落下帷幕, 我们用一枚二等奖为此次上海之行画上了圆满的句号, 不枉我们风尘仆仆跑去异地参赛(强烈期待明年杭州能作为赛场, 主办方也该鼓励鼓励杭州当地的小伙伴呀 :D ).
我们几个 PingCAP 的小伙伴找到了 Tony 同学一起组队, 组队之后找了一个周末进行了 "秘密会晤"--Hackathon kick off. 想了 N 个 idea, 包括使用 unikernel 技术将 TiDB 直接跑在裸机上, 或者将网络协议栈做到用户态以提升 TiDB 集群性能, 亦或是使用异步 io 技术提升 TiKV 的读写能力, 这些都被一一否决, 原因是这些 idea 不是和 Tony 的工作内容相关, 就是和我们 PingCAP 小伙伴的日常工作相关, 做这些相当于我们在 Hackathon 加了两天班, 这一点都不酷. 本着「与工作无关」的标准, 我们想了一个 idea: 把 TiDB 编译成 Wasm 运行在浏览器里, 让用户无需安装就可以使用 TiDB. 我们一致认为这很酷, 于是给队伍命名为 Ti-Cool(太酷了).
WebAssembly 简介
这里插入一些 WebAssembly 的背景知识, 让大家对这个技术有个大致的了解.
WebAssembly 的 官方介绍 https://webassembly.org/ 是这样的: WebAssembly(缩写为 Wasm)是一种为基于堆栈的虚拟机设计的指令格式. 它被设计为 C/C++/Rust 等高级编程语言的可移植目标, 可在 Web 上部署客户端和服务端应用程序.
从上面一段话我们可以得出几个信息:
Wasm 是一种可执行的指令格式.
C/C++/Rust 等高级语言写的程序可以编译成 Wasm.
Wasm 可以在 Web(浏览器)环境中运行.
可执行指令格式
看到上面的三个信息我们可能又有疑问: 什么是指令格式?
我们常见的 ELF 文件 就是 Unix 系统上最常用的二进制指令格式, 它被 loader 解析识别, 加载进内存执行. 同理, Wasm 也是被某种实现了 Wasm 的 runtime 识别, 加载进内存执行, 目前常见的实现了 Wasm runtime 的工具有各种主流浏览器, Node.JS, 以及一个专门为 Wasm 设计的通用实现: Wasmer, 甚至还有人给 Linux 内核提 feature 将 Wasm runtime 集成在内核中, 这样用户写的程序可以很方便的跑在内核态.
各种主流浏览器对 WebAssembly 的支持程度:
<center > 图 1 主流浏览器对 WebAssembly 的支持程度</center>
从高级语言到 Wasm
有了上面的背景就不难理解高级语言是如何编译成 Wasm 的, 看一下高级语言的编译流程:
<center > 图 2 高级语言编译流程</center>
我们知道高级编程语言的特性之一就是可移植性, 例如 C/C++ 既可以编译成 x86 机器可运行的格式, 也可以编译到 ARM 上面跑, 而我们的 Wasm 运行时和 ARM,x86_32 其实是同类东西, 可以认为它是一台虚拟的机器, 支持执行某种字节码, 这一点其实和 Java 非常像, 实际上 C/C++ 也可以编译到 JVM 上运行(参考:).
各种 runtime 以及 WASI
再啰嗦一下各种环境中运行 Wasm 的事, 上面说了 Wasm 是设计为可以在 Web 中运行的程序, 其实 Wasm 最初设计是为了弥补 JS 执行效率的问题, 但是发展到后面发现, 这玩意儿当虚拟机来移植各种程序也是很赞的, 于是有了 Node.JS 环境, Wasmer 环境, 甚至还有内核环境.
这么多环境就有一个问题了: 各个环境支持的接口不一致. 比如 Node.JS 支持读写文件, 但浏览器不支持, 这挑战了 Wasm 的可移植性, 于是 WASI (WebAssembly System Interface) 应运而生, 它定义了一套底层接口规范, 只要编译器和 Wasm 运行环境都支持这套规范, 那么编译器生成的 Wasm 就可以在各种环境中无缝移植. 如果用现有的概念来类比, Wasm runtime 相当于一台虚拟的机器, Wasm 就是这台机器的可执行程序, 而 WASI 是运行在这台机器上的系统, 它为 Wasm 提供底层接口(如文件操作, socket 等).
Example or Hello World?
- (module
- ;; type iov struct { iov_base, iov_len int32 }
- ;; func fd_write(id *iov, iovs_len int32, nwritten *int32) (written int32)
- (import "wasi_unstable" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))
- (memory 1)(export "memory" (memory 0))
- ;; The first 8 bytes are reserved for the iov array, starting with address 8
- (data (i32.const 8) "hello world\n")
- ;; _start is similar to main function, will be executed automatically
- (func $main (export "_start")
- (i32.store (i32.const 0) (i32.const 8)) ;; iov.iov_base - The string address is 8
- (i32.store (i32.const 4) (i32.const 12)) ;; iov.iov_len - String length
- (call $fd_write
- (i32.const 1) ;; 1 is stdout
- (i32.const 0) ;; *iovs - The first 8 bytes are reserved for the iov array
- (i32.const 1) ;; len(iovs) - Only 1 string
- (i32.const 20) ;; nwritten - Pointer, inside is the length of the data to be written
- )
- drop ;; Ignore return value
- )
- )
- result = tk.MustQuery("select count(*) from t group by d order by c")
- result.Check(testkit.Rows("3", "2", "2"))
- // Exec executes a sql statement.
- func (tk *TestKit) Exec(sql string, args ...interface{}) (sqlexec.RecordSet, error) {
- var err error
- if tk.Se == nil {
- tk.Se, err = session.CreateSession4Test(tk.store)
- tk.c.Assert(err, check.IsNil)
- id := atomic.AddUint64(&connectionID, 1)
- tk.Se.SetConnectionID(id)
- }
- ctx := context.Background()
- if len(args) == 0 {
- var rss []sqlexec.RecordSet
- rss, err = tk.Se.Execute(ctx, sql)
- if err == nil && len(rss)> 0 {
- return rss[0], nil
- }
- return nil, errors.Trace(err)
- }
- stmtID, _, _, err := tk.Se.PrepareStmt(sql)
- if err != nil {
- return nil, errors.Trace(err)
- }
- params := make([]types.Datum, len(args))
- for i := 0; i <len(params); i++ {
- params[i] = types.NewDatum(args[i])
- }
- rs, err := tk.Se.ExecutePreparedStmt(ctx, stmtID, params)
- if err != nil {
- return nil, errors.Trace(err)
- }
- err = tk.Se.DropPreparedStmt(stmtID)
- if err != nil {
- return nil, errors.Trace(err)
- }
- return rs, nil
- }
- package storage
- import (
- "os"
- "syscall"
- )
- func newFileLock(path string, readOnly bool) (fl fileLock, err error) {
- return nil, syscall.ENOTSUP
- }
- func setFileLock(f *os.File, readOnly, lock bool) error {
- return syscall.ENOTSUP
- }
- func rename(oldpath, newpath string) error {
- return syscall.ENOTSUP
- }
- func isErrInvalid(err error) bool {
- return false
- }
- func syncDir(name string) error {
- return syscall.ENOTSUP
- }
- global.fs = {
- writeSync(fd, buf) {
- ...
- },
- write(fd, buf, offset, length, position, callback) {
- ...
- },
- open(path, flags, mode, callback) {
- ...
- },
- ...
- }
- function unimplemented(callback) {
- const err = new Error("not implemented");
- err.code = "ENOSYS";
- callback(err);
- }
- function unimplemented1(_1, callback) { unimplemented(callback); }
- function unimplemented2(_1, _2, callback) { unimplemented(callback); }
- fs.stat = unimplemented1;
- fs.lstat = unimplemented1;
- fs.unlink = unimplemented1;
- fs.rmdir = unimplemented1;
- fs.mkdir = unimplemented2;
- go.run(result.instance);
- JS.Global().Set("executeSQL", JS.FuncOf(func(this JS.Value, args []JS.Value) interface{} {
- go func() {
- // Simplified code
- sql := args[0].String()
- args[1].Invoke(k.Exec(sql))
- }()
- return nil
- }))
- JS.Global().Get("upload").Invoke(JS.FuncOf(func(this JS.Value, args []JS.Value) interface{} {
- go func() {
- fileContent := args[0].String()
- _, e := doSomething(fileContent)
- c <- e
- }()
- return nil
- }), JS.FuncOf(func(this JS.Value, args []JS.Value) interface{} {
- go func() {
- c <- errors.New(args[0].String())
- }()
- return nil
- }))
- CREATE DATABASE IF NOT EXISTS samp_db;
- USE samp_db;
- CREATE TABLE IF NOT EXISTS person (
- number INT(11),
- name VARCHAR(255),
- birthday DATE
- );
- CREATE INDEX person_num ON person (number);
- INSERT INTO person VALUES("1","tom","20170912");
- UPDATE person SET birthday='20171010' WHERE name='tom';
来源: https://segmentfault.com/a/1190000020988548