Redis模塊化介紹意譯與解析

筆者博客地址: https://charpty.com

原文:https://redis.io/topics/modules-intro

解析難懂之處,並提供更多的代碼示例幫助理解。
原文還有不少章節是缺失的,這一部分我先將原文補齊,PR通過後我會補充。


The modules documentation is composed of the following files:

  • INTRO.md (this file). An overview about Redis Modules system and API. It’s a good idea to start your reading here.
  • API.md is generated from module.c top comments of RedisMoule functions. It is a good reference in order to understand how each function works.
  • TYPES.md covers the implementation of native data types into modules.
  • BLOCK.md shows how to write blocking commands that will not reply immediately, but will block the client, without blocking the Redis server, and will provide a reply whenever will be possible.

Redis modules make possible to extend Redis functionality using external
modules, implementing new Redis commands at a speed and with features
similar to what can be done inside the core itself.

Redis modules are dynamic libraries, that can be loaded into Redis at
startup or using the MODULE LOAD command. Redis exports a C API, in the
form of a single C header file called redismodule.h. Modules are meant
to be written in C, however it will be possible to use C++ or other languages
that have C binding functionalities.

Modules are designed in order to be loaded into different versions of Redis,
so a given module does not need to be designed, or recompiled, in order to
run with a specific version of Redis. For this reason, the module will
register to the Redis core using a specific API version. The current API
version is “1”.

This document is about an alpha version of Redis modules. API, functionalities
and other details may change in the future.

Redis模塊化概要介紹

本文章用於介紹Redis模塊,分爲以下幾個文件

INTRO.md(當前文件),Redis模塊化的概要介紹,先讀這個比較好。
API.md,介紹Redis的模塊化提供的所有API,每個函數都有詳細介紹。
BLOCK.md,介紹寫一個阻塞客戶端但不阻塞服務器的命令。

Redis內部命令的實現也使用了模塊化,這種模式使得可以方便的自定義擴展模塊,擴展的模塊也可以方便的利用Redis中本來只能內部使用的優良特性。

Redis的模塊化主要利用的是動態庫(Windows的dll、Linux的so)特性,想實現自己的模塊,需要實現redismodule.h頭文件,用C和C++或其他語言寫都行,只要最後能編譯成so文件就可以。

Redis還是比較有良心的,模塊化API不會大的調整或者會做高版本兼容,所以寫好一個模塊,一次編譯好了就可以在多個Redis版本中使用而無需改代碼或重新編譯。

這個文件是初代版本的文檔,後續會逐漸改進(很多內容包括示例都是3.x版本年代的了)。

Loading modules

In order to test the module you are developing, you can load the module
using the following redis.conf configuration directive:

loadmodule /path/to/mymodule.so

It is also possible to load a module at runtime using the following command:

MODULE LOAD /path/to/mymodule.so

In order to list all loaded modules, use:

MODULE LIST

Finally, you can unload (and later reload if you wish) a module using the
following command:

MODULE UNLOAD mymodule

Note that mymodule above is not the filename without the .so suffix, but
instead, the name the module used to register itself into the Redis core.
The name can be obtained using MODULE LIST. However it is good practice
that the filename of the dynamic library is the same as the name the module
uses to register itself into the Redis core.

裝載模塊

你可以通過配置或命令方式來加載或卸載自己寫的模塊,也可以查看已加載的模塊情況。

註冊模塊時,註冊名默認約定是文件名filename去掉尾綴,好好取名,否則過段時間自己都不知道哪個模塊對應着自己寫的那個so了。

The simplest module you can write

In order to show the different parts of a module, here we’ll show a very
simple module that implements a command that outputs a random number.

#include "redismodule.h"
#include <stdlib.h>

int HelloworldRand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
    RedisModule_ReplyWithLongLong(ctx,rand());
    return REDISMODULE_OK;
}

int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
    if (RedisModule_Init(ctx,"helloworld",1,REDISMODULE_APIVER_1)
        == REDISMODULE_ERR) return REDISMODULE_ERR;

    if (RedisModule_CreateCommand(ctx,"helloworld.rand",
        HelloworldRand_RedisCommand) == REDISMODULE_ERR)
        return REDISMODULE_ERR;

    return REDISMODULE_OK;
}

The example module has two functions. One implements a command called
HELLOWORLD.RAND. This function is specific of that module. However the
other function called RedisModule_OnLoad() must be present in each
Redis module. It is the entry point for the module to be initialized,
register its commands, and potentially other private data structures
it uses.

Note that it is a good idea for modules to call commands with the
name of the module followed by a dot, and finally the command name,
like in the case of HELLOWORLD.RAND. This way it is less likely to
have collisions.

Note that if different modules have colliding commands, they’ll not be
able to work in Redis at the same time, since the function
RedisModule_CreateCommand will fail in one of the modules, so the module
loading will abort returning an error condition.

模塊編寫示例

自己寫個模塊試一下是最快的學習路徑,這是個最簡單示例,輸出一個隨機數字(這個示例比較老了,新版本對RedisModule_CreateCommand函數增加了3個參數),如下)

#include "redismodule.h"
#include <stdlib.h>

int HelloworldRand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
    RedisModule_ReplyWithLongLong(ctx,rand());
    return REDISMODULE_OK;
}

int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
    if (RedisModule_Init(ctx,"helloworld",1,REDISMODULE_APIVER_1)
        == REDISMODULE_ERR) return REDISMODULE_ERR;

	 // 這裏增加了3個參數:"readonly",0,0,0
    if (RedisModule_CreateCommand(ctx,"helloworld.rand",
        HelloworldRand_RedisCommand,"readonly",0,0,0) == REDISMODULE_ERR)
        return REDISMODULE_ERR;

    return REDISMODULE_OK;
}

RedisModule_OnLoad()是整個模塊的初始化入口函數,可以在這裏做很多的事情,包括:註冊自定義的命令(RedisModule_CreateCommand)、初始化自定義的數據結構(RedisModule_CreateDataType
)、預先分配內存(RedisModule_Alloc)等等。

對自定義命令取名字很重要,要防止衝突,一般的做法就是加上自己的命令空間,比如支付系統用的都叫pay.xxx訂單系統用的都叫order.xxx,官方建議使用模塊名、實際調用的命令實現函數名稱以點號分割。

Module initialization

The above example shows the usage of the function RedisModule_Init().
It should be the first function called by the module OnLoad function.
The following is the function prototype:

