SQLite — это кроссплатформенная реляционная база данных, которая широко используется при разработке клиентов, Feishu также использует SQLite в качестве постоянного хранилища данных, при этом для удобства использования на верхнем уровне в качестве формы для взаимодействия с SQLite используется дизель. Общее использование выглядит следующим образом:
rust code -> diesel orm -> sqlite ffi
Ситуация со звонком следующая:
Чтобы перенести SQLite в WEB, нам нужно сделать две части:
- Скомпилируйте sqlite на платформу wasm
- Инкапсулировать межмодульный интерфейс вызова платформы wasm для дизельного использования.
Учитывая хрупкость механизма персистентного хранения в WEB и рассмотрение бизнес-формы, нет необходимости делать персистентность в WEB.реляционная база данных в памяти; После подтверждения этих характеристик и требований наш путь переноса SQLite WASM начался, поехали!
Как работает ВАСМ
На данный момент WASM фактически имеет три режима работы: режим Emscripten, режим WASI и чистый режим без каких-либо зависимостей, на языке Rust им соответствуетwasm32-unknown-emscripten
,wasm32-wasi
,wasm32-unknown-unknown
Три цели компиляции; продукты wasm первых двух режимов требуют, чтобы хост предоставлял функции интерфейса posix и интерфейса wasi соответственно, а последний режим вообще не имеет внешних зависимостей.
Для этих трех режимов дружественность кода C/C++:Emscripten>Wasi>>Unknown
Экология сообщества ржавчины в основном вращается вокругwasm32-unknown-unknown
а такжеwasm32-wasi
Встроенные, такие как инструмент wasm-bindgen и т. д., однако, учитывая, что неизвестное окружение имеет меньше внешних зависимостей, мы сначала определим код ржавчины в sdk и будем использовать его в первую очередь.wasm32-unknown-unknown
модель,wasm32-wasi
режим следующий. Что касается sqlite, мы попробовали все три режима работы wasm:
Адаптация режима Emscripten
Emscripten — это цепочка инструментов, используемая для компиляции кода C/C++ в целевой формат WASM и обеспечивающая эмуляцию вызовов, связанных с posix.
Скомпилируйте продукт emscripten
SQLite — это библиотека C. Очень удобно использовать emscripten для компиляции SQLite в wasm.Этот процесс очень прост, и его можно скомпилировать напрямую с помощью emcc (см.:GitHub.com/SQL — Вот и все/SQL. …)
Первый шаг — легко скомпилировать sqlite в wasm: мы компилируем sqlite в экземпляр wasm цели emscripten, который загружается внешним интерфейсом; затем на стороне sdk вызываем интерфейс, предоставленный экземпляром sqlite wasm, через wasm abi. интерфейс.
Вызов интерфейса SQLite
Но на втором шаге, при предоставлении wasm ffi для Diesel, мы столкнулись с проблемой: libsqlite-sys по умолчанию, используемый Diesel, предоставляет ffi C abi. легче обрабатывать такие вещи, как выделение памяти или прямое управление указателями и т. д.; но если sqlite скомпилирован в один экземпляр wasm, а часть ffi вызывает sqlite как отдельный экземпляр wasm, два экземпляра wasm находятся в разной памяти. Операции, которые полагаются на одно и то же пространство памяти, например указатели, нельзя использовать напрямую, что приводит к необходимости новой реализации процесса вызова ffi под целью emscripten.
Назовите его так же, как динамическую библиотеку.
Конкретная производительность такова: сначала асинхронно запустить экземпляр sqlite wasm, и повесить интерфейс, экспортируемый экземпляром, на окно глобального объекта js, а затем использовать wasm-bindgen для привязки этих js-интерфейсов в rust. Например, когда sqlite подключается к базе данных для создания соединения с базой данных, передается путь к базе данных. Среде wasm необходимо вызвать функцию распределения памяти wasm для выделения памяти и записи данных:
// native版操作
pub fn establish(raw_database_url: &str) -> ConnectionResult<Self> {
let mut conn_pointer = ptr::null_mut();
let database_url = CString::new(raw_database_url.trim_start_matches("sqlite://"))?;
let connection_status = unsafe { ffi::sqlite3_open(database_url.as_ptr(), &mut conn_pointer) };
...
}
// wasm版操作
#[wasm_bindgen]
extern "C" {
// sqliteBindings是挂在window上的全局对象
// allocateUTF8、stackAlloc是emscripten wasm导出的字符串、栈内存分配接口
#[wasm_bindgen(js_namespace = sqliteBindings, js_name = allocateUTF8)]
pub fn allocate_utf8(s: &str) -> *const i8;
#[wasm_bindgen(js_namespace = sqliteBindings, js_name = stackAlloc)]
pub fn stack_alloc_sqlite3(size: usize) -> *mut *mut ffi::sqlite3;
}
pub fn establish(raw_database_url: &str) -> ConnectionResult<Self> {
let conn_pointer = stack_alloc_sqlite3(0);
let database_url_ptr = allocate_utf8(raw_database_url.trim_start_matches("sqlite://"));
let connection_status = unsafe { ffi::sqlite3_open(database_url_ptr, conn_pointer) };
...
}
Для мест, где sqlite используется в дизельном топливе, это похоже на добавление поддержки wasm. Мы реализовали Diesel + sqlite, работающий в режиме emscripten. Метод потока данных выглядит следующим образом:
В этом рабочем режиме sqlite является независимым экземпляром wasm, а другие коды sdk lark являются экземпляром.При фактическом запуске код js загружает экземпляр wasm sqlite, затем загружает экземпляр wasm sdk, а затем дизельный код в sdk. Вызывайте функции экземпляра sqlite через инкапсулированный интерактивный интерфейс.
В этом рабочем режиме каждый вызов sqlite включает копирование данных между двумя экземплярами wasm (пространства памяти разных экземпляров wasm независимы), что слишком дорого для сценария высокочастотных вызовов данных, такого как db.
Итак, мы рассматриваем: можно ли объединить экземпляр sqlite и другие коды экземпляра sdk для создания экземпляра wasm? Если sqlite является wasm в режиме emscripten, другие коды sdk также должны быть помечены в режиме emscripten, но, как упоминалось ранее, ядром экологии wasm в ржавчине являетсяwasm32-unknown-unknown
а такжеwasm32-wasi
, поэтому, если вы хотите создать экземпляр, содержащий код SDK и sqlite, вы не можете использоватьwasm32-unknown-emscripten
модель. Кроме того, вwasm32-wasi
а такжеwasm32-unknown-unknown
В режиме мы можем использовать C abi, то есть нам не нужна инкапсуляция интерфейса wasm, такая как режим emscripten, и мы можем вызывать sqlite из rust аналогично нативной платформе.
Адаптация режима WASI
В практике оптимизации sdk и sqlite как экземпляра мы исключили использование режима Emscripten, в режиме wasi и unknown васи является платформой, более дружественной к C/C++ коду, а интерфейс в стандарте wasi ближе к posix .
Однако васи в настоящее время выполняется на платформах, отличных от WEB. Если вы хотите работать в Интернете, вам необходимо предоставить симуляцию соответствующих функций, требуемых васи. К счастью, у сообщества уже есть соответствующие функции:GitHub.com/вау, какой день/вау…
Работающая хост-среда готова, давайте посмотрим на сам sqlite, на данный момент sqlite не предоставляет официальной поддержки wasi, но sqlite имеет очень гибкую архитектуру:
SQLite инкапсулирует все операции, связанные с платформой, в соответствующие модули ОС и абстрагирует использование функций платформы через VFS, так что пока мы реализуем vfs, работающую в режиме WASI.
Обратитесь непосредственно к официальной реализацииwoohoo.sqlite.org/double/doc/ввод…, откройте опцию SQLITE_OS_OTHER при компиляции и свяжите с нашими соответствующими vfs, реализованными на языке C, сотрудничайте с симуляцией wasmer-js и, наконец, мы добавим sqlite и другие коды sdk в экземпляр wasm в режиме wasm32-wasi.
но. . .
После однократного обновления ржавой версии было обнаружено, что wasm-bindgen больше не работает. . . Подробнее см.:GitHub.com/rust socksSM/вау…, Причина проблемы в том, что 13 января 2021 года rust слил коммит, меняющий формат abi в режиме wasi.До этого abi режима wasi и unknown mode в rust были одинаковыми, но после этого коммита two forked , и wasm-bindgen официально не планирует адаптироваться к wasi. . .
Так что теперь нам остается только один путь: wasm32-unknown-unknown
Адаптация неизвестного режима
Неизвестный режим наименее дружелюбен к C/C++: без объявлений заголовочных файлов, без методов манипулирования строками, без методов, связанных с fd, и даже с malloc. . . Но есть только один путь, увидеть горы и открыть горы, и встретиться с морем, чтобы вернуть себе море.
Есть три функции, которые необходимо реализовать для работы в неизвестном режиме: распределитель памяти, используемые функции C, реализация VFS.
адаптация распределителя памяти
язык С вwasm32-unknown-unknown
В режиме нет инкапсуляции malloc, но в rust есть инкапсуляция, связанная с памятью, поэтому мы можем реализовать метод malloc в rust, чтобы sqlite вызывал его после компоновки:
// 为了最小影响,更改了malloc的调用名
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
mod allocator {
use std::alloc::{alloc, dealloc, realloc as rs_realloc, Layout};
#[no_mangle]
pub unsafe fn sqlite_malloc(len: usize) -> *mut u8 {
let align = std::mem::align_of::<usize>();
let layout = Layout::from_size_align_unchecked(len, align);
let ptr = alloc(layout);
ptr
}
const SQLITE_PTR_SIZE: usize = 8;
#[no_mangle]
pub unsafe fn sqlite_free(ptr: *mut u8) -> i32 {
let mut size_a = [0; SQLITE_PTR_SIZE];
size_a.as_mut_ptr().copy_from(ptr, SQLITE_PTR_SIZE);
let ptr_size: u64 = u64::from_le_bytes(size_a);
let align = std::mem::align_of::<usize>();
let layout = Layout::from_size_align_unchecked(ptr_size as usize, align);
dealloc(ptr, layout);
0
}
#[no_mangle]
pub unsafe fn sqlite_realloc(ptr: *mut u8, size: usize) -> *mut u8 {
let align = std::mem::align_of::<usize>();
let layout = Layout::from_size_align_unchecked(size, align);
rs_realloc(ptr, layout, size)
}
}
функция libc предоставляет
После включения переключателя SQLITE_OS_OTHER, поскольку системный интерфейс больше не используется, зависимость от libc значительно уменьшилась, но все еще есть несколько основных несистемных зависимостей функций:
strcspn
strcmp/strncmp
strlen
strchr/strrchr
qsort
Некоторые функции строки очень просты, просто реализуйте их сами, а для последней функции qsort скопируйте стороннюю реализацию с свободной лицензией.
Реализация ВФС
И emscripten, и wasi используют для работы виртуальную файловую систему, предоставленную хостом.Чтобы не увеличивать внешние зависимости в неизвестном режиме, мы можем напрямую предоставить vfs памяти внутри кода SDK для использования sqlite.
Суть реализации vfs заключается в обеспечении двух реализаций структуры:
typedef struct sqlite3_vfs sqlite3_vfs;
typedef void (*sqlite3_syscall_ptr)(void);
struct sqlite3_vfs {
int iVersion; /* Structure version number (currently 3) */
int szOsFile; /* Size of subclassed sqlite3_file */
int mxPathname; /* Maximum file pathname length */
sqlite3_vfs *pNext; /* Next registered VFS */
const char *zName; /* Name of this virtual file system */
void *pAppData; /* Pointer to application-specific data */
int (*xOpen)(sqlite3_vfs*, const char *zName, sqlite3_file*,
int flags, int *pOutFlags);
int (*xDelete)(sqlite3_vfs*, const char *zName, int syncDir);
int (*xAccess)(sqlite3_vfs*, const char *zName, int flags, int *pResOut);
int (*xFullPathname)(sqlite3_vfs*, const char *zName, int nOut, char *zOut);
void *(*xDlOpen)(sqlite3_vfs*, const char *zFilename);
void (*xDlError)(sqlite3_vfs*, int nByte, char *zErrMsg);
void (*(*xDlSym)(sqlite3_vfs*,void*, const char *zSymbol))(void);
void (*xDlClose)(sqlite3_vfs*, void*);
int (*xRandomness)(sqlite3_vfs*, int nByte, char *zOut);
int (*xSleep)(sqlite3_vfs*, int microseconds);
int (*xCurrentTime)(sqlite3_vfs*, double*);
int (*xGetLastError)(sqlite3_vfs*, int, char *);
/*
** The methods above are in version 1 of the sqlite_vfs object
** definition. Those that follow are added in version 2 or later
*/
int (*xCurrentTimeInt64)(sqlite3_vfs*, sqlite3_int64*);
/*
** The methods above are in versions 1 and 2 of the sqlite_vfs object.
** Those below are for version 3 and greater.
*/
int (*xSetSystemCall)(sqlite3_vfs*, const char *zName, sqlite3_syscall_ptr);
sqlite3_syscall_ptr (*xGetSystemCall)(sqlite3_vfs*, const char *zName);
const char *(*xNextSystemCall)(sqlite3_vfs*, const char *zName);
/*
** The methods above are in versions 1 through 3 of the sqlite_vfs object.
** New fields may be appended in future versions. The iVersion
** value will increment whenever this happens.
*/
};
typedef struct sqlite3_io_methods sqlite3_io_methods;
struct sqlite3_io_methods {
int iVersion;
int (*xClose)(sqlite3_file*);
int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst);
int (*xTruncate)(sqlite3_file*, sqlite3_int64 size);
int (*xSync)(sqlite3_file*, int flags);
int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize);
int (*xLock)(sqlite3_file*, int);
int (*xUnlock)(sqlite3_file*, int);
int (*xCheckReservedLock)(sqlite3_file*, int *pResOut);
int (*xFileControl)(sqlite3_file*, int op, void *pArg);
int (*xSectorSize)(sqlite3_file*);
int (*xDeviceCharacteristics)(sqlite3_file*);
/* Methods above are valid for version 1 */
int (*xShmMap)(sqlite3_file*, int iPg, int pgsz, int, void volatile**);
int (*xShmLock)(sqlite3_file*, int offset, int n, int flags);
void (*xShmBarrier)(sqlite3_file*);
int (*xShmUnmap)(sqlite3_file*, int deleteFlag);
/* Methods above are valid for version 2 */
int (*xFetch)(sqlite3_file*, sqlite3_int64 iOfst, int iAmt, void **pp);
int (*xUnfetch)(sqlite3_file*, sqlite3_int64 iOfst, void *p);
/* Methods above are valid for version 3 */
/* Additional methods may be added in future releases */
};
Использование rust для реализации vfs
Для реализации memvfs нужен как минимум динамически настраиваемый контейнер, а в языке C такого контейнера официально нет. немного хлопотно, поэтому эта логика также реализована в rust.
привязка VFS
В коде Rust укажитеsqlite3_os_init
Выходит метод, при линковке с sqlite он автоматически линкуется на эту функцию
#[no_mangle]
pub unsafe fn sqlite3_os_init() -> std::os::raw::c_int {
let mut mem_vfs = Box::new(super::memvfs::get_mem_vfs());
let mem_vfs_ptr: *mut crate::sqlite3_vfs = mem_vfs.as_mut();
let rc = crate::sqlite3_vfs_register(mem_vfs_ptr, 1);
debug!("sqlite3 vfs register result: {}", rc);
std::mem::forget(mem_vfs);
rc
}
Контейнер для хранения данных в памяти
Поскольку вы хотите поддерживать несколько путей, простейшей реализацией является предоставление HashMap с использованием пути в качестве ключа:
struct Node {
size: usize,
data: Vec<u8>,
}
lazy_static! {
static ref FS: RwLock<HashMap<String, Arc<RwLock<Node>>>> = RwLock::new(HashMap::new());
}
Интерфейс чтения и записи данных:
fn copy_out(&self, dst: *mut raw::c_void, offset: isize, count: usize) -> Option<()> {
if self.size < offset as usize + count {
log::trace!("handle invalid input offset");
return None;
}
let ptr = self.data.as_ptr();
let dst = dst as *mut u8;
unsafe {
let ptr = ptr.offset(offset);
ptr.copy_to(dst, count);
}
Some(())
}
fn write_in(&mut self, src: *const raw::c_void, offset: isize, count: usize) {
let new_end = offset as usize + count;
// 这里注意要根据传入的offset做扩容
let count_extend: isize = new_end as isize - self.data.len() as isize;
if count_extend > 0 {
self.data.extend(vec![0; count_extend as usize]);
}
if new_end > self.size {
self.size = new_end;
}
let ptr = self.data.as_mut_ptr();
unsafe {
let ptr = ptr.offset(offset);
ptr.copy_from(src as *const u8, count);
}
}
Реализация ВФС
существуетsqlite3_vfs
изxOpen
Зарегистрируйте соответствующий кастом в реализации методаsqlite3_io_methods
С помощью вышеуказанной работы мы, наконец, скомпилировали sqlite какwasm32-unknown-unknown
Файл wasm в режиме, а верхний слой может напрямую повторно использовать дизель, так что бизнес-код менять не нужно.
На данный момент рабочий режим lark sdk в Интернете:
Общий режим работы снова выровнен с нативной платформой, без внешней зависимости, а копия данных между экземпляром WASM не требуется при запросах.
Присоединяйтесь к нам
-
Feishu, корпоративная платформа для совместной работы под управлением ByteDance, представляет собой универсальную корпоративную платформу для общения и совместной работы, объединяющую видеоконференции, онлайн-документы, мобильный офис и программное обеспечение для совместной работы. В настоящее время бизнес Feishu быстро развивается.Есть центры исследований и разработок в Пекине, Шэньчжэне и других городах.Достаточно HC на фронтенде, мобильных устройствах, ржавчине, сервере, тестировании, продукте и других позициях.Мы с нетерпением ждем вашего присоединения и работать с нами. Пожалуйста, нажмите на ссылку:future.feishu.cn/recruit
-
Мы также приветствуем обмен техническими вопросами со студентами Feishu.Если вы заинтересованы, пожалуйста, нажмитеГруппа технического обмена Feishuгрупповое общение