跳转至

fskv - kv数据库,掉电不丢数据

作者:马亚丹

一、概述

fskv 核心库是操作合宙 LuatOS 系统中的键值对(Key-Value)数据库的库,旨在替代旧的 fdb 库,并兼容 fdb 的函数,同时使用 fdb 的 Flash 空间。其作用是在 Flash 存储器中持久化存储键值对数据,允许开发者以键值对的形式存储和检索数据,并且这些数据会被持久化存储在 Flash 存储器上,确保设备断电后数据不会丢失。适用于物联网设备的配置信息、传感器数据等场景应用。

核心特点:

  • 持久化存储:数据写入 Flash,断电后不丢失;
  • 功能丰富:提供 fskv.init()(初始化)、fskv.set(key, value)(存储数据)、fskv.get(key)(读取数据)、fskv.del(key)(删除数据)等 API;
  • 稳定高效:读写速度恒定,不受 “脏数据” 影响,最高 10 万次均衡擦写;
  • 优化数据长度限制:如 value 最大 4096 字节,key 最大 63 字节。

合宙 fskv 核心库原理是在模组片上 flash 单独开辟了一个总可用空间是 64K 的小区域,跑了个小文件系统,单独操作,实现类似于微型数据库的功能,只支持操作芯片自身的 flash 文件系统,不支持操作通过 lf 核心库或者 sfud 核心库挂载的文件系统。

简单说,fskv 核心库就是一个 “嵌入式设备里的小数据库”,专门用来安全、稳定地存放配置或业务数据,断电也不会丢,

和其他掉电数据不丢失的存储区域 otp,imei,sn 有一定区别:

fakv,可以在 luatools 烧录程序时进行清除,轻量快速,适合零散配置,频繁读写效率高,比如存储串口波特率、服务器 IP 等用户的应用数据;

otp,一次性写入不可篡改,安全性高,适合固定关键数据,比如存储射频校准数据、密钥等,具体使用可以参考 otp 核心库

imei,国际移动设备识别码,每个设备全球唯一,永久只读不可修改,是设备入网的 “身份证”,用于蜂窝模组入网、合规认证等需求,具体使用可以参考 mobile 核心库

sn,设备序列号,合宙内部唯一,默认只读,可快速关联生产 / 售后信息,方便设备管理,常用于设备售后报修、生产追溯等,具体使用可以参考 mobile 核心库

注:10 万次均衡擦写是指 Flash 存储的单个单元最多能承受 10 万次 “擦除 - 写入” 循环,再通过均衡擦写技术将数据分散到所有单元,避免个别单元提前损坏,从而延长整体设备寿命,

从原理上来说,依靠均衡擦写技术,设备能持续的稳定使用。

二、核心示例

1、核心示例是指:使用本库文件提供的核心 API,开发的基础业务逻辑的演示代码;

2、核心示例的作用是:帮助开发者快速理解如何使用本库,所以核心示例的逻辑都比较简单;

3、更加完整和详细的 demo,请参考 LuatOS 仓库 中各个产品目录下的 demo/fskv

