移植案例与原理 - utils子系统之KV存储部件 (2) 原创 精华
【本文正在参与优质创作者激励】
3、KV存储部件对外接口
在文件utils\native\lite\include\kv_store.h中定义了KV存储部件对外接口,如下,支持从键值对缓存里读取键值,设置键值,删除键值,清除缓存等等。
int UtilsGetValue(const char* key, char* value, unsigned int len);
int UtilsSetValue(const char* key, const char* value);
int UtilsDeleteValue(const char* key);
#ifdef FEATURE_KV_CACHE
int ClearKVCache(void);
#endif
在文件utils\native\lite\kv_store\innerkits\kvstore_env.h中定义了如下接口,在使用POSIX接口时,需要首先使用接口需要设置数据文件路径。使用UtilsFile接口时,不需要该接口。
int UtilsSetEnv(const char* path);
4、KV存储部件对应POSIX接口部分的代码
分析下KV存储部件对应POSIX接口部分的代码。我们知道对外接口有设置键值UtilsSetValue、获取键值UtilsGetValue、删除键值UtilsDeleteValue和清除缓存ClearKVCache。我们先看看内部接口。
4.1 内部接口
4.1.1 GetResolvedPath解析路径
函数GetResolvedPath用于解析文件路径,根据键名key组装存放值value的文件路径。需要4个参数,第一个参数char* dataPath为键值对保存的文件路径,在使用KV特性前由UtilsSetEnv函数设置到全局变量里g_dataPath;第二个参数为键char* key;第三个参数char* resolvedPath为解析后的路径,为输出参数;第4个参数unsigned int len为路径长度。看下代码,⑴处为解析的路径申请内存,⑵处拼装键值对的文件路径,格式为"XXX/kvstore/key"。⑶将相对路径转换成绝对路径,如果解析成功,会把文件路径解析到输出参数resolvedPath。⑷处如果执行realpath函数出错,指定的文件不存在,会执行⑸把keyPath复制到输出函数resolvedPath。
static int GetResolvedPath(const char* dataPath, const char* key, char* resolvedPath, unsigned int len)
{
⑴ char* keyPath = (char *)malloc(MAX_KEY_PATH + 1);
if (keyPath == NULL) {
return EC_FAILURE;
}
⑵ if (sprintf_s(keyPath, MAX_KEY_PATH + 1, "%s/%s/%s", dataPath, KVSTORE_PATH, key) < 0) {
free(keyPath);
return EC_FAILURE;
}
⑶ if (realpath(keyPath, resolvedPath) != NULL) {
free(keyPath);
return EC_SUCCESS;
}
⑷ if (errno == ENOENT) {
⑸ if (strncpy_s(resolvedPath, len, keyPath, strlen(keyPath)) == EOK) {
free(keyPath);
return EC_SUCCESS;
}
}
free(keyPath);
return EC_FAILURE;
}
4.1.2 GetValueByFile从文件中读取键值
函数GetValueByFile从文件中读取键对应的值,需要4个参数,第一个参数为键值文件存放的目录路径;第二个参数为键;第三个为输出参数,存放获取的键的值;第4个参数为输出参数的长度。该函数返回值为EC_FAILURE或成功获取的值的长度。⑴处获取对应键名key的文件路径,⑵处读取文件的状态信息。因为文件内容是键对应的值,⑶处表明如果值的大小大于等于参数len,则返回错误码。等于也不行,需要1个字符长度存放null字符用于结尾。⑷处打开文件,然后读取文件,内容会存入输出参数value里。⑸处设置字符串结尾的null字符。
static int GetValueByFile(const char* dataPath, const char* key, char* value, unsigned int len)
{
char* keyPath = (char *)malloc(PATH_MAX + 1);
if (keyPath == NULL) {
return EC_FAILURE;
}
⑴ if (GetResolvedPath(dataPath, key, keyPath, PATH_MAX + 1) != EC_SUCCESS) {
free(keyPath);
return EC_FAILURE;
}
struct stat info = {0};
⑵ if (stat(keyPath, &info) != F_OK) {
free(keyPath);
return EC_FAILURE;
}
⑶ if (info.st_size >= len) {
free(keyPath);
return EC_FAILURE;
}
⑷ int fd = open(keyPath, O_RDONLY, S_IRUSR);
free(keyPath);
keyPath = NULL;
if (fd < 0) {
return EC_FAILURE;
}
int ret = read(fd, value, info.st_size);
close(fd);
fd = -1;
if (ret < 0) {
return EC_FAILURE;
}
⑸ value[info.st_size] = '\0';
return info.st_size;
}
4.1.3 SetValueToFile\DeleteValueFromFile存入\删除键值
函数SetValueToFile同于把键值存入文件,函数DeleteValueFromFile则用于删除键值。⑴处根据键名获取存放值的文件路径keyPath,⑵处打开文件,然后写入键名对应的值。在函数DeleteValueFromFile中,⑶处先组装路径,然后删除文件。
static int SetValueToFile(const char* dataPath, const char* key, const char* value)
{
char* keyPath = (char *)malloc(PATH_MAX + 1);
if (keyPath == NULL) {
return EC_FAILURE;
}
⑴ if (GetResolvedPath(dataPath, key, keyPath, PATH_MAX + 1) != EC_SUCCESS) {
free(keyPath);
return EC_FAILURE;
}
⑵ int fd = open(keyPath, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
free(keyPath);
keyPath = NULL;
if (fd < 0) {
return EC_FAILURE;
}
int ret = write(fd, value, strlen(value));
close(fd);
fd = -1;
return (ret < 0) ? EC_FAILURE : EC_SUCCESS;
}
static int DeleteValueFromFile(const char* dataPath, const char* key)
{
char* keyPath = (char *)malloc(MAX_KEY_PATH + 1);
if (keyPath == NULL) {
return EC_FAILURE;
}
⑶ if (sprintf_s(keyPath, MAX_KEY_PATH + 1, "%s/%s/%s", dataPath, KVSTORE_PATH, key) < 0) {
free(keyPath);
return EC_FAILURE;
}
int ret = unlink(keyPath);
free(keyPath);
return ret;
}
4.1.4 InitKv创建kvstore目录
函数InitKv确保保存键值时,kvstore目录被创建,用于存放键值文件。⑴处组装kvstore目录,⑵处使用F_OK参数判断目录是否存在,如果存在返回EC_SUCCESS。否则执行⑶创建kvstore目录。
static int InitKv(const char* dataPath)
{
if (dataPath == NULL) {
return EC_FAILURE;
}
char* kvPath = (char *)malloc(MAX_KEY_PATH + 1);
if (kvPath == NULL) {
return EC_FAILURE;
}
⑴ if (sprintf_s(kvPath, MAX_KEY_PATH + 1, "%s/%s", dataPath, KVSTORE_PATH) < 0) {
free(kvPath);
return EC_FAILURE;
}
⑵ if (access(kvPath, F_OK) == F_OK) {
free(kvPath);
return EC_SUCCESS;
}
⑶ if (mkdir(kvPath, S_IRUSR | S_IWUSR | S_IXUSR) != F_OK) {
free(kvPath);
return EC_FAILURE;
}
free(kvPath);
return EC_SUCCESS;
}
4.1.5 GetCurrentItem获取当前的键值对数目
函数GetCurrentItem用于获取当前的键值对数目。首先,组装目录路径"XXX/kvstore",然后执行⑴打开目录,然后读取目录项。⑵循环每一个目录项,判断键值对的数量。⑶处组装kvstore目录下每一个键的文件路径,然后获取每个文件的状态信息。⑷如果文件是常规普通文件,则键值对数量加1。然后读取kvstore目录下的下一个目录项,依次循环。
static int GetCurrentItem(const char* dataPath)
{
char kvPath[MAX_KEY_PATH + 1] = {0};
if (sprintf_s(kvPath, MAX_KEY_PATH + 1, "%s/%s", dataPath, KVSTORE_PATH) < 0) {
return EC_FAILURE;
}
⑴ DIR* fileDir = opendir(kvPath);
if (fileDir == NULL) {
return EC_FAILURE;
}
struct dirent* dir = readdir(fileDir);
int sum = 0;
⑵ while (dir != NULL) {
char fullPath[MAX_KEY_PATH + 1] = {0};
struct stat info = {0};
⑶ if (sprintf_s(fullPath, MAX_KEY_PATH + 1, "%s/%s", kvPath, dir->d_name) < 0) {
closedir(fileDir);
return EC_FAILURE;
}
if (stat(fullPath, &info) != 0) {
closedir(fileDir);
return EC_FAILURE;
}
⑷ if (S_ISREG(info.st_mode)) {
sum++;
}
dir = readdir(fileDir);
}
closedir(fileDir);
return sum;
}
4.1.6 NewItem判断是否新键值对
函数NewItem可以用于判断是否新的键值对。⑴处获取键名对应的文件路径,⑵处判断文件是否存在,存在则返回FALSE;不存在键值对则返回TRUE。
static boolean NewItem(const char* dataPath, const char* key)
{
char* keyPath = (char *)malloc(MAX_KEY_PATH + 1);
if (keyPath == NULL) {
return FALSE;
}
⑴ if (sprintf_s(keyPath, MAX_KEY_PATH + 1, "%s/%s/%s", dataPath, KVSTORE_PATH, key) < 0) {
free(keyPath);
return FALSE;
}
⑵ if (access(keyPath, F_OK) == F_OK) {
free(keyPath);
return FALSE;
}
free(keyPath);
return TRUE;
}
4.2 读取键值UtilsGetValue
函数UtilsSetValue用于读取键名对应的值,第一个参数为输入参数键名,第二个参数为输出参数键名对应的值,第三个参数为值的字符串长度。⑴处获取键值对所在的路径,注意互斥锁的使用。如果支持键值缓存,则执行⑵尝试从缓存中读取。缓存中不能读取时,继续执行⑶从文件中读取。如果读取成功,则执行⑷,加入缓存中,注意第三个参数为FALSE。读取时,会把读取到的键值对,放到缓存的键值对链表的头部,但不删除之前的键值对数据。
int UtilsGetValue(const char* key, char* value, unsigned int len)
{
if (!IsValidKey(key) || (value == NULL) || (len > MAX_GET_VALUE_LEN)) {
return EC_INVALID;
}
pthread_mutex_lock(&g_kvGlobalMutex);
⑴ const char* dataPath = g_dataPath;
if (dataPath == NULL) {
pthread_mutex_unlock(&g_kvGlobalMutex);
return EC_FAILURE;
}
#ifdef FEATURE_KV_CACHE
⑵ if (GetValueByCache(key, value, len) == EC_SUCCESS) {
pthread_mutex_unlock(&g_kvGlobalMutex);
return EC_SUCCESS;
}
#endif
⑶ int ret = GetValueByFile(dataPath, key, value, len);
if (ret < 0) {
pthread_mutex_unlock(&g_kvGlobalMutex);
return EC_FAILURE;
}
#ifdef FEATURE_KV_CACHE
⑷ AddKVCache(key, value, FALSE);
#endif
pthread_mutex_unlock(&g_kvGlobalMutex);
return ret;
}
4.3 设置键值UtilsGetValue
函数UtilsSetValue用于保存一对键值,⑴处确保kvstore目录存在,不存在则创建。⑵处用于获取kvstore目录下键值对的数目。g_getKvSum默认为FALSE,只需要获取一次即可,键值对数目保存在全局变量g_kvSum。⑶处判断是否新的键值对,如果键值对数目超过缓存允许的最大数,并且需要设置的是新的缓存则返回EC_FAILURE。⑷处把键值对保存到文件中,如果支持缓存,还需要存入缓存中。注意AddKVCache存入缓存的第三方参数为TRUE,会先删除之前同一个键名对应的键值对。⑸处如果是新的键值对,键值对数目需要加1。
int UtilsSetValue(const char* key, const char* value)
{
if (!IsValidKey(key) || !IsValidValue(value, MAX_VALUE_LEN)) {
return EC_INVALID;
}
pthread_mutex_lock(&g_kvGlobalMutex);
const char* dataPath = g_dataPath;
⑴ int ret = InitKv(dataPath);
if (ret != EC_SUCCESS) {
g_getKvSum = FALSE;
pthread_mutex_unlock(&g_kvGlobalMutex);
return EC_FAILURE;
}
⑵ if (!g_getKvSum) {
g_kvSum = GetCurrentItem(dataPath);
if (g_kvSum < 0) {
pthread_mutex_unlock(&g_kvGlobalMutex);
return EC_FAILURE;
}
g_getKvSum = TRUE;
}
⑶ boolean newItem = NewItem(dataPath, key);
if ((g_kvSum >= MAX_KV_SUM) && newItem) {
pthread_mutex_unlock(&g_kvGlobalMutex);
return EC_FAILURE;
}
⑷ ret = SetValueToFile(dataPath, key, value);
if (ret == EC_SUCCESS) {
#ifdef FEATURE_KV_CACHE
AddKVCache(key, value, TRUE);
#endif
if (newItem) {
⑸ g_kvSum++;
}
}
pthread_mutex_unlock(&g_kvGlobalMutex);
return ret;
}
4.4 删除键值UtilsDeleteValue
函数UtilsDeleteValue用于删除一对键值。⑴处如果支持键值缓存,则首先尝试从缓存中删除键值对。⑵处从文件中删除键值,如果删除超过,键值对数目减1。
int UtilsDeleteValue(const char* key)
{
if (!IsValidKey(key)) {
return EC_INVALID;
}
pthread_mutex_lock(&g_kvGlobalMutex);
const char* dataPath = g_dataPath;
if (dataPath == NULL) {
pthread_mutex_unlock(&g_kvGlobalMutex);
return EC_FAILURE;
}
#ifdef FEATURE_KV_CACHE
⑴ DeleteKVCache(key);
#endif
⑵ int ret = DeleteValueFromFile(dataPath, key);
if (ret == EC_SUCCESS) {
g_kvSum--;
}
pthread_mutex_unlock(&g_kvGlobalMutex);
return ret;
}
4.5 清除键值缓存ClearKVCache和设置缓存路径UtilsSetEnv
函数ClearKVCache用于清除缓存,直接调用接口ClearKVCacheInner完成。函数UtilsSetEnv用于设置键值对的保存路径,维护在全局变量g_dataPath里。
#ifdef FEATURE_KV_CACHE
int ClearKVCache(void)
{
pthread_mutex_lock(&g_kvGlobalMutex);
int ret = ClearKVCacheInner();
pthread_mutex_unlock(&g_kvGlobalMutex);
return ret;
}
#endif
int UtilsSetEnv(const char* path)
{
if (path == NULL) {
return EC_FAILURE;
}
pthread_mutex_lock(&g_kvGlobalMutex);
int ret = strcpy_s(g_dataPath, MAX_KEY_PATH + 1, path);
pthread_mutex_unlock(&g_kvGlobalMutex);
return (ret != EOK) ? EC_FAILURE : EC_SUCCESS;
}
【本文正在参与优质创作者激励】
看定义这个是存储于缓存之中, 系统重启后是否这些数据就都不在了?
缓存中不存在了,但是文件系统里还有。只从缓存里读取吧?
主要是想了解 这个“缓存”的位置是在内存,还是文件系统。
如果缓存是在内存中,重启之后是会失效的。
如果缓存是在文件系统中,那么重启对数据没有影响。
根据 SetValueToFile\DeleteValueFromFile 看着像是存储在文件系统。