int RedisModule_Init(RedisModuleCtx *ctx, const char *modulename,
                     int module_version, int api_version);

The Init function announces the Redis core that the module has a given
name, its version (that is reported by MODULE LIST), and that is willing
to use a specific version of the API.

If the API version is wrong, the name is already taken, or there are other
similar errors, the function will return REDISMODULE_ERR, and the module
OnLoad function should return ASAP with an error.

Before the Init function is called, no other API function can be called,
otherwise the module will segfault and the Redis instance will crash.

The second function called, RedisModule_CreateCommand, is used in order
to register commands into the Redis core. The following is the prototype:

int RedisModule_CreateCommand(RedisModuleCtx *ctx, const char *cmdname,
                              RedisModuleCmdFunc cmdfunc);

As you can see, most Redis modules API calls all take as first argument
the context of the module, so that they have a reference to the module
calling it, to the command and client executing a given command, and so forth.

To create a new command, the above function needs the context, the command
name, and the function pointer of the function implementing the command,
which must have the following prototype:

int mycommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);

The command function arguments are just the context, that will be passed
to all the other API calls, the command argument vector, and total number
of arguments, as passed by the user.

As you can see, the arguments are provided as pointers to a specific data
type, the RedisModuleString. This is an opaque data type you have API
functions to access and use, direct access to its fields is never needed.

Zooming into the example command implementation, we can find another call:

int RedisModule_ReplyWithLongLong(RedisModuleCtx *ctx, long long integer);

This function returns an integer to the client that invoked the command,
exactly like other Redis commands do, like for example INCR or SCARD.

模塊的初始化

你應該發現了在模塊入口函數RedisModule_OnLoad()的第一行調用的是RedisModule_Init(),函數RedisModule_Init()用於註冊本模塊,告知Redis系統本模塊的名稱、模塊版本號,模塊要使用的Redis API版本號。 新註冊的模塊名稱不能是已存在的,要使用的API版本號也必須是Redis支持的。

在調用其他模塊化API之前必須先調用RedisModule_Init()進行初始化。

初始化之後,可以使用RedisModule_CreateCommand()自定義一個Redis Command,第一個參數是模塊上下文,創建命令或其他操作時都會使用到RedisModuleCtx上下文,這個上下文貫穿整個自定義模塊。還需要兩個參數,分別是command名稱,command對應實現的函數指針。

實現函數指針必須是以下類型

int mycommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);

第一個參數還是模塊上下文,第二個參數是裝載模塊時傳遞的參數值(module load xx1 xx2),被封裝爲robj對象的參數,這個robj對象是個萬能結構,幾乎啥都能表示(字符串、列表、字典等),RedisModuleString只是它的#define。第三個參數是參數個數,Redis裏一貫做法。

在command實現裏還用到了RedisModule_ReplyWithLongLong用於向客戶端展示結果,Redis模塊化API還有很多,靈活使用它們才能充分利用Redis特性寫好自定義模塊。

Setup and dependencies of a Redis module

Redis modules don’t depend on Redis or some other library, nor they
need to be compiled with a specific redismodule.h file. In order
to create a new module, just copy a recent version of redismodule.h
in your source tree, link all the libraries you want, and create
a dynamic library having the RedisModule_OnLoad() function symbol
exported.

The module will be able to load into different versions of Redis.

模塊庫依賴問題

Redis模塊本身不依賴於任何第三方模塊或Redis本身,編寫自定義模塊只需要將頭文件redismodule.h引入即可。

不管模塊引入了哪些第三方庫,只要最終將其靜態編譯成so動態庫,即可在多個Redis版本中被載入使用。

Passing configuration parameters to Redis modules

When the module is loaded with the MODULE LOAD command, or using the
loadmodule directive in the redis.conf file, the user is able to pass
configuration parameters to the module by adding arguments after the module
file name:

loadmodule mymodule.so foo bar 1234

In the above example the strings foo, bar and 123 will be passed
to the module OnLoad() function in the argv argument as an array
of RedisModuleString pointers. The number of arguments passed is into argc.

The way you can access those strings will be explained in the rest of this
document. Normally the module will store the module configuration parameters
in some static global variable that can be accessed module wide, so that
the configuration can change the behavior of different commands.

模塊初始化行爲的配置

使用命令或者配置文件來裝載模塊時可以傳遞參數,如module load arg1 arg2 arg3,參數將傳遞給入口函數RedisModule_OnLoad中的RedisModuleString **argv,這樣模塊可以對參數自行解析,從而在裝載時微調模塊特性。

Working with RedisModuleString objects

The command argument vector argv passed to module commands, and the
return value of other module APIs functions, are of type RedisModuleString.

Usually you directly pass module strings to other API calls, however sometimes
you may need to directly access the string object.

There are a few functions in order to work with string objects:

const char *RedisModule_StringPtrLen(RedisModuleString *string, size_t *len);

The above function accesses a string by returning its pointer and setting its
length in len.
You should never write to a string object pointer, as you can see from the
const pointer qualifier.

However, if you want, you can create new string objects using the following
API:

RedisModuleString *RedisModule_CreateString(RedisModuleCtx *ctx, const char *ptr, size_t len);

The string returned by the above command must be freed using a corresponding
call to RedisModule_FreeString():

void RedisModule_FreeString(RedisModuleString *str);

However if you want to avoid having to free strings, the automatic memory
management, covered later in this document, can be a good alternative, by
doing it for you.

Note that the strings provided via the argument vector argv never need
to be freed. You only need to free new strings you create, or new strings
returned by other APIs, where it is specified that the returned string must
be freed.

Creating strings from numbers or parsing strings as numbers

Creating a new string from an integer is a very common operation, so there
is a function to do this:

RedisModuleString *mystr = RedisModule_CreateStringFromLongLong(ctx,10);

Similarly in order to parse a string as a number:

long long myval;
if (RedisModule_StringToLongLong(ctx,argv[1],&myval) == REDISMODULE_OK) {
    /* Do something with 'myval' */
}

Accessing Redis keys from modules

Most Redis modules, in order to be useful, have to interact with the Redis
data space (this is not always true, for example an ID generator may
never touch Redis keys). Redis modules have two different APIs in order to
access the Redis data space, one is a low level API that provides very
fast access and a set of functions to manipulate Redis data structures.
The other API is more high level, and allows to call Redis commands and
fetch the result, similarly to how Lua scripts access Redis.

The high level API is also useful in order to access Redis functionalities
that are not available as APIs.