local function fskv_test()
    local result= fskv.init()
    if result then
        log.info("fskv", "kv数据库初始化成功",result)

        -- 设置数据, 字符串,数值,table,布尔值,均可
        -- 但不可以是nil, function, userdata, task
        fskv.set("my_bool", true)
        fskv.set("my_int", 123)
        fskv.set("my_number", 1.23)
        fskv.set("my_str", "luatos")
        fskv.set("my_table", { name = "wendal", age = 18 })
        log.info("fskv", "获取my_bool", type(fskv.get("my_bool")), fskv.get("my_bool"))
        log.info("fskv", "获取my_int", type(fskv.get("my_int")), fskv.get("my_int"))
        log.info("fskv", "获取my_number", type(fskv.get("my_number")), fskv.get("my_number"))
        log.info("fskv", "获取my_str", type(fskv.get("my_str")), fskv.get("my_str"))
        log.info("fskv", "获取my_table", type(fskv.get("my_table")), json.encode(fskv.get("my_table")))

        if fskv.sett then
            -- 设置数据, 字符串,数值,table,布尔值,均可
            -- 但不可以是nil, function, userdata, task
            log.info("fskv", fskv.sett("mytable", "wendal", "goodgoodstudy"))
            log.info("fskv", fskv.sett("mytable", "upgrade", true))
            log.info("fskv", fskv.sett("mytable", "timer", 1))
            log.info("fskv", fskv.sett("mytable", "bigd", { name = "wendal", age = 123 }))

            -- 下列语句将打印出4个元素的table
            -- I/user.fdb 获取mytable table: 0C7F60A0
            --{"bigd":{"name":"wendal","age":123},"wendal":"goodgoodstudy","timer":1,"upgrade":true}            
            log.info("fskv", "获取mytable", fskv.get("mytable"), json.encode(fskv.get("mytable")))

            -- 如果设置sett的value用户数据为nil, 代表删除对应的skey
            --log打印  I/user.fskv true
            --log打印  I/user.fskv删除 true
            --log打印  I/user.fskv table: 0C7F62F0 {"age":18}
            log.info("fskv",  fskv.set("mytable2", { age = 18, name = "wendal" }))
            log.info("fskv删除", fskv.sett("mytable2", "name",nil))
            log.info("fskv",  fskv.get("mytable2"), json.encode(fskv.get("mytable2")))
        end
        --根据KV迭代器获取key值
        local iter = fskv.iter()
        log.info("kv数据库迭代器", iter)
        if iter then
            while 1 do
                local k = fskv.next(iter)
                log.info("kv迭代器获取下一个key", k)
                if not k then
                    break
                end
                log.info("fskv", k, "value", fskv.get(k))
            end
        end
        --查询kv数据库状态
        local used, total, kv_count = fskv.status()
        log.info("获取kv数据库状态", "fskv", "kv", used, total, kv_count)
        log.info("清空整个kv数据库", fskv.clear())
        local used, total, kv_count = fskv.status()
        log.info("获取kv数据库状态", "fskv", "kv", used, total, kv_count)
    end
end
sys.taskInit(fskv_test)

三、常量详解

核心库常量,顾名思义是由合宙 LuatOS 内核固件中定义的、不可重新赋值或修改的固定值,在脚本代码中不需要声明,可直接调用;

每个常量对应的常量取值仅做日志打印时查询使用,不要将这个常量取值用做具体的业务逻辑判断,因为LuatOS内核固件可能会变更每个常量对应的常量取值;

如果用做具体的业务逻辑判断,一旦常量取值发生改变,业务逻辑就会出错;

fskv 核心库,没有常量。

四、函数详解

4.1 fskv.init()

功能

初始化 kv 数据库

注意事项

暂无

参数

返回值

local result= fskv.init()

有一个返回值 result

result

含义说明:初始化kv数据库
         成功时返回true,否则返回false     
数值类型:boolean
取值范围:true/false
注意事项:暂无
返回示例:例如返回true时表示初始化kv数据库成功

示例

local result= fskv.init()
if result then
    log.info("fskv", "kv数据库初始化成功",result)
end

4.2 fskv.set(key, value)

功能

设置一对 kv 数据

注意事项

value 参数不能设为 nil、 function、 userdata、task 类型;

参数

key

参数含义:键的名称,即kv数据的key的名称
数据类型:string
取值范围:长度1~63字节
是否必选:必须传入此参数;
注意事项:不能是空字符串
参数示例:--如下所示,"wendal"即是设置kv数据时的key的名称
         log.info("fskv", fskv.set("wendal", "goodgoodstudy"))

value

参数含义:用户数据
数据类型:string/number/table/boolean
取值范围:长度1~4095字节
是否必选:必须传入此参数;
注意事项:参数不能设为nil function userdatatask
         可以是字符串/数值/table/布尔值, 数据长度最大4095字节
参数示例:--如下所示,"goodgoodstudy"即是设置kv数据时的用户数据
         log.info("fskv", fskv.set("wendal", "goodgoodstudy"))

返回值

local result= fskv.set(key, value)

有一个返回值 result

result

含义说明:设置一对kv数据
         成功时返回true,否则返回false     
数值类型:boolean
取值范围:true/false
注意事项:暂无
返回示例:例如返回true时表示设置一对kv数据成功

示例

-- 设置用户数据, 字符串、数值、table、布尔值均可
-- 用户数据不可以是nil, function, userdata, task

--如下所示设置用户数据是字符串
log.info("fskv", fskv.set("wendal", "goodgoodstudy"))
--如下所示设置用户数据是布尔值
log.info("fskv", fskv.set("upgrade", true))
--如下所示设置用户数据是数值
log.info("fskv", fskv.set("timer", 1))
--如下所示设置用户数据是table
log.info("fskv", fskv.set("bigd", {name="wendal",age=123}))

