尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Deno KV 存储引擎解析:ext/kv 的 Storage 后端、KV Connect 协议与 op 层实现

Deno KV 存储引擎解析:ext/kv 的 Storage 后端、KV Connect 协议与 op 层实现 Deno KV 存储引擎解析ext/kv 的 Storage 后端、KV Connect 协议与 op 层实现【免费下载链接】denoA modern runtime for JavaScript and TypeScript.项目地址: https://gitcode.com/GitHub_Trending/de/deno本文以 Deno 仓库中的 ext/kv/README.md 为骨架系统讲解 Deno KVdeno_kvcrate自述为 Implementation of the Deno database API的实现原理它以Databasetrait 为可插拔存储接口内置 SQLite 本地后端与实现 KV Connect 协议的远程后端并通过一组op_kv_*op 把后端能力暴露给 JS 层的Deno.openKvAPI。读完本文你将理解 KV 的三种打开路径默认本地库、kv.sqlite3文件、:memory:/远程 URL各自落在哪段 Rust 代码上以及各类配额限制、游标编码、原子写校验是如何在运行时被强制执行的。一、crate 定位与文档骨架ext/kv/README.md 明确了三件事本文按同一脉络展开deno_kv是 Deno 的键值存储 crate。README 指出 Deno KV 的用户手册在 Deno 官方文档Deploy KV Manual中本 crate 是其运行时实现可插拔的存储接口Storage BackendsREADME 列出了两种官方后端——SQLite本地开发默认实现位于独立仓库denokv的denokv_sqlitecrate与 Remote对接实现 KV Connect 协议的远程服务例如 Deno Deploy并说明额外后端可通过实现Databasetrait 添加KV Connect 协议它定义了 Deno CLI 与远程 KV 数据库的通信方式协议规范与 protobuf 定义proto/kv-connect.md、proto/schema/datapath.proto位于独立仓库denokv的proto目录下。从源码结构看这三点在 ext/kv/Cargo.toml 中一一对应crate 直接依赖denokv_proto协议/类型定义、denokv_sqliteSQLite 后端、denokv_remoteKV Connect 客户端以及rusqlite用于 SQLite 连接管理。二、可插拔后端DatabaseHandler 与按前缀路由通过实现Databasetrait 添加新后端这句话在源码中的落点是 ext/kv/interface.rs#[async_trait(?Send)] pub trait DatabaseHandler { type DB: Database static; async fn open( self, state: RcRefCellOpState, path: OptionString, ) - ResultSelf::DB, JsErrorBox; }每个后端只需提供一个DatabaseHandler实现type DB是denokv_proto::Database的具体实现。ext/kv/dynamic.rs 进一步把它抹平成对象安全的DynamicDbHandler/RcDynamicDb动态分发层DynamicDbtrait 暴露dyn_snapshot_read、dyn_atomic_write、dyn_dequeue_next_message、dyn_watch、dyn_close五个能力使 op 层不必关心底层是 SQLite 还是远程服务。真正体现可插拔的是MultiBackendDbHandlerext/kv/dynamic.rsMultiBackendDbHandler::remote_or_sqlite(...)注册了两组前缀[https://, http://]映射到RemoteDbHandler空前缀[]兜底映射到SqliteDbHandler。因此Deno.openKv(https://...)走远程Deno.openKv(my.sqlite3)或无参调用走本地 SQLiteopen前还会处理环境变量DENO_KV_DEFAULT_PATH无参时作为默认库路径、DENO_KV_PATH_PREFIX给库路径加前缀便于多租户隔离DENO_KV_REQUIRES_DISTRIBUTED_DATABASE面向 Deno Deploy 场景值为error时若路径不是http(s)://分布式的库openKv直接报错提示未附加 KV 数据库值为warn时仅打印一次警告并回退到内存库。从这段逻辑可以推断Deno Deploy 在运行时会借此防止应用看似成功、实则落到了本地内存的静默降级没有任何前缀匹配成功时返回TypeError: No backend supports the given path。三、SQLite 后端路径解析、:memory:、WAL 与跨进程 watchext/kv/sqlite.rs 中的SqliteDbHandler是本地默认后端核心逻辑包括路径校验validate_path未传路径返回None交由后端决定落盘位置路径为:memory:时显式打开内存库空字符串报TypeError: Filename cannot be empty以:开头的文件名被拒绝提示需加./前缀这是为了与 SQLite URI 语法隔离普通文件路径会经过权限系统permissions.check_open(..., OpenAccessKind::ReadWriteNoFollow, Some(Deno.openKv))即 KV 的本地读写受--allow-read/--allow-write体系约束且不允许跟随符号链接。存储模式与默认库路径spawn_blocking中根据环境变量DENO_KV_DB_MODEdisk/空串为磁盘模式memory为内存模式未知值告警并回退磁盘决定模式落盘时有显式路径 →rusqlite::Connection::open_with_flags(path, flags)并先 canonicalize 用于 watch 通知去重无路径但 handler 配置了default_storage_dir→ 在该目录创建并打开kv.sqlite3。这解释了本地不传参的Deno.openKv()数据存哪了——default_storage_dir指向 Deno 缓存目录下的存储目录库文件名固定为kv.sqlite3其余情况全部open_in_memory。WAL 与 watch 通知每个连接建立后执行PRAGMA journal_mode wal随后以SqliteConfig { batch_timeout: None, num_workers: 1 }构造denokv_sqlite::Sqlite。值得注意的细节是SQLITE_NOTIFIERS_MAPext/kv/sqlite.rs 顶部一个以规范化路径为键的全局OnceLockMutexHashMap_, SqliteNotifier让同一进程内打开同一个文件的多个Kv实例共享同一通知器——这正是本地db.watch()能跨实例看到变更的原因内存库则各自使用独立的SqliteNotifier::default()互不感知。此外versionstamp_rng_seed允许测试注入随机种子使本地库生成的 versionstamp 可复现见SqliteDbHandler::new。四、Remote 后端KV Connect 协议与打开时校验ext/kv/remote.rs 实现了 README 中Remote - backed by a remote service that implements the KV Connect protocol这一后端RemoteDbHandler::open的流程是理解 KV Connect 在客户端侧行为的关键参数与权限远程库必须提供 URL缺失报Missing database url无法解析报Invalid database url随后依次做check_env(DENO_KV_ACCESS_TOKEN)与check_net_url即需要--allow-envDENO_KV_ACCESS_TOKEN和对应域名的网络权限访问令牌从环境变量DENO_KV_ACCESS_TOKEN读取缺失时报错并提示在 Deno 控制台获取令牌源码注释直接写明该 env var 名HTTP 客户端通过deno_fetch::create_http_client构造一个强制 HTTP/2http1: false, http2: true的客户端支持自定义 root CA、代理、客户端证书并将其包装为RemoteTransportFetchClient仅POST与RemoteResponse支持bytes()/stream()/text()两种 trait 实现交给denokv_remote::Remote打开时校验fail fastvalidate_metadata_endpoint会立刻向数据库 URL 发送一次 metadata 交换请求body 为MetadataExchangeRequest { supported_versions }约束包括METADATA_VALIDATION_TIMEOUT 30s超时即openKv报timed out connecting to the metadata endpoint仅接受200 OK与denokv_remote保持一致避免204等 2xx 落到难以理解的解析错误先单独解析响应里的version字段SUPPORTED_PROTOCOL_VERSIONS: [u64; 3] [1, 2, 3]之外的版本报unsupported KV Connect metadata version然后再解析完整的DatabaseMetadata源码中的 TODO 注释对应上游 issue #22248说明该校验会让一次成功的openKv实际 POST 两次 metadata 端点此处一次、Remote::new的 refresher 一次且协议版本常量目前是从denokv_remote复制的未来存在漂移风险。这段代码同时回答了 README 中KV Connect 规范与 protobuf 定义在 denokv 仓库proto目录的客户端侧意义Deno CLI 与任何实现了该协议的服务如 Deno Deploy都以 metadata 交换握手、以统一的版本集合协商兼容性。五、op 层与 JS 层Deno.openKv 的完整调用链ext/kv/lib.rs 顶部用deno_core::extension!注册了整个扩展state中注入KvConfig与Rcdyn DynamicDbHandler并声明 8 个 op 与懒加载 JSdeno_core::extension!(deno_kv, deps [ deno_web ], ops [ op_kv_database_open, op_kv_snapshot_read, op_kv_atomic_write, op_kv_encode_cursor, op_kv_dequeue_next_message, op_kv_finish_dequeued_message, op_kv_watch, op_kv_watch_next, ], lazy_loaded_js [ 01_db.ts ], options { handler: Boxdyn DynamicDbHandler, config: KvConfig, }, ... );UNSTABLE_FEATURE_NAME kvop_kv_database_open第一步即feature_checker.check_or_exit(kv, Deno.openKv)。也就是说KV API 当前是不稳定特性运行时必须显式启用--unstable-kv或配置中unstable: [kv]否则Deno.openKv直接退出——使用本 API 的适用前提就写在这里。打开数据库op_kv_database_open(path: OptionString)取出DynamicDbHandler调用dyn_open成功则把DatabaseResource { db, cancel_handle }放进资源表返回rid。资源关闭时调用db.close()并取消相关任务——Kv实例被 GC 时底层连接随之释放。键与值的编解码键在 Rust 侧表示为VecAnyValueKeyPart支持 bool/float/Int(BigInt)/String/Bytes经denokv_proto::encode_key/decode_key序列化为字节后传给后端返回值V8 序列化、字节、U64 三种见FromV8Value/ToV8Value与 versionstamphex 字符串check 时按 20 字节校验同样在此完成 V8 ↔ proto 的转换。读op_kv_snapshot_read接收一组(prefix, start, end, limit, reverse, cursor)六元组先做配额检查max_read_ranges、total limit ≤ max_read_entries、读键大小再由RawSelector::from_tuple归一化选择器(prefix, -, -)→ 前缀扫描(prefix, start, -)要求start以 prefix 开头且更长否则StartKeyNotInKeyspace(prefix, -, end)同理校验EndKeyNotInKeyspace(-, start, end)要求start end(-, start, -)展开为精确键区间start..start||0x00其余组合报InvalidRange。游标分页由encode_cursor/decode_selector_and_cursor完成游标是边界键去掉选择器公共前缀后的 Base64URL 串带游标续读时正向读起点为prefixcursor0x00避免重复读到边界键反向读终点为prefixcursor并有CursorOutOfBounds越界防护。op_kv_encode_cursor则把这套编码暴露给 JS 层手工构造游标。原子写op_kv_atomic_write一次接收 checks、mutations、enqueues 三类请求并整体执行配额checks ≤ max_checksmutations enqueues ≤ max_mutations版本校验check 的 versionstamp 必须是 20 字节 hexInvalidVersionstamp变异类型set/delete/sum/min/max/setSuffixVersionstampedKey带值却给了 delete或delete 却给了值都会报InvalidMutationWithValue/WithoutValueexpireIn毫秒在当前时间戳上做 checked 加法换算为绝对过期时间溢出报InvalidExpireAt而不是让进程 panic源码注释明确这是防溢出加固体积校验单个键不超过max_write_key_size_bytes单个值或 enqueue payload不超过max_value_size_bytes总 payload 不超过max_total_mutation_size_bytes、总键长不超过max_total_key_size_bytesU64 值按固定 8 字节计成功后返回提交产生的 versionstamphex即KvAtomicWriteResult.versionstamp。队列与 watchop_kv_dequeue_next_message取出队首消息并注册QueueMessageResourcehandle_ridop_kv_finish_dequeued_message(handle_rid, success)决定确认还是重试finish 失败仅打 debug 日志因为消息反正会被重试。op_kv_watch把键编码后交给后端db.watch(keys)得到WatchStream注册为DatabaseWatcherResourceop_kv_watch_next逐批拉取输出区分Changed(entry?)entry 为null表示键被删除与Unchanged心跳。max_watched_keys在此强制执行。JS 层约束ext/kv/01_db.ts 在调用 op 之前还有一道校验例如队列delay不得为负、不得大于 30 天maxQueueDelay 30 * 24 * 60 * 60 * 1000、expireIn必须是非负整数注释说明这是防止 NaN/Infinity 进入 native 层时溢出 panic、backoffSchedule最多 5 段且每段不超过 1 小时。openKv(path)最终就是op_kv_database_open后包成Kv对象。六、配额配置KvConfig 与默认值ext/kv/config.rs 的KvConfigBuilder::build给出了所有限制的默认值这是嵌入者如 Deno CLI 自身调整 KV 行为的主要入口配置项含义默认值max_write_key_size_bytes写入键编码后大小上限2048max_read_key_size_bytes读取键大小上限写键上限 1注释范围选择器可能带 0x00/0xff 后缀max_value_size_bytes单值 / enqueue payload 上限6553664 KiBmax_read_ranges单次快照读的范围数10max_read_entries单次快照读的总 limit 之和1000max_checks单次原子写的 check 数100max_mutations单次原子写的 mutationenqueue 总数1000max_watched_keys单个 watch 的键数10max_total_mutation_size_bytes单次原子写总 payload800 * 1024max_total_key_size_bytes单次原子写总键长80 * 1024超限会分别抛出TooManyRanges、TooManyEntries、TooManyChecks、TooManyMutations、KeyTooLargeToRead/Write、ValueTooLarge、TotalMutationTooLarge、TotalKeyTooLarge等带具体上限值的 JS TypeError定义在 ext/kv/lib.rs 的KvErrorKind方便上层按错误类型与限额做客户端节流。七、如何阅读与扩展这一模块从 ext/kv/README.md 出发先读 ext/kv/lib.rs 的extension!声明即可按 8 个 op 名逐一对照 ext/kv/01_db.ts 中Kv类的方法实现建立 API → op → trait 分发的完整链路想理解本地存储行为:memory:、DENO_KV_DB_MODE、kv.sqlite3、watch 通知器聚焦 ext/kv/sqlite.rs想理解远程接入与协议版本协商DENO_KV_ACCESS_TOKEN、30 秒超时、版本 1/2/3聚焦 ext/kv/remote.rs 的validate_metadata_endpoint想接入手写后端实现 ext/kv/interface.rs 的DatabaseHandlertype DB: denokv_proto::Database再经MultiBackendDbHandler按前缀注册即可被Deno.openKv透明使用配额与默认值的调整点集中在 ext/kv/config.rs所有max_*均可通过 builder 覆盖。需要说明的适用前提Deno.openKv依赖不稳定特性开关--unstable-kv或等价配置远程库依赖DENO_KV_ACCESS_TOKEN环境变量与网络/环境权限本地 SQLite 路径受文件权限约束且不支持以:开头的文件名。【免费下载链接】denoA modern runtime for JavaScript and TypeScript.项目地址: https://gitcode.com/GitHub_Trending/de/deno创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表