一文带你了解 OpenNJet KV Store 及实现

By 洪昕 2023-12-27

一文带你了解 OpenNJet KV Store 及实现

NGINX 向云原生演进,All inOpenNJet


1. 特性介绍

OpenNJet 实现了可持久化的键值存储(key-value store)功能,并提供了相应的 API 对键值进行操作。基于 KV Store 提供的能力,可以用于如下场景:

  • 模块动态配置信息的持久化
  • OpenNJet 不同模块间运行状态信息的共享
  • 外部系统对 NJet 运行模块的动态参数调整

例如 OpenNJet 目前提供的动态 Worker 数目调整功能,就是通过设置特定的 KEY 值,以此来触发 Master 进程对运行 Worker 进程的管理。

2. 实现方案

开源社区已经有大量的中间件,如 Redis, Etcd,这些中间件都可以用来做键值存储的数据库, 但这样不仅需要 OpenNJet 中有相应模块与其进行交互,并且部署时增加了外部组件的依赖。

OpenNJet KV Store 的实现上,使用了轻量级的内存数据库 LMDB。LMDB 是基于内存映射的,效率高,并且访问简单,不需要单独的数据管理进程,只要在访问代码里引用 LMDB 库。LMDB 的文件结构简单,包含一个数据文件和一个锁文件,通过锁控制来实现事务隔离,LMDB 文件可以同时由多个进程打开。使用该方案,整体架构简单清晰。

img

OpenNJet 与其它负载均衡产品 KV Store 功能的比较:

OpenNJet 与其它负载均衡产品 KV Store 功能的比较:

OpenNJet nginx OSS nginx Plus APISIX
是否支持 支持 无内置 KV Store 支持 支持
实现方式 LMDB / 共享内存 Etcd
使用方式 Restful, Lua, C API / Restful, Njs Restful, Lua
是否收费 开源免费 / 付费使用 开源免费

3. 使用说明

3.1 C API

使用 C 开发 OpenNJet 的扩展模块时,可以使用 OpenNJet 提供的 kv 能力,对 kvstore 进行操作。

以数据面的模块为例,模块中需要包含 OpenNJet 源码中的头文件:

#include <njt_http_kv_module.h>

该头文件中 kvstore 相关的函数原型声明如下:

int njt_db_kv_get(njt_str_t *key, njt_str_t *value);
int njt_db_kv_set(njt_str_t *key, njt_str_t *value);
int njt_db_kv_del(njt_str_t *key);

调用样例代码如下:

static int kv_get_example()
{
    njt_str_t lmdb_key = njt_string("test_key");
    njt_str_t lmdb_value;
    njt_int_t ok;
    lmdb_value.len=0;
    lmdb_value.data=NULL;
    ok = njt_db_kv_get(&lmdb_key, &lmdb_value);
    if (ok == NJT_OK)
    {
       ...
    }

    return NJT_OK;
}

3.2 Lua 方式

OpenNJet 提供了封装后的 Lua kv store API,在 Lua 中引用 “njt.kv” Lua 库, 之后使用 Lua 函数对 kv store 进行操作,函数包括:db_kv_get, db_kv_set, db_kv_del。

测试代码如下:

location /lua_kv_test {
           content_by_lua_block {
              local kv = require("njt.kv")
              local args, err = njt.req.get_uri_args()
              local key = args["key"]
              local rc,msg = kv.db_kv_get(key)
              if rc == 0 then
                 njt.say("old value is: "..msg)
              else
                 njt.say("there is no such key in kv")
              end

              local newValue = key .."_"..tostring(os.time())
              rc = kv.db_kv_set(key, newValue)
              if rc == 0 then
                 njt.say("set to new value: "..newValue)
              else
               njt.say("error occuried")
              end
           }
         }

        location /lua_kv_del {
          content_by_lua_block {
             local kv=require("njt.kv")
             local args, err= njt.req.get_uri_args()
             local key=args["key"]     
             local _, msg= kv.db_kv_del(key)
             njt.say(msg)
          }
     } 

img

3.3 Restful 方式

使用官网提供的 RPM 包安装后,生成的控制面 njet_ctrl 配置中,已包含一个可以用来设置 kv 值的 location

 location /kv {
            dyn_sendmsg_kv;
        }

可以使用 Restful 的方式,GET,POST, DELETE 来对键值进行操作。

img

img

使用控制面提供的 Restful 接口在设置键值时,将在 key 前添加 “kv_http_” 的前缀。如果使用 C 或 Lua API,与 Restful 接口设置的键值需要互操作的场景下, 在 C 或 Lua API 调用对应函数时,需要自行添加 “kv_http_” 的前缀。