4.3 fskv.sett(key, skey, value)

功能

设置 table 内的键值对数据

注意事项

value 参数不能设为 function、 userdata、task 类型;

value 设置为 nil,是删除 skey

参数

key

参数含义:键的名称,即kv数据的key的名称
数据类型:string
取值范围:长度1~63字节
是否必选:必须传入此参数;
注意事项:不能是空字符串
参数示例:--如下所示,"mytable"即是设置table内的键值对数据时的key的名称
         log.info("fskv", fskv.sett("mytable", "wendal", "goodgoodstudy"))

skey

参数含义:table的key名称
数据类型:string
取值范围:长度1~63字节
是否必选:必须传入此参数;
注意事项:不能是空字符串
参数示例:--如下所示,"wendal"即是设置table内的键值对数据时的skey的名称
         log.info("fskv", fskv.sett("mytable", "wendal", "goodgoodstudy"))

value

参数含义:用户数据
数据类型:string/number/table/boolean
取值范围:nil,长度1~4095字节
是否必选:必须传入此参数;
注意事项:参数不能 function userdatatask
         可以是字符串/数值/table/布尔值, 数据长度最大4095字节,
         如果设为nil代表删除对应的skey
参数示例:--如下所示,"goodgoodstudy"即是设置table内的键值对数据时的value
         log.info("fskv", fskv.sett("mytable", "wendal", "goodgoodstudy"))

返回值

local result= fskv.sett(key, skey, value)

有一个返回值 result

result

含义说明:设置table内的键值对数据
         成功时返回true,否则返回false/nil     
数值类型:boolean
取值范围:true/false/nil
注意事项:暂无
返回示例:例如返回true时表示设置table内的键值对数据成功

示例

-- 设置用户数据, 字符串、数值、table、布尔值均可
-- 用户数据不可以是function, userdata, task
-- 用户数据设为nil代表删除对应的key

--如下所示设置用户数据是字符串
log.info("fskv", fskv.sett("mytable1", "wendal", "goodgoodstudy"))
--如下所示设置用户数据是布尔值
log.info("fskv", fskv.sett("mytable1", "upgrade", true))
--如下所示设置用户数据是数值
log.info("fskv", fskv.sett("mytable1", "timer", 1))
--如下所示设置用户数据是table
log.info("fskv", fskv.sett("mytable1", "bigd", {name="wendal",age=123}))
-- 下列语句将打印出上面4个元素的table
--log打印  I/user.fskv table: 0C7F60F8 {"bigd":{"name":"wendal","age":123},"wendal":"goodgoodstudy","timer":1,"upgrade":true}
log.info("fskv", fskv.get("mytable1"), json.encode(fskv.get("mytable1")))

----
-- 注意: 如果key不存在, 或者key已存在但是原本的值不是table类型,再次设置同样key时原来的值将会完全覆盖
-- 例如下列两行的写法,最终获取到的是table 即{age:"123"},而非第一行的字符串"123"
log.info("fskv", fskv.set("mykv1", "123"))
-- 保存的将是 {age:"123"}
log.info("fskv", fskv.sett("mykv1", "age", "123")) 
----

----
-- 如果设置value用户数据为nil, 代表删除对应的skey
--log打印  I/user.fskv true
--log打印  I/user.fskv删除 true
--log打印  I/user.fskv table: 0C7F62F0 {"age":18}
log.info("fskv",  fskv.set("mytable2", { age = 18, name = "wendal" }))
log.info("fskv删除", fskv.sett("mytable2", "name",nil))
log.info("fskv",  fskv.get("mytable2"), json.encode(fskv.get("mytable2")))

--以下写法也是删除对应的skey
--I/user.fskv设置 true
--I/user.fskv 获取wendal table: 0C7F64D8 {"wendal":"goodpeople"}
--I/user.fskv删除 true
--I/user.fskv 获取wendal table: 0C7F6360 {}
log.info("fskv设置", fskv.sett("mytable3","wendal","goodpeople"))
log.info("fskv", "获取wendal", fskv.get("mytable3"), json.encode(fskv.get("mytable3")))
log.info("fskv删除", fskv.sett("mytable3","wendal", nil))
log.info("fskv", "获取wendal", fskv.get("mytable3"), json.encode(fskv.get("mytable3")))

