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、 userdata、task;
可以是字符串/数值/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、 userdata、task;
可以是字符串/数值/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)的示例设置了4对kv数据,
返回"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时表示已存在8对KV数据
示例
-- 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 核心库。