我在Redis上进行了快速文件访问的测试,使用了Google Colab来尝试
我在Google Colab上尝试了使用Redis进行快速文件访问。
在Redis中,我无法找到直接读取现有文件的功能。(可能是我见识短浅,如果您知道的话请告诉我。)因此,我创建了一个模块,通过mmap将包含double值的文件映射到内存中,并从指定位置读取和写入值。我在代码中提供了所有内容,您只需从左上角的按钮打开Colab并按顺序执行单元格即可进行测试。也请将其作为制作Redis模块的参考方式。
如果安装了这个模块,就能在Redis中使用以下命令。
// file_pathにあるファイルをkeyに結びつける
MMAP key file_path
// keyからindex位置にある値を取得する
VGET key index
// keyから複数のindex位置にある値を取得する
VMGET key index [index ...]
// keyのindex位置に値を書き込む
VSET key index value [index value ...]
// keyにvalueを追加する
VADD key value [value ...]
// keyの最後の値を取得して削除する
VPOP key
// keyの値の数を取得する
VSIZE key
// keyの内容を消去する
VCLEAR key
首先从Redis源代码中获取,然后移动到模块的目录中。
%cd /content
!wget https://download.redis.io/redis-stable.tar.gz
!tar -xzf redis-stable.tar.gz
%cd /content/redis-stable/src/modules
让我们试着编译一下这个样例代码吧。
!make
我会确认模块的.so文件是否已创建。
!ls *.so
helloacl.so hellocluster.so hellohook.so hellotype.so
helloblock.so hellodict.so hellotimer.so helloworld.so
我們現在來寫fmmap.c的代碼。讀寫的值是double型的。為了讓代碼更易讀,錯誤處理被省略了。請注意,如果進行測試,請不要輸入惡意指令?
此外,我們在這裡公開的程式碼中,可以選擇要讀寫的值的類型,並且還具備完整的錯誤處理。
首先需要包含必要的头文件。同时也定义一些方便的宏。
%%writefile fmmap.c
#pragma GCC diagnostic ignored "-Wunused-parameter"
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdint.h>
#include <strings.h>
#include <string.h>
#include "../redismodule.h"
#include "../sds.h"
#include "../zmalloc.h"
// (RedisModuleString *)と(char *)を比較するマクロ
static inline int mstringcmp(const RedisModuleString *rs1, const char *s2)
{
return strcasecmp(RedisModule_StringPtrLen(rs1, NULL), s2);
}
int ftruncate(int fildes, off_t length); // unistd.hにあるはずだがwarningが出るので
定义MMapObject。填入了mmap所需的信息。sds是Redis内部使用的字符串类型。
%%writefile -a fmmap.c
typedef struct _MMapObject
{
sds file_path;
int fd;
void *mmap;
size_t file_size;
} MMapObject;
这是一个变量,它持有MMap类型,并且这是一个生成MMapObject的函数。
%%writefile -a fmmap.c
RedisModuleType *MMapType = NULL; // MMap型を保持する変数
// MMapObjectの生成
MMapObject *MCreateObject(void)
{
return (MMapObject *)zcalloc(sizeof(MMapObject));
}
这个函数用于释放 MMapObject 对象。
%%writefile -a fmmap.c
// MMapObjectの解放
void MFree(void *value)
{
if (value == NULL) return;
const MMapObject *obj_ptr = value;
if (obj_ptr->mmap != NULL) munmap(obj_ptr->mmap, obj_ptr->file_size);
if (obj_ptr->fd != -1) close(obj_ptr->fd);
sdsfree(obj_ptr->file_path);
zfree(value);
}
这是一个将以file_path指定的文件映射为键的函数。
%%writefile -a fmmap.c
// MMAP key file_path
int MMap_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
RedisModule_AutoMemory(ctx); /* Use automatic memory management. */
if (argc != 3) return RedisModule_WrongArity(ctx);
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ | REDISMODULE_WRITE);
// keyの型を確認する
int type = RedisModule_KeyType(key);
if (type != REDISMODULE_KEYTYPE_EMPTY &&
RedisModule_ModuleTypeGetType(key) != MMapType) {
return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
}
// keyが空なら新たに作成してmmapする
MMapObject *obj_ptr;
if (type == REDISMODULE_KEYTYPE_EMPTY) {
obj_ptr = MCreateObject();
obj_ptr->file_path = sdsnew(RedisModule_StringPtrLen(argv[2], NULL));
obj_ptr->fd = open(obj_ptr->file_path, O_RDWR | O_CREAT, 0666);
if (obj_ptr->fd == -1) {
MFree(obj_ptr);
return RedisModule_ReplyWithError(ctx, sdsnew(RedisModule_StringPtrLen(argv[2], NULL)));
}
struct stat sb;
fstat(obj_ptr->fd, &sb);
obj_ptr->file_size = sb.st_size;
obj_ptr->mmap = mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, obj_ptr->fd, 0);
RedisModule_ModuleTypeSetValue(key, MMapType, obj_ptr);
}
// 既存のkeyがある場合は同一性を確認する
else {
obj_ptr = RedisModule_ModuleTypeGetValue(key);
if (obj_ptr == NULL) {
RedisModule_ReplyWithNull(ctx);
return REDISMODULE_ERR;
}
// 既存のファイルと異なる場合はエラー
if (strcmp(obj_ptr->file_path, RedisModule_StringPtrLen(argv[2], NULL)) != 0) {
return RedisModule_ReplyWithError(ctx, "It is already mapped on another file.");
}
}
return RedisModule_ReplyWithLongLong(ctx, obj_ptr->file_size / sizeof(double));
}
这是一个用于获取指定位置值的函数,在索引中指定位置的值。
%%writefile -a fmmap.c
// VGET key index
int VGet_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
RedisModule_AutoMemory(ctx); /* Use automatic memory management. */
RedisModuleKey *key =
RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ | REDISMODULE_WRITE);
long long index;
RedisModule_StringToLongLong(argv[2], &index);
MMapObject *obj_ptr = RedisModule_ModuleTypeGetValue(key);
// indexが範囲外ならNullを返す
if (obj_ptr->file_size < (size_t)index * sizeof(double) || index < 0) {
RedisModule_ReplyWithNull(ctx);
}
// mmap[index]を返す
else {
RedisModule_ReplyWithDouble(ctx, ((double*)obj_ptr->mmap)[index]);
}
return REDISMODULE_OK;
}
这是一个函数,用多个索引来获取指定位置的值。
%%writefile -a fmmap.c
// VMGET key index [index ...]
int VMGet_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
RedisModule_AutoMemory(ctx); /* Use automatic memory management. */
RedisModuleKey *key =
RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ | REDISMODULE_WRITE);
MMapObject *obj_ptr = RedisModule_ModuleTypeGetValue(key);
RedisModule_ReplyWithArray(ctx, argc - 2);
for (int i = 2; i < argc; i++) {
long long index;
RedisModule_StringToLongLong(argv[i], &index);
// indexが範囲外ならNullを返す
if (obj_ptr->file_size < (size_t)index * sizeof(double) || index < 0) {
RedisModule_ReplyWithNull(ctx);
}
// mmap[index]を返す
else {
RedisModule_ReplyWithDouble(ctx, ((double*)obj_ptr->mmap)[index]);
}
}
return REDISMODULE_OK;
}
这是一个将值写入索引位置的函数, 其索引由索引表示。
%%writefile -a fmmap.c
// VSET key index value [index value ...]
int VSet_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
RedisModule_AutoMemory(ctx); /* Use automatic memory management. */
RedisModuleKey *key =
RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ | REDISMODULE_WRITE);
MMapObject *obj_ptr = RedisModule_ModuleTypeGetValue(key);
long long index;
double value;
// 書き込む値の型を確認する
for (int i = 3; i < argc; i += 2) {
if (RedisModule_StringToDouble(argv[i], &value) == REDISMODULE_ERR) {
return RedisModule_ReplyWithError(ctx, "value must be double.");
}
}
int n_factors = 0; // 書き込めた要素の数を保持する変数
// mmap[index]に値を書き込む
for (int i = 2; i < argc; i += 2) {
RedisModule_StringToLongLong(argv[i], &index);
// indexが範囲内の時のみ書き込む
if (0 <= index && (size_t)index * sizeof(double) < obj_ptr->file_size) {
RedisModule_StringToDouble(argv[i + 1], &value);
((double*)obj_ptr->mmap)[index] = (double)value;
++n_factors;
}
}
// 書き込んだ回数を返す
return RedisModule_ReplyWithLongLong(ctx, n_factors);
}
这个函数用来在文件末尾追加值。
%%writefile -a fmmap.c
// VADD key value [value ...]
int VAdd_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
RedisModule_AutoMemory(ctx); /* Use automatic memory management. */
RedisModuleKey *key =
RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ | REDISMODULE_WRITE);
MMapObject *obj_ptr = RedisModule_ModuleTypeGetValue(key);
double value;
// 追加する値の型を確認する
for (int i = 2; i < argc; ++i) {
if (RedisModule_StringToDouble(argv[i], &value) == REDISMODULE_ERR) {
return RedisModule_ReplyWithError(ctx, "value must be double.");
}
}
// mmapを拡張して書き込む
size_t new_size = obj_ptr->file_size + sizeof(double) * (argc - 2);
ftruncate(obj_ptr->fd, new_size);
munmap(obj_ptr->mmap, obj_ptr->file_size);
obj_ptr->mmap = mmap(NULL, new_size, PROT_READ | PROT_WRITE, MAP_SHARED, obj_ptr->fd, 0);
for (int i = 2; i < argc; ++i) {
size_t index = obj_ptr->file_size / sizeof(double);
RedisModule_StringToDouble(argv[i], &value);
obj_ptr->file_size += sizeof(double);
((double*)obj_ptr->mmap)[index] = (double)value;
}
// 書き込んだ要素の数を返す
return RedisModule_ReplyWithLongLong(ctx, argc - 2);
}
这个函数用于获取文件中包含的值的数量。
%%writefile -a fmmap.c
// VSIZE key
int VSize_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
RedisModule_AutoMemory(ctx); /* Use automatic memory management. */
RedisModuleKey *key =
RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ | REDISMODULE_WRITE);
MMapObject *obj_ptr = RedisModule_ModuleTypeGetValue(key);
return RedisModule_ReplyWithLongLong(ctx, obj_ptr->file_size / sizeof(double));
}
这是一个用来清除文件内容的函数。
%%writefile -a fmmap.c
// VCLEAR key
int VClear_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
RedisModule_AutoMemory(ctx); /* Use automatic memory management. */
RedisModuleKey *key =
RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ | REDISMODULE_WRITE);
MMapObject *obj_ptr = RedisModule_ModuleTypeGetValue(key);
munmap(obj_ptr->mmap, obj_ptr->file_size);
ftruncate(obj_ptr->fd, 0);
obj_ptr->mmap = mmap(NULL, 0, PROT_READ | PROT_WRITE, MAP_SHARED, obj_ptr->fd, 0);
RedisModule_ReplyWithLongLong(ctx, obj_ptr->file_size / sizeof(double));
obj_ptr->file_size = 0;
return REDISMODULE_OK;
}
这是一个函数,可以从文件末尾获取并删除数值。
%%writefile -a fmmap.c
// VPOP key
int VPop_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
RedisModule_AutoMemory(ctx); /* Use automatic memory management. */
RedisModuleKey *key =
RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ | REDISMODULE_WRITE);
MMapObject *obj_ptr = RedisModule_ModuleTypeGetValue(key);
if (obj_ptr->file_size == 0) {
RedisModule_ReplyWithNull(ctx);
}
else {
size_t index = obj_ptr->file_size / sizeof(double) - 1;
RedisModule_ReplyWithDouble(ctx, ((double*)obj_ptr->mmap)[index]);
munmap(obj_ptr->mmap, obj_ptr->file_size);
obj_ptr->file_size -= sizeof(double);
ftruncate(obj_ptr->fd, obj_ptr->file_size);
obj_ptr->mmap = mmap(NULL, obj_ptr->file_size, PROT_READ | PROT_WRITE, MAP_SHARED, obj_ptr->fd, 0);
}
return REDISMODULE_OK;
}
这个函数用于将与mmap相关的信息保存在Redis的RDB文件中。
%%writefile -a fmmap.c
void MRdbSave(RedisModuleIO *rdb, void *value)
{
MMapObject *obj_ptr = value;
RedisModule_SaveStringBuffer(rdb, obj_ptr->file_path, sdslen(obj_ptr->file_path));
msync(obj_ptr->mmap, obj_ptr->file_size, MS_ASYNC);
}
这是一个从Redis的RDB文件中读取有关mmap的信息的函数。
%%writefile -a fmmap.c
void *MRdbLoad(RedisModuleIO *rdb, int encver)
{
MMapObject *obj_ptr = MCreateObject();
obj_ptr->file_path = sdsnew(RedisModule_StringPtrLen(RedisModule_LoadString(rdb), NULL));
obj_ptr->fd = open(obj_ptr->file_path, O_RDWR);
struct stat sb;
fstat(obj_ptr->fd, &sb);
obj_ptr->file_size = sb.st_size;
obj_ptr->mmap = mmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, obj_ptr->fd, 0);
return obj_ptr;
}
這是一個用於使用Redis AOF的函數。
%%writefile -a fmmap.c
void MAofRewrite(RedisModuleIO *aof, RedisModuleString *key, void *value)
{
char buffer[0x200];
MMapObject *obj_ptr = (MMapObject*)value;
RedisModule_EmitAOF(aof, "MMAP", "sc",
key,
obj_ptr->file_path);
RedisModule_EmitAOF(aof, "MCLEAR", "ss", key, obj_ptr->file_path);
for (size_t i = 0; i < obj_ptr->file_size; i += sizeof(double)) {
double value = *(double *)((uint8_t*)obj_ptr->mmap + i);
sprintf(buffer, "%.16f", value);
RedisModule_EmitAOF(aof, "MADD", "sbc", key, buffer);
}
}
另外,这是Redis模块所需的其他函数。
%%writefile -a fmmap.c
size_t MMemUsage(const void *value)
{
const MMapObject *obj_ptr = value;
return obj_ptr->file_size;
}
void MDigest(RedisModuleDigest *md, void *value)
{
REDISMODULE_NOT_USED(md);
REDISMODULE_NOT_USED(value);
}
制作一个用于创建Redis命令的宏。
%%writefile -a fmmap.c
#define CREATE_CMD(name, tgt, attr, key_pos, key_last) \
do { \
if (RedisModule_CreateCommand(ctx, name, tgt, attr, key_pos, key_last, \
1) != REDISMODULE_OK) { \
return REDISMODULE_ERR; \
} \
} while (0);
在加载模块时调用的函数,用于准备模块。在这里,将每个命令与执行该命令的函数绑定在一起。
%%writefile -a fmmap.c
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
{
REDISMODULE_NOT_USED(argv);
REDISMODULE_NOT_USED(argc);
RedisModule_Init(ctx, "FuchiMMap", 1, REDISMODULE_APIVER_1);
RedisModuleTypeMethods tm = {.version = REDISMODULE_TYPE_METHOD_VERSION,
.rdb_load = MRdbLoad,
.rdb_save = MRdbSave,
.aof_rewrite = MAofRewrite,
.mem_usage = MMemUsage,
.free = MFree,
.digest = MDigest};
MMapType = RedisModule_CreateDataType(ctx, "FuchiMMap", 0, &tm);
// MMAP key file_path
CREATE_CMD("MMAP", MMap_RedisCommand, "write fast", 1, 1);
// VCLEAR key
CREATE_CMD("VCLEAR", VClear_RedisCommand, "write fast", 1, 1);
// VADD key value [value ...]
CREATE_CMD("VADD", VAdd_RedisCommand, "write fast", 1, 1);
// VGET key index
CREATE_CMD("VGET", VGet_RedisCommand, "readonly fast", 1, 1);
// VMGET key index [index ...]
CREATE_CMD("VMGET", VMGet_RedisCommand, "readonly fast", 1, 1);
// VSET key index value [index value ...]
CREATE_CMD("VSET", VSet_RedisCommand, "write fast", 1, 1);
// VSIZE key
CREATE_CMD("VSIZE", VSize_RedisCommand, "readonly fast", 1, 1);
// VPOP key
CREATE_CMD("VPOP", VPop_RedisCommand, "write fast", 1, 1);
return REDISMODULE_OK;
}
这是用于构建源代码的Makefile。
%%writefile Makefile.fmmap
SHOBJ_CFLAGS ?= -W -Wall -fno-common -g -ggdb -std=c99 -O2
SHOBJ_LDFLAGS ?= -shared
.SUFFIXES: .c .so .xo .o
all: fmmap.so
.c.xo:
$(CC) -I. $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@
fmmap.xo: ../redismodule.h
fmmap.so: fmmap.xo
$(LD) -o $@ $^ $(SHOBJ_LDFLAGS) $(LIBS) -lc
clean:
rm -rf *.xo *.so
进行模块的构建。
!make -f Makefile.fmmap
!ls fmmap.so
cc -I. -W -Wall -fno-common -g -ggdb -std=c99 -O2 -fPIC -c fmmap.c -o fmmap.xo
ld -o fmmap.so fmmap.xo -shared -lc
fmmap.so
我将安装Redis。
!sudo yes | add-apt-repository ppa:redislabs/redis
!sudo apt-get update
!sudo apt-get install redis
将配置文件进行编写。
%%writefile -a /etc/redis/redis.conf
enable-module-command yes
loadmodule /content/redis-stable/src/modules/fmmap.so
运行Redis。
!service redis-server start
Starting redis-server: redis-server.
检查是否正在运行Redis。
!sleep 1
!ps aux | grep redis | grep -v grep
redis 3195 0.0 0.0 59132 6380 ? Ssl 01:21 0:00 /usr/bin/redis-server 127.0.0.1:6379
我们将准备一个文件写入的位置。
!mkdir /content/db
!chmod 777 /content/db
将文件映射到数据库中。返回值为数字数量,因为是新的,所以为0。
!echo "MMAP db /content/db/file.mmap" | redis-cli
(integer) 0
确认file.mmap文件已经创建。但文件大小仍为0。
!ls -l /content/db
total 0
-rw------- 1 redis redis 0 Jun 20 01:21 file.mmap
我尝试添加一个值。返回值是添加的数字数量。
!echo "VADD db 0.0" | redis-cli
(integer) 1
可以确认文件大小已经增加到8字节。
!ls -l /content/db
total 4
-rw------- 1 redis redis 8 Jun 20 01:21 file.mmap
让我们将命令写入文件并执行一下。
%%writefile command
vadd db 0.1
vadd db 0.2
vadd db 0.3
vadd db 0.4
vadd db 0.5
vadd db 0.6
vadd db 0.7
vadd db 0.8
vadd db 0.9
vsize db
运行后,您会发现注册数已经达到了10个。
!redis-cli < command
(integer) 1
(integer) 1
(integer) 1
(integer) 1
(integer) 1
(integer) 1
(integer) 1
(integer) 1
(integer) 1
(integer) 10
文件大小增加到了80个字节。
!ls -l /content/db
total 4
-rw------- 1 redis redis 80 Jun 20 01:21 file.mmap
您可以在一个命令中进行多个添加,返回添加的值的数量。
!echo "VADD db 1.0 1.1 1.2 1.3 1.4 1.5" | redis-cli
(integer) 6
我尝试提取值。
!echo "VGET db 5" | redis-cli
"0.5"
如果存在多个情况,情况如下。由于浮点数存在误差。
!echo "VMGET db 1 2 3 4 5" | redis-cli
1) "0.10000000000000001"
2) "0.20000000000000001"
3) "0.29999999999999999"
4) "0.40000000000000002"
5) "0.5"
您也可以改变值。将50赋给db[5],将100赋给db[10]。VSET的返回值是已更改的位置数量。
!echo "VSET db 5 50 10 100" | redis-cli
!echo "VMGET db 5 10" | redis-cli
(integer) 2
1) "50"
2) "100"
将列表的最后一个元素弹出并删除。
!echo "VSIZE db" | redis-cli
!echo "VGET db 15" | redis-cli
!echo "VPOP db" | redis-cli
!echo "VSIZE db" | redis-cli
(integer) 16
"1.5"
"1.5"
(integer) 15
停止Redis并重新启动。
!service redis-server stop
!service redis-server start
Stopping redis-server: redis-server.
Starting redis-server: redis-server.
即使重新启动,值仍然保持不变。
!echo "VSIZE db" | redis-cli
(integer) 15
删除数据库后,只需要重新映射,文件仍然保持原样,因此可以获取值。
!echo "KEYS *" | redis-cli
!echo "DEL db" | redis-cli
!echo "KEYS *" | redis-cli
!ls -l /content/db/
!echo "MMAP dba /content/db/file.mmap" | redis-cli
!echo "VGET dba 5" | redis-cli
1) "db"
(integer) 1
(empty array)
total 4
-rw------- 1 redis redis 120 Jun 20 01:21 file.mmap
(integer) 15
"50"
要清除内容就要这样做。
!echo "VCLEAR dba" | redis-cli
!echo "VSIZE dba" | redis-cli
(integer) 15
(integer) 0
文件大小将变为零。
!ls -l /content/db/
total 0
-rw------- 1 redis redis 0 Jun 20 01:21 file.mmap
删除dba。
!echo "DEL dba" | redis-cli
(integer) 1
使用Python创建一个包含100个double的文件。
import struct
with open('/content/db/file.mmap', 'wb') as fout:
for i in range(100):
fout.write(struct.pack('d', i))
映射文件并查看其内容。
!echo "mmap db /content/db/file.mmap" | redis-cli
!echo "vsize db" | redis-cli
!echo "vmget db 1 3 5 7 9" | redis-cli
(integer) 100
(integer) 100
1) "1"
2) "3"
3) "5"
4) "7"
5) "9"
停止 Redis。没有 redis-server。
!echo "shutdown" | redis-cli
!sleep 1
!ps aux | grep redis
root 3338 0.0 0.0 6904 3172 ? S 01:21 0:00 /bin/bash -c ps aux | grep redis
root 3340 0.0 0.0 6444 724 ? S 01:21 0:00 grep redis
文件仍然存在。
!ls -l /content/db
total 4
-rw------- 1 redis redis 800 Jun 20 01:21 file.mmap
完全翻译:以上就是全部。
简化表达:就是这些。