In general modules developers should prefer the low level API, because commands
implemented using the low level API run at a speed comparable to the speed
of native Redis commands. However there are definitely use cases for the
higher level API. For example often the bottleneck could be processing the
data and not accessing it.

Also note that sometimes using the low level API is not harder compared to
the higher level one.

重要數據結構–RedisModuleString

編寫模塊時很多函數的參數或返回值都有RedisModuleString類型,前面我們已經說了實際上它是robj類型,和JAVA的Object一樣,啥都能表示,RedisModuleString用於存儲參數和返回值,大多數情況下,它們都是字符串類型,模塊化API提供了對各種類型包括字符串的操作函數,字符串最常見所以舉了幾個例子。

  1. 設置字符串長度:RedisModule_StringPtrLen。
  2. 通過C字符串創建RedisModuleString:RedisModule_CreateString。
  3. 釋放字符串空間:RedisModule_FreeString。
  4. 根據數字創建字符串對象RedisModuleString:RedisModule_CreateStringFromLongLong
  5. 從字符串轉爲數字:RedisModule_StringToLongLong

自己編寫模塊,大多數情況下要訪問DB中的鍵,Redis提供了兩種方式來訪問DB中的鍵。

一是直接調用Redis對外的高層API,這就類似寫lua腳本來調用Redis API,效率比較低但是簡單不容易出錯。

二是調用Redis提供的底層API,它們效率很高,但是需要你對Redis的數據結構稍微有一定的瞭解(不復雜),出於效率考慮,應該優先選擇使用底層API。

Calling Redis commands

The high level API to access Redis is the sum of the RedisModule_Call()
function, together with the functions needed in order to access the
reply object returned by Call().

RedisModule_Call uses a special calling convention, with a format specifier
that is used to specify what kind of objects you are passing as arguments
to the function.

Redis commands are invoked just using a command name and a list of arguments.
However when calling commands, the arguments may originate from different
kind of strings: null-terminated C strings, RedisModuleString objects as
received from the argv parameter in the command implementation, binary
safe C buffers with a pointer and a length, and so forth.

For example if I want to call INCRBY using a first argument (the key)
a string received in the argument vector argv, which is an array
of RedisModuleString object pointers, and a C string representing the
number “10” as second argument (the increment), I’ll use the following
function call:

RedisModuleCallReply *reply;
reply = RedisModule_Call(ctx,"INCR","sc",argv[1],"10");

The first argument is the context, and the second is always a null terminated
C string with the command name. The third argument is the format specifier
where each character corresponds to the type of the arguments that will follow.
In the above case "sc" means a RedisModuleString object, and a null
terminated C string. The other arguments are just the two arguments as
specified. In fact argv[1] is a RedisModuleString and "10" is a null
terminated C string.

This is the full list of format specifiers:

  • c – Null terminated C string pointer.
  • b – C buffer, two arguments needed: C string pointer and size_t length.
  • s – RedisModuleString as received in argv or by other Redis module APIs returning a RedisModuleString object.
  • l – Long long integer.
  • v – Array of RedisModuleString objects.
  • ! – This modifier just tells the function to replicate the command to slaves and AOF. It is ignored from the point of view of arguments parsing.

The function returns a RedisModuleCallReply object on success, on
error NULL is returned.

NULL is returned when the command name is invalid, the format specifier uses
characters that are not recognized, or when the command is called with the
wrong number of arguments. In the above cases the errno var is set to EINVAL. NULL is also returned when, in an instance with Cluster enabled, the target
keys are about non local hash slots. In this case errno is set to EPERM.

Working with RedisModuleCallReply objects.

RedisModuleCall returns reply objects that can be accessed using the
RedisModule_CallReply* family of functions.

In order to obtain the type or reply (corresponding to one of the data types
supported by the Redis protocol), the function RedisModule_CallReplyType()
is used:

reply = RedisModule_Call(ctx,"INCR","sc",argv[1],"10");
if (RedisModule_CallReplyType(reply) == REDISMODULE_REPLY_INTEGER) {
    long long myval = RedisModule_CallReplyInteger(reply);
    /* Do something with myval. */
}

Valid reply types are:

  • REDISMODULE_REPLY_STRING Bulk string or status replies.
  • REDISMODULE_REPLY_ERROR Errors.
  • REDISMODULE_REPLY_INTEGER Signed 64 bit integers.
  • REDISMODULE_REPLY_ARRAY Array of replies.
  • REDISMODULE_REPLY_NULL NULL reply.

Strings, errors and arrays have an associated length. For strings and errors
the length corresponds to the length of the string. For arrays the length
is the number of elements. To obtain the reply length the following function
is used:

size_t reply_len = RedisModule_CallReplyLength(reply);

In order to obtain the value of an integer reply, the following function is used, as already shown in the example above:

long long reply_integer_val = RedisModule_CallReplyInteger(reply);

Called with a reply object of the wrong type, the above function always
returns LLONG_MIN.

Sub elements of array replies are accessed this way:

RedisModuleCallReply *subreply;
subreply = RedisModule_CallReplyArrayElement(reply,idx);

The above function returns NULL if you try to access out of range elements.

Strings and errors (which are like strings but with a different type) can
be accessed using in the following way, making sure to never write to
the resulting pointer (that is returned as as const pointer so that
misusing must be pretty explicit):

size_t len;
char *ptr = RedisModule_CallReplyStringPtr(reply,&len);

If the reply type is not a string or an error, NULL is returned.

RedisCallReply objects are not the same as module string objects
(RedisModuleString types). However sometimes you may need to pass replies
of type string or integer, to API functions expecting a module string.

When this is the case, you may want to evaluate if using the low level
API could be a simpler way to implement your command, or you can use
the following function in order to create a new string object from a
call reply of type string, error or integer:

RedisModuleString *mystr = RedisModule_CreateStringFromCallReply(myreply);

If the reply is not of the right type, NULL is returned.
The returned string object should be released with RedisModule_FreeString()
as usually, or by enabling automatic memory management (see corresponding
section).

調用Redis高層API

Redis高層API是對外開放的,在lua腳本中可以使用redis.call()調用,在擴展模塊中則可以使用RedisModule_Call()調用,調用的方式和lua的調用方式形式一致,只是不同語言寫法不同。

調用RedisModule_Call()時傳遞的參數形式比較自由,比如調用INCY增加某個key計數時可以用如下寫法

// 第一個參數key是RedisModuleString類型,第二個參數增加數是標準C字符串類型
reply = RedisModule_Call(ctx,"INCR","sc",argv[1],"10");

"sc"是標記位,代表實際參數的類型,傳遞參數時,允許以下幾種類型(和它們的標記位):

  1. c:標準的NULL結尾的字符串
  2. b:buffer+長度形式的字符串
  3. s:RedisModuleString,前面介紹的通用結構體
  4. v:RedisModuleString的數組
  5. l:數字
  6. !:這個標記位不表示參數類型,僅用於告知Reids將操作同步給slave或備份

當調用成功,會返回RedisModuleCallReply對象,失敗則返回NULL。多種原因會導致返回NULL,發生錯誤時會將錯誤原因代碼設置在全局變量EINVAL中。

比如當發現key對應的slot沒落在當前節點上時,則會設置EINVAL=EPERM來告知調用者。

Redis的返回值RedisModuleCallReply存儲的實際值一共有5種類型。

  • REDISMODULE_REPLY_STRING:字符串(RESP響應格式)
  • REDISMODULE_REPLY_ERROR:錯誤信息,一般是業務上的錯誤
  • REDISMODULE_REPLY_INTEGER:long long數字
  • REDISMODULE_REPLY_ARRAY:響應是個數組
  • REDISMODULE_REPLY_NULL:代表NULL.

Redis還提供了一些輔助函數來幫助解析RedisModule_Call()的正確返回值RedisModuleCallReply,它們的命名方式都是RedisModule_CallReply*。

  1. 比如想解析返回結構體的值數據類型可以使用RedisModule_CallReplyType()。
  2. 想獲取返回數據的長度可以使用RedisModule_CallReplyLength()。
  3. 明確返回值是數字並想轉換爲數字可使用RedisModule_CallReplyInteger()。
  4. 獲取返回結構體中結果數組的某一個元素可使用RedisModule_CallReplyArrayElement()。
  5. 提取響應結果中的字符串可用RedisModule_CallReplyStringPtr()。
  6. 直接將結果轉換爲字符串可用RedisModule_CreateStringFromCallReply()。

Releasing call reply objects

Reply objects must be freed using RedisModule_FreeCallReply. For arrays,
you need to free only the top level reply, not the nested replies.
Currently the module implementation provides a protection in order to avoid
crashing if you free a nested reply object for error, however this feature
is not guaranteed to be here forever, so should not be considered part
of the API.

If you use automatic memory management (explained later in this document)
you don’t need to free replies (but you still could if you wish to release
memory ASAP).

Returning values from Redis commands

Like normal Redis commands, new commands implemented via modules must be
able to return values to the caller. The API exports a set of functions for
this goal, in order to return the usual types of the Redis protocol, and
arrays of such types as elemented. Also errors can be returned with any
error string and code (the error code is the initial uppercase letters in
the error message, like the “BUSY” string in the “BUSY the sever is busy” error
message).

All the functions to send a reply to the client are called
RedisModule_ReplyWith<something>.

To return an error, use:

RedisModule_ReplyWithError(RedisModuleCtx *ctx, const char *err);

There is a predefined error string for key of wrong type errors:

REDISMODULE_ERRORMSG_WRONGTYPE

Example usage:

RedisModule_ReplyWithError(ctx,"ERR invalid arguments");

We already saw how to reply with a long long in the examples above:

RedisModule_ReplyWithLongLong(ctx,12345);

To reply with a simple string, that can’t contain binary values or newlines,
(so it’s suitable to send small words, like “OK”) we use:

RedisModule_ReplyWithSimpleString(ctx,"OK");

It’s possible to reply with “bulk strings” that are binary safe, using
two different functions:

int RedisModule_ReplyWithStringBuffer(RedisModuleCtx *ctx, const char *buf, size_t len);

int RedisModule_ReplyWithString(RedisModuleCtx *ctx, RedisModuleString *str);

The first function gets a C pointer and length. The second a RedisMoudleString
object. Use one or the other depending on the source type you have at hand.

In order to reply with an array, you just need to use a function to emit the
array length, followed by as many calls to the above functions as the number
of elements of the array are:

RedisModule_ReplyWithArray(ctx,2);
RedisModule_ReplyWithStringBuffer(ctx,"age",3);
RedisModule_ReplyWithLongLong(ctx,22);

To return nested arrays is easy, your nested array element just uses another
call to RedisModule_ReplyWithArray() followed by the calls to emit the
sub array elements.

Returning arrays with dynamic length

Sometimes it is not possible to know beforehand the number of items of
an array. As an example, think of a Redis module implementing a FACTOR
command that given a number outputs the prime factors. Instead of
factorializing the number, storing the prime factors into an array, and
later produce the command reply, a better solution is to start an array
reply where the length is not known, and set it later. This is accomplished
with a special argument to RedisModule_ReplyWithArray():

RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);

The above call starts an array reply so we can use other ReplyWith calls
in order to produce the array items. Finally in order to set the length
se use the following call:

RedisModule_ReplySetArrayLength(ctx, number_of_items);

In the case of the FACTOR command, this translates to some code similar
to this:

RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
number_of_factors = 0;
while(still_factors) {
    RedisModule_ReplyWithLongLong(ctx, some_factor);
    number_of_factors++;
}
RedisModule_ReplySetArrayLength(ctx, number_of_factors);

Another common use case for this feature is iterating over the arrays of
some collection and only returning the ones passing some kind of filtering.

It is possible to have multiple nested arrays with postponed reply.
Each call to SetArray() will set the length of the latest corresponding
call to ReplyWithArray():

RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
... generate 100 elements ...
RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
... generate 10 elements ...
RedisModule_ReplySetArrayLength(ctx, 10);
RedisModule_ReplySetArrayLength(ctx, 100);

This creates a 100 items array having as last element a 10 items array.

釋放返回結果的內存空間

在使用完響應結果RedisModuleCallReply之後,必須手動釋放其內存空間,Redis提供了函數RedisModule_FreeCallReply來幫助你正確釋放內存。Redis模塊化系統目前增加了防崩潰保護,以免錯誤的內存釋放導致的系統崩潰,但是釋放內存或其他風險操作時還是應該謹慎。

厲害的是,你可以選擇讓Redis自動管理內存,就和JAVA的GC一樣,你只管用而不用釋放內存,只需要一行配置代碼即可開啓自動管理內存功能。

自定義命令的實現函數必須要有返回值,而且返回值必須遵循既有的格式規範,爲此Redis模塊化系統提供了一組函數來幫助開發者實現該目標,這些名稱函數皆如RedisModule_ReplyWith。

好理解的幾個函數

  1. 返回錯誤信息:RedisModule_ReplyWithError(RedisModuleCtx *ctx, const char *errMsg)
  2. 返回數字結果:RedisModule_ReplyWithLongLong(ctx,12345);
  3. 返回簡單字符串(加號前綴當行短字符串):RedisModule_ReplyWithSimpleString(ctx,“OK”);
  4. 返回大字符串(RESP中的bulk strings):RedisModule_ReplyWithStringBuffer或RedisModule_ReplyWithString
  5. 返回數組(RESP Arrays):和RESP實現模式類似,先設置數組長度RedisModule_ReplyWithArray,而後逐個元素輸出

上面幾個函數大都是對RESP格式的封裝,比較好理解,還剩下一個動態數組和嵌套動態數組,道理一樣。

動態數組,某些場景下,實現函數自己也是從別人那裏流式接收列表數據,目前它還不知道長度,那麼可以設置返回類型是一個動態數組,先將元素逐個返回,之後再設置返回數組長度。遍歷列表並返回是最常見的應用場景。

// REDISMODULE_POSTPONED_ARRAY_LEN標記動態數組
RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
number_of_factors = 0;
while(still_factors) {
	 // 先將元素逐個返回
    RedisModule_ReplyWithLongLong(ctx, some_factor);
    number_of_factors++;
}
// 最後再設置數組長度
RedisModule_ReplySetArrayLength(ctx, number_of_factors);

嵌套動態數組就是其字面意思,在動態數組的基礎上進行嵌套,數組裏有數組或者說數組裏的元素類型又是數組。

// 外層的數組
RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
... generate 100 elements ...
// 內層的數組
RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
... generate 10 elements ...
// 分別設置內外層數組長度
// 外層數組一共有100個元素,其中最後一個元素是一個數組,這個數組又存放了10個元素
RedisModule_ReplySetArrayLength(ctx, 10);
RedisModule_ReplySetArrayLength(ctx, 100);

Arity and type checks

Often commands need to check that the number of arguments and type of the key
is correct. In order to report a wrong arity, there is a specific function
called RedisModule_WrongArity(). The usage is trivial:

if (argc != 2) return RedisModule_WrongArity(ctx);

Checking for the wrong type involves opening the key and checking the type:

RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
    REDISMODULE_READ|REDISMODULE_WRITE);

int keytype = RedisModule_KeyType(key);
if (keytype != REDISMODULE_KEYTYPE_STRING &&
    keytype != REDISMODULE_KEYTYPE_EMPTY)
{
    RedisModule_CloseKey(key);
    return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
}

Note that you often want to proceed with a command both if the key
is of the expected type, or if it’s empty.

Low level access to keys

Low level access to keys allow to perform operations on value objects associated
to keys directly, with a speed similar to what Redis uses internally to
implement the built-in commands.

Once a key is opened, a key pointer is returned that will be used with all the
other low level API calls in order to perform operations on the key or its
associated value.

Because the API is meant to be very fast, it cannot do too many run-time
checks, so the user must be aware of certain rules to follow:

  • Opening the same key multiple times where at least one instance is opened for writing, is undefined and may lead to crashes.
  • While a key is open, it should only be accessed via the low level key API. For example opening a key, then calling DEL on the same key using the RedisModule_Call() API will result into a crash. However it is safe to open a key, perform some operation with the low level API, closing it, then using other APIs to manage the same key, and later opening it again to do some more work.

In order to open a key the RedisModule_OpenKey function is used. It returns
a key pointer, that we’ll use with all the next calls to access and modify
the value:

RedisModuleKey *key;
key = RedisModule_OpenKey(ctx,argv[1],REDISMODULE_READ);

The second argument is the key name, that must be a RedisModuleString object.
The third argument is the mode: REDISMODULE_READ or REDISMODULE_WRITE.
It is possible to use | to bitwise OR the two modes to open the key in
both modes. Currently a key opened for writing can also be accessed for reading
but this is to be considered an implementation detail. The right mode should
be used in sane modules.

You can open non exisitng keys for writing, since the keys will be created
when an attempt to write to the key is performed. However when opening keys
just for reading, RedisModule_OpenKey will return NULL if the key does not
exist.

Once you are done using a key, you can close it with:

RedisModule_CloseKey(key);

Note that if automatic memory management is enabled, you are not forced to
close keys. When the module function returns, Redis will take care to close
all the keys which are still open.

Getting the key type

In order to obtain the value of a key, use the RedisModule_KeyType() function:

int keytype = RedisModule_KeyType(key);

It returns one of the following values:

REDISMODULE_KEYTYPE_EMPTY
REDISMODULE_KEYTYPE_STRING
REDISMODULE_KEYTYPE_LIST
REDISMODULE_KEYTYPE_HASH
REDISMODULE_KEYTYPE_SET
REDISMODULE_KEYTYPE_ZSET

The above are just the usual Redis key types, with the addition of an empty
type, that signals the key pointer is associated with an empty key that
does not yet exists.

Creating new keys

To create a new key, open it for writing and then write to it using one
of the key writing functions. Example:

RedisModuleKey *key;
key = RedisModule_OpenKey(ctx,argv[1],REDISMODULE_WRITE);
if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_EMPTY) {
    RedisModule_StringSet(key,argv[2]);
}

Deleting keys

Just use:

RedisModule_DeleteKey(key);

The function returns REDISMODULE_ERR if the key is not open for writing.
Note that after a key gets deleted, it is setup in order to be targeted
by new key commands. For example RedisModule_KeyType() will return it is
an empty key, and writing to it will create a new key, possibly of another
type (depending on the API used).

Managing key expires (TTLs)

To control key expires two functions are provided, that are able to set,
modify, get, and unset the time to live associated with a key.

One function is used in order to query the current expire of an open key:

mstime_t RedisModule_GetExpire(RedisModuleKey *key);

The function returns the time to live of the key in milliseconds, or
REDISMODULE_NO_EXPIRE as a special value to signal the key has no associated
expire or does not exist at all (you can differentiate the two cases checking
if the key type is REDISMODULE_KEYTYPE_EMPTY).

In order to change the expire of a key the following function is used instead:

int RedisModule_SetExpire(RedisModuleKey *key, mstime_t expire);

When called on a non existing key, REDISMODULE_ERR is returned, because
the function can only associate expires to existing open keys (non existing
open keys are only useful in order to create new values with data type
specific write operations).

Again the expire time is specified in milliseconds. If the key has currently
no expire, a new expire is set. If the key already have an expire, it is
replaced with the new value.

If the key has an expire, and the special value REDISMODULE_NO_EXPIRE is
used as a new expire, the expire is removed, similarly to the Redis
PERSIST command. In case the key was already persistent, no operation is
performed.

Obtaining the length of values

There is a single function in order to retrieve the length of the value
associated to an open key. The returned length is value-specific, and is
the string length for strings, and the number of elements for the aggregated
data types (how many elements there is in a list, set, sorted set, hash).

size_t len = RedisModule_ValueLength(key);

If the key does not exist, 0 is returned by the function:

String type API

Setting a new string value, like the Redis SET command does, is performed
using:

int RedisModule_StringSet(RedisModuleKey *key, RedisModuleString *str);

The function works exactly like the Redis SET command itself, that is, if
there is a prior value (of any type) it will be deleted.

Accessing existing string values is performed using DMA (direct memory
access) for speed. The API will return a pointer and a length, so that’s
possible to access and, if needed, modify the string directly.

size_t len, j;
char *myptr = RedisModule_StringDMA(key,&len,REDISMODULE_WRITE);
for (j = 0; j < len; j++) myptr[j] = 'A';

In the above example we write directly on the string. Note that if you want
to write, you must be sure to ask for WRITE mode.

DMA pointers are only valid if no other operations are performed with the key
before using the pointer, after the DMA call.

Sometimes when we want to manipulate strings directly, we need to change
their size as well. For this scope, the RedisModule_StringTruncate function
is used. Example:

RedisModule_StringTruncate(mykey,1024);

The function truncates, or enlarges the string as needed, padding it with
zero bytes if the previos length is smaller than the new length we request.
If the string does not exist since key is associated to an open empty key,
a string value is created and associated to the key.

Note that every time StringTruncate() is called, we need to re-obtain
the DMA pointer again, since the old may be invalid.

List type API

It’s possible to push and pop values from list values:

int RedisModule_ListPush(RedisModuleKey *key, int where, RedisModuleString *ele);
RedisModuleString *RedisModule_ListPop(RedisModuleKey *key, int where);

In both the APIs the where argument specifies if to push or pop from tail
or head, using the following macros:

REDISMODULE_LIST_HEAD
REDISMODULE_LIST_TAIL

Elements returned by RedisModule_ListPop() are like strings craeted with
RedisModule_CreateString(), they must be released with
RedisModule_FreeString() or by enabling automatic memory management.

Set type API

Work in progress.

Sorted set type API

Documentation missing, please refer to the top comments inside module.c
for the following functions:

  • RedisModule_ZsetAdd
  • RedisModule_ZsetIncrby
  • RedisModule_ZsetScore
  • RedisModule_ZsetRem

And for the sorted set iterator:

  • RedisModule_ZsetRangeStop
  • RedisModule_ZsetFirstInScoreRange
  • RedisModule_ZsetLastInScoreRange
  • RedisModule_ZsetFirstInLexRange
  • RedisModule_ZsetLastInLexRange
  • RedisModule_ZsetRangeCurrentElement
  • RedisModule_ZsetRangeNext
  • RedisModule_ZsetRangePrev
  • RedisModule_ZsetRangeEndReached

Hash type API

Documentation missing, please refer to the top comments inside module.c
for the following functions:

  • RedisModule_HashSet
  • RedisModule_HashGet

Iterating aggregated values

Work in progress.

參數檢查與使用

函數被調用時,一般來說做的第一件事情是檢查調用者傳遞的參數是否正確,包括參數個數檢查、參數類型檢查、值邊界檢查、對應key是否存在等等,Redis提供了一組函數來幫助開發者檢查參數以及在參數有誤時返回錯誤信息。

比如檢查參數個數是否正確以及返回錯誤

if (argc != 2) return RedisModule_WrongArity(ctx);

比如檢查參數的類型是否正確

RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
    REDISMODULE_READ|REDISMODULE_WRITE);

int keytype = RedisModule_KeyType(key);
if (keytype != REDISMODULE_KEYTYPE_STRING &&
    keytype != REDISMODULE_KEYTYPE_EMPTY)
{
    RedisModule_CloseKey(key);
    // 預定好的REDISMODULE_ERRORMSG_WRONGTYPE表示參數類型錯誤
    return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
}

對於更深次的檢查和操作,Redis模塊化系統提供了許多底層API來完成,Redis作爲一個K-V存儲系統,最常見的當然是對key的操作(查找key對應元素、判斷key還有多久過期、刪除key等等),在對一個key進行操作前必須open它。

key = RedisModule_OpenKey(ctx,argv[1],REDISMODULE_READ);

只有在open之後,才能繼續調用Redis模塊化的底層API進行操作,底層API的效率很高,當然也意味着限制更少,防崩潰手段更少,所以開發者使用時需要注意以下事項。

  1. 一個key只能被打開一次,多次打開某個key的結果是不可預料的
  2. 當某個key被打開時,它只能被底層API訪問和操作

在調用RedisModule_OpenKey時,第二個參數當然就是要open的具體key了,這個key必須是RedisModuleString類型,實質上它還必須是個string,Redis中的key都是string。
第三個參數是open的模式,和打開文件一樣有讀(REDISMODULE_READ)、寫(REDISMODULE_WRITE)模式。

以寫模式open一個不存在的key就等同於創建這個key,然後可以寫入自己的數據。如果以讀模式open一個不存在的key則會返回空。

在使用一個key後,必須對它進行關閉,調用RedisModule_CloseKey(key)即可。
和前面返回值一樣,如果打開了自動內存管理則無需close操作,後續的許多操作在獲取都返回值後,只要是打開了自動內存管理則都不需要主動close了(當然你也可以主動close來快速釋放內存),後續就不再重複說明了。

接下來對常用的底層API進行簡單的介紹

獲取key類型

可以使用RedisModule_KeyType()函數獲取key類型

int keytype = RedisModule_KeyType(key);

一共有5種類型(Redis的5種基本類型),再加一種空類型

  • REDISMODULE_KEYTYPE_STRING
  • REDISMODULE_KEYTYPE_LIST
  • REDISMODULE_KEYTYPE_HASH
  • REDISMODULE_KEYTYPE_SET
  • REDISMODULE_KEYTYPE_ZSET
  • REDISMODULE_KEYTYPE_EMPTY

空類型用於代表當前key不存在

創建新key

通過寫模式打開一個不存在的key則可以創建該key

RedisModuleKey *key;
// 原文模式沒寫對,應該是寫方式打開
key = RedisModule_OpenKey(ctx,argv[1],REDISMODULE_WRITE);
if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_EMPTY) {
    RedisModule_StringSet(key,argv[2]);
}

刪除key

RedisModule_DeleteKey(key);

必須先open後才能刪除該key,open成功則意味着模塊當前成功佔用了該key,此時才能進行刪除。

管理key剩餘生存時間

和外部的expire命令一樣,Redis模塊化系統提供了後取和設置的命令來管理key的生存時間。

  • 獲取key的生存時間
mstime_t RedisModule_GetExpire(RedisModuleKey *key);

返回的時間單位是毫秒,如果key不存在或者未設置過期時間則都會返回REDISMODULE_NO_EXPIRE,如果想區分是哪種情況則先判斷下key是否存在。

  • 設置key的生存時間
int RedisModule_SetExpire(RedisModuleKey *key, mstime_t expire);

如果對不存在的或未open(任何操作都要先open,後續不再重複描述)的key設置過期則會返回REDISMODULE_ERR錯誤。

設置特殊的過期時間REDISMODULE_NO_EXPIRE代表取消該key的生存時間,該key永不過期。

獲取key的長度

size_t len = RedisModule_ValueLength(key);

如果key是字符串則返回字符長度,如果key是集合則返回集合中元素的個數。

字符串類型常用的API

  • 設置字符串內容,和命令SET同效果
int RedisModule_StringSet(RedisModuleKey *key, RedisModuleString *str);
  • 直接訪問字符串內存地址
size_t len, j;
char *myptr = RedisModule_StringDMA(key,&len,REDISMODULE_WRITE);
for (j = 0; j < len; j++) myptr[j] = 'A';

這種方式是直接操作內存,效率更高,當然也更不安全。此操作方式沒法分配新內存,只能改變現有內存中的內容。

  • 完全操作字符串
RedisModule_StringTruncate(mykey,1024);

這種方式是分配了一個新的內存地址,只是將原字符串拷貝過去,後續的操作都在新的地址上,此時想怎麼設置都是隨心所欲了。

列表類型常用的API

  • 放入或彈出元素
// 在指定位置放入元素
int RedisModule_ListPush(RedisModuleKey *key, int where, RedisModuleString *ele);
// 獲取指定位置元素
RedisModuleString *RedisModule_ListPop(RedisModuleKey *key, int where);

爲了方便常用的操作,Redis定義好了一些常用宏

REDISMODULE_LIST_HEAD  列表頭位置
REDISMODULE_LIST_TAIL  列表尾位置

集合set類型常用的API

還沒有D–

有序集合zset常用的API

也沒好好寫,大概有這些API,在源代碼中找到直接看代碼吧

RedisModule_ZsetAdd
RedisModule_ZsetIncrby
RedisModule_ZsetScore
RedisModule_ZsetRem

RedisModule_ZsetRangeStop
RedisModule_ZsetFirstInScoreRange
RedisModule_ZsetLastInScoreRange
RedisModule_ZsetFirstInLexRange
RedisModule_ZsetLastInLexRange
RedisModule_ZsetRangeCurrentElement
RedisModule_ZsetRangeNext
RedisModule_ZsetRangePrev
RedisModule_ZsetRangeEndReached

哈希表

沒寫,自己看函數

RedisModule_HashSet
RedisModule_HashGet

迭代聚合

還沒有

Replicating commands

If you want to use module commands exactly like normal Redis commands, in the
context of replicated Redis instances, or using the AOF file for persistence,
it is important for module commands to handle their replication in a consistent
way.

When using the higher level APIs to invoke commands, replication happens
automatically if you use the “!” modifier in the format string of
RedisModule_Call() as in the following example:

reply = RedisModule_Call(ctx,"INCR","!sc",argv[1],"10");

As you can see the format specifier is "!sc". The bang is not parsed as a
format specifier, but it internally flags the command as “must replicate”.

If you use the above programming style, there are no problems.
However sometimes things are more complex than that, and you use the low level
API. In this case, if there are no side effects in the command execution, and
it consistently always performs the same work, what is possible to do is to
replicate the command verbatim as the user executed it. To do that, you just
need to call the following function:

RedisModule_ReplicateVerbatim(ctx);

When you use the above API, you should not use any other replication function
since they are not guaranteed to mix well.

However this is not the only option. It’s also possible to exactly tell
Redis what commands to replicate as the effect of the command execution, using
an API similar to RedisModule_Call() but that instead of calling the command
sends it to the AOF / slaves stream. Example:

RedisModule_Replicate(ctx,"INCRBY","cl","foo",my_increment);

It’s possible to call RedisModule_Replicate multiple times, and each
will emit a command. All the sequence emitted is wrapped between a
MULTI/EXEC transaction, so that the AOF and replication effects are the
same as executing a single command.

Note that Call() replication and Replicate() replication have a rule,
in case you want to mix both forms of replication (not necessarily a good
idea if there are simpler approaches). Commands replicated with Call()
are always the first emitted in the final MULTI/EXEC block, while all
the commands emitted with Replicate() will follow.

副本集命令

如果想模塊中執行的call代碼和外部執行命令效果一樣,很重要的一塊是AOF備份與集羣模式的主從同步。

在高層API或者命令行執行命令時,已經包含了這一部分功能,但是底層API卻沒有直接包含。

想要在底層API中實現這兩個功能也比較簡單,只需在call()命令的標記位中增加一個感嘆號即可。

reply = RedisModule_Call(ctx,"INCR","!sc",argv[1],"10");

"!"感嘆號不代表參數格式,而是代表必須執行AOF備份和主從同步(集羣模式下)。

當然這只是告知Redis核心必須進行備份,具體何種情況下備份則由Redis系統自行選擇(配置相關)。如果想要逐行備份(或同步副本集)則可以使用ReplicateVerbatim來強制立刻執行。

RedisModule_ReplicateVerbatim(ctx);

這個命令是非事務的,即比方它只管同步它的上一條命令,但是我們知道上一條命令不一定是上一行代碼,所以如果兩行執行期間有其他帶副本集功能(要寫備份或同步的)的命令混入使用則可能會出問題。

如果想要原子執行一次副本集命令,則可以使用RedisModule_Replicate來執行備份(或同步)。

RedisModule_Replicate(ctx,"INCRBY","cl","foo",my_increment);

這個將數據操作命令和同步命令放在一起了,可以想到是原子操作了。

可以調用Replicate()多次,不管怎麼玩,Redis會保證他們像單線程逐行執行一樣的效果。

在一個命令實現代碼中,如果既調用RedisModule_Call(帶感嘆號)帶有副本集命令,又顯式指定了RedisModule_Replicate等副本集命令(最好別這樣做),在執行備份時,會讓Call()命令副本集命令在事務最前。

這裏要理解副本集執行時,會先將該模塊實現的這個自定義命令的所有副本集命令都一起放到一個事務MULTI/EXEC block中,這時就可以先對各個命令進行順序調整。

爲何這麼處理,我理解的是顯式的副本集命令優先級更高,放在後面才能生效。

Automatic memory management

Normally when writing programs in the C language, programmers need to manage
memory manually. This is why the Redis modules API has functions to release
strings, close open keys, free replies, and so forth.

However given that commands are executed in a contained environment and
with a set of strict APIs, Redis is able to provide automatic memory management
to modules, at the cost of some performance (most of the time, a very low
cost).

When automatic memory management is enabled:

  1. You don’t need to close open keys.
  2. You don’t need to free replies.
  3. You don’t need to free RedisModuleString objects.

However you can still do it, if you want. For example, automatic memory
management may be active, but inside a loop allocating a lot of strings,
you may still want to free strings no longer used.

In order to enable automatic memory management, just call the following
function at the start of the command implementation:

RedisModule_AutoMemory(ctx);

Automatic memory management is usually the way to go, however experienced
C programmers may not use it in order to gain some speed and memory usage
benefit.

自動化內存管理

Redis使得寫C代碼也擁有GC特性,避免程序自行管理內存,雖然有一點小小的消耗,但是帶來了很大的編程便利。Redis的自動內存管理有以下幾個特性:

  1. 不需要顯示關閉open的key
  2. 不需要顯示關閉返回的副本集
  3. 不需要顯示關閉作爲參數或返回值的RedisModuleString

通過一句簡單的配置即可啓用自動內存管理

RedisModule_AutoMemory(ctx);

自動內存相當於一個定時器,通過定期檢查來釋放無用內存,開啓了自動內存管理之後,還是可以手動free來立刻釋放內存(某些大內存場景需要即刻釋放)。

自動內存管理是一個編程友好的功能,推薦使用,但是對於那些充滿自信、富有經驗的老程序員,也可以自己管理內存,以便能提高性能和內存使用率。

Allocating memory into modules

Normal C programs use malloc() and free() in order to allocate and
release memory dynamically. While in Redis modules the use of malloc is
not technically forbidden, it is a lot better to use the Redis Modules
specific functions, that are exact replacements for malloc, free,
realloc and strdup. These functions are:

void *RedisModule_Alloc(size_t bytes);
void* RedisModule_Realloc(void *ptr, size_t bytes);
void RedisModule_Free(void *ptr);
void RedisModule_Calloc(size_t nmemb, size_t size);
char *RedisModule_Strdup(const char *str);

They work exactly like their libc equivalent calls, however they use
the same allocator Redis uses, and the memory allocated using these
functions is reported by the INFO command in the memory section, is
accounted when enforcing the maxmemory policy, and in general is
a first citizen of the Redis executable. On the contrar, the method
allocated inside modules with libc malloc() is transparent to Redis.

Another reason to use the modules functions in order to allocate memory
is that, when creating native data types inside modules, the RDB loading
functions can return deserialized strings (from the RDB file) directly
as RedisModule_Alloc() allocations, so they can be used directly to
populate data structures after loading, instead of having to copy them
to the data structure.

Pool allocator

Sometimes in commands implementations, it is required to perform many
small allocations that will be not retained at the end of the command
execution, but are just functional to execute the command itself.

This work can be more easily accomplished using the Redis pool allocator:

void *RedisModule_PoolAlloc(RedisModuleCtx *ctx, size_t bytes);

It works similarly to malloc(), and returns memory aligned to the
next power of two of greater or equal to bytes (for a maximum alignment
of 8 bytes). However it allocates memory in blocks, so it the overhead
of the allocations is small, and more important, the memory allocated
is automatically released when the command returns.

So in general short living allocations are a good candidates for the pool
allocator.

在模塊裏分配內存

在模塊寫的代碼和普通代碼一樣,當然也可以使用malloc等原始方式進行內存分配,但是是不推薦這麼做的,而應該使用Redis模塊化系統定製好的API。

void *RedisModule_Alloc(size_t bytes);
void* RedisModule_Realloc(void *ptr, size_t bytes);
void RedisModule_Free(void *ptr);
void RedisModule_Calloc(size_t nmemb, size_t size);
char *RedisModule_Strdup(const char *str);

顧名思義,這幾個函數很好理解,調用也很方便,和原始的內存分配不同,這幾個函數會使用和Redis相同的內存分配方式(jemalloc、tcmalloc、標準libc),這樣模塊也會根據環境來選擇最適合的內存分配方式。

使用這些定製的API還有一個好處,就是Redis內存可以根據要分配的內存場景進行特殊處理。

池化內存分配

當需要多次分配數據結構的內存,爲了防止多次向操作系統申請內存,可以合併操作,一次性向操作系統申請好要用的內存,然後在內部慢慢使用。

Redis模塊化系統也提供了池化分配內存的能力,可以使用PoolAlloc命令

void *RedisModule_PoolAlloc(RedisModuleCtx *ctx, size_t bytes);

申請的內存大小爲大於bytes的最小2次冪,池化分配減少了內存分配的開銷(減少申請次數),而且能夠用完後一次性釋放所有內存,避免碎片,也能很好的被自動內存管理器管理。

對於大量臨時的或者說生命週期短的內存分配,很適合使用池化分配方式。

Writing commands compatible with Redis Cluster

Documentation missing, please check the following functions inside module.c:

RedisModule_IsKeysPositionRequest(ctx);
RedisModule_KeyAtPos(ctx,pos);

模塊兼容集羣模式

還沒寫,自己看源代碼中的函數

RedisModule_IsKeysPositionRequest(ctx);
RedisModule_KeyAtPos(ctx,pos);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章