4.4 fskv.get(key, skey)

功能

根据 key 获取对应的数据

注意事项

暂无

参数

key

参数含义:键的名称,即kv数据的key的名称
数据类型:string
取值范围:长度1~63字节
是否必选:必须传入此参数;
注意事项:不能是空字符串
参数示例:--如下所示,fskv.get("wendal")中的参数"wendal"即是获取数据时的key的名称
        --log打印  I/user.fskv goodgoodstudy
        log.info("fskv", fskv.set("wendal", "goodgoodstudy"))     
        log.info("fskv", fskv.get("wendal"))

skey

参数含义:次级key,仅当原始值为table时有效,相当于fskv.get(key)[skey]
数据类型:string
取值范围:长度1~63字节
是否必选:非必须传入此参数;
注意事项:暂无
参数示例:--如下所示,fskv.get("mytable", "wendal")中的参数"wendal"即是获取数据时的            key的名称,
        --log打印  I/user.fskv goodgoodstudy
        log.info("fskv", , fskv.sett("mytable", "wendal", "goodgoodstudy"))
        log.info("fskv",  fskv.get("mytable", "wendal"))

返回值

不带 skey 参数的写法

local v= fskv.get(key)

有一个返回值 v

v

含义说明:与key对应的数据
         数据存在则返回数据,否则返回nil   
数值类型:string/number/table/boolean
取值范围:与sett()set()设置的value一致
注意事项:暂无
返回示例:例如返回 "goodgoodstudy"时表示获取数据成功

带 skey 参数的写法

local v= fskv.get(key, skey)

有一个返回值 v

v

含义说明:与skey对应的数据
         数据存在则返回数据,否则返回nil   
数值类型:string/number/table/boolean
取值范围:与sett()set()设置的value一致
注意事项:暂无
返回示例:例如返回 "goodgoodstudy"时表示获取数据成功

示例

-- 例如下列写法,最终获取到的是数据"123"
log.info("fskv", fskv.set("mykv", "123"))
log.info("fskv", fskv.get("mykv"))


log.info("fskv", fskv.set("bigd", {name="wendal",age=123}))
-- 例如下列写法,最终获取到的是table: 0C7F6798  
log.info("fskv",  fskv.get("bigd"))
-- 例如下列写法,最终获取到的是wendal   
log.info("fskv",  fskv.get("bigd","name"))

-- 如果需要在获取不到value数据时返回一个默认值, 可以参考如下写法,
-- nil是skey="wendal"时的value,此处get会返回nil,最终获取到的v是"123",
-- "123"即是在get返回nil时,v获取到的默认值,可按需修改
fskv.sett("mytable","wendal", nil)
local v1=fskv.get("mytable","wendal")or"123"

4.5 fskv.del(key)

功能

根据 key 删除数据

注意事项

如果是 set()接口设置的 KV 数据,是删除一对 KV 数据;

如果是 sett()接口设置的 table 内的键值对数据,是删除 key 和对应的 table,如需删除 table 内的 skey 和 value,可以使用 sett(key,skey,nil)

参数

key

参数含义:键的名称,即kv数据的key的名称
数据类型:string
取值范围:长度1~63字节
是否必选:必须传入此参数;
注意事项:不能是空字符串
参数示例:--如下所示,"wendal"即是删除kv数据时的key的名称
         log.info("fskv", fskv.del("wendal"))

返回值

local result= fskv.del(key)

有一个返回值 result

result

含义说明:删除一对kv数据
         成功时返回true,否则返回false     
数值类型:boolean
取值范围:true/false
注意事项:暂无
返回示例:例如返回true时表示删除一对kv数据成功

示例

log.info("fskv", fskv.set("wendal", "goodgoodstudy"))
--如下写法,删除上述设置的kv数据,返回true,表示删除成功
log.info("fskv", fskv.del("wendal"))

4.6 fskv.clear()

功能

清空整个 kv 数据库

注意事项

暂无

参数

返回值

local result= fskv.clear()

有一个返回值 result

result

含义说明:清空整个kv数据库
         成功时返回true,否则返回false     
数值类型:boolean
取值范围:true/false
注意事项:暂无
返回示例:例如返回true时表示清空kv数据库成功

示例

local result= fskv.clear()
if result then
    log.info("fskv", "kv数据库清空成功",result)
end

4.7 fskv.iter()

功能

kv 数据库迭代器

注意事项

该接口作用是创建一个 “迭代器指针”(可以理解为 “遍历的起始游标”)。

再通过 fskv.next(iter) 配合这个指针,就能逐个获取 KV 数据库中的键(当返回 nil 时,表示遍历完所有键)。

这样你就可以通过 “迭代器 + next 方法” 的组合,遍历整个 KV 数据库的所有键值对(拿到键后,再通过 fskv.get 等方法取对应值),而不需要关心 KV 数据库内部是如何存储的。

禁止一边遍历一边做其他动作,比如一边遍历一边删除,建议是先记录待删除的键,遍历结束后统一删除,确保遍历的完整性和确定性。

参数

返回值

local iter = fskv.iter()

有一个返回值 iter

iter

含义说明:kv数据库迭代器
         成功返回迭代器指针,否则返回nil   
数值类型:userdata
取值范围:无特别限制
注意事项:暂无
返回示例:例如返回userdata: 0C7F5158时表示获取kv数据库迭代器成功

示例

local iter = fskv.iter()
log.info("kv数据库迭代器", iter)

4.8 fskv.next(iter)

功能

根据 kv 迭代器指针获取下一个 key

注意事项

禁止一边遍历一边做其他动作,比如一边遍历一边删除,建议是先记录待删除的键,遍历结束后统一删除,确保遍历的完整性和确定性。

参数

iter

参数含义:kv迭代器fskv.iter()返回的指针
数据类型:userdata
取值范围:无特别限制
是否必选:必须传入此参数;
注意事项:暂无
参数示例:--如下所示,"iter"即是获取下一个key时的kv迭代器指针
         local k = fskv.next(iter)

返回值

local k = fskv.next(iter)

有一个返回值 k

k

含义说明:根据kv迭代器指针获取的下一个key
         成功返回字符串key值, 否则返回nil  
数值类型:string
取值范围:1~63字节
注意事项:暂无
返回示例:例如下方示例按照4.2fskv.set(key, value)的示例设置了4kv数据
         返回"upgrade"key时表示获取到下一个key成功

示例

-- 设置用户数据, 字符串、数值、table、布尔值均可
-- 用户数据不可以是nil, function, userdata, task

--如下所示设置用户数据是字符串
log.info("fskv", fskv.set("wendal", "goodgoodstudy"))
--如下所示设置用户数据是布尔值
log.info("fskv", fskv.set("upgrade", true))
--如下所示设置用户数据是数值
log.info("fskv", fskv.set("timer", 1))
--如下所示设置用户数据是table
log.info("fskv", fskv.set("bigd", {name="wendal",age=123}))
local iter = fskv.iter()
    log.info("kv数据库迭代器", iter)
    if iter then
        while 1 do
            local k = fskv.next(iter)
            --log打印   kv迭代器获取下一个key  upgrade
            log.info("kv迭代器获取下一个key", k)
            if not k then
                break
            end
         log.info("fskv", k, "value", fskv.get(k))
     end
end

4.9 fskv.status()

功能

获取 kv 数据库状态

注意事项

暂无

参数

返回值

local used, total,kv_count = fskv.status()

有三个返回值 used, total,kv_count

used

含义说明:已使用的空间,单位字节;
         成功时返回已使用空间大小    
数值类型:number
取值范围:0~65536
注意事项:暂无
返回示例:例如返回8192时表示已使用8192字节的空间

total

含义说明:总可用空间, 单位字节;
         成功时返回总可用空间大小    
数值类型:number
取值范围:65536
注意事项:支持LuatOS的合宙模组总可用fskv空间是64K
返回示例:例如返回65536时表示总可用空间是65536字节

kv_count

含义说明:总kv键值对数量, 单位个;
         成功时返回总kv键值对数量    
数值类型:number
取值范围:0~800
注意事项:fskv键值对数量极限在800个, value值只有一个字节,
         与键和值的长度都有关系, 实际能存下来10~16k左右的value数据, 
         单个key最多存4k数据
         key数量越多, value能存的就越少, 反之也是.
返回示例:例如返回8时表示已存在8KV数据

示例

-- log打印  I/user.获取kv数据库状态 fskv kv 8192 65536 10
local used, total, kv_count = fskv.status()
log.info("获取kv数据库状态", "fskv", "kv", used, total, kv_count)

五、产品支持说明

支持 LuatOS 开发的所有产品都支持 fskv 核心库。