Redis在LUA脚本中支持任意精度

Qua*_*ear 5 lua arbitrary-precision redis

我需要能够在Redis中进行以下操作:

  • 当且仅当结果> 0时,才减小n值
  • 否则,什么都不做
  • 处理任意精度的十进制数字(我需要使用浮点格式)
  • 可供其他进程访问

简而言之,这是一个“平衡”:如果我在此字段中有足够的空间,则可以使用它,否则,不可以。有时候,它必须减少很多余额

为此,我制作了一个LUA脚本来计算减量的结果,然后使用该结果修改字段。我选择此解决方案是因为:

我面临的问题:

  • 使用的lib不适合:它仅适用于整数,并且每次发送都太大(使用evalsha的事件,速度很慢)
  • 在Redis =>中编程Lua脚本时如何包含第三方库 =>接下来,我对在Redis上使用附加模块非常困惑。但是,这是从过去到现在。现在怎么样?
  • 我不确定是否有更有效的方法来做到这一点?欢迎提供有关代码本身的任何建议
  • Redis真的可以满足我的需求吗?

输入“值”的格式如下:Array <{键:字符串,字段:字符串,值:字符串// //实际上是一个BigNumber,具有字符串格式}>

this.redisClient.eval(`
    ${luaBigNumbers}

    local operations = cjson.decode(KEYS[1])
    local isStillValid = true
    local test

    for k, v in pairs(operations) do
      local temp = BigNum.new(redis.call('hget', v.key, v.field))
      local res = BigNum.mt.add(temp, BigNum.new(v.value))

      if BigNum.mt.lt(res, BigNum.new('0')) then
        isStillValid = false
      end
    end

    if isStillValid then
      for k, v in pairs(operations) do
        local temp = BigNum.new(redis.call('hget',v.key, v.field))
        redis.call('hset', v.key, v.field, BigNum.mt.tostring(BigNum.mt.add(temp, BigNum.new(v.value))))
      end
    end

    return tostring(isStillValid)`,
  1, JSON.stringify(values), (err, reply) => {
Run Code Online (Sandbox Code Playgroud)

TL; DR:我需要在Redis上有一个共享余额功能,如何做得好?

如果您对如何实现它有想法,请张贴在堆栈交换中https://softwareengineering.stackexchange.com/questions/391529/what-architecture-is-the-most-adapted-for-a-shared-balance-in-节点和可能

Ste*_*cht 5

正如您的答案的评论中所指出的,编写您自己的模块将是一个非常适合您的要求的选项。

这样的模块将用 C 语言编写。因此需要一个满足金融应用数学要求的十进制库。

这里我使用 decNumber C 库,这是一个最初由 IBM 编写的库。我使用以下链接进行测试:

演示

在看代码之前,先看一个小演示:

余额递减演示

正如您所看到的,它可以以任意精度工作。

balance.decrement mykey myfield "0.1"像递减这样的命令mykey myfield会使用作为最后一个字符串参数传递的值。新值存储在命令中mykey myfield并作为命令的结果输出。如果结果小于 0,则不会递减。然后NOP输出a。该操作是原子的。

模块源码

#include "../redismodule.h"
#include "../rmutil/util.h"
#include "../rmutil/strings.h"
#include "../rmutil/test_util.h"

#define  DECNUMDIGITS 34

#include "decNumber.h"


int decrementCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {

    if (argc != 4) {
        return RedisModule_WrongArity(ctx);
    }
    RedisModule_AutoMemory(ctx);

    RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ | REDISMODULE_WRITE);
    if (RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_HASH &&
        RedisModule_KeyType(key) != REDISMODULE_KEYTYPE_EMPTY) {
        return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
    }

    RedisModuleCallReply *currentValueReply = RedisModule_Call(ctx, "HGET", "ss", argv[1], argv[2]);
    RMUTIL_ASSERT_NOERROR(ctx, currentValueReply);

    RedisModuleString *currentValueRedisString = RedisModule_CreateStringFromCallReply(currentValueReply);
    if (!currentValueRedisString) {
        return 0;
    }
    const char *currentValueString = RedisModule_StringPtrLen(currentValueRedisString, NULL);
    const char *decrementValueString = RedisModule_StringPtrLen(argv[3], NULL);

    decNumber currentNum, decrementNum;
    decContext set;
    char resultStr[DECNUMDIGITS + 14];
    decContextDefault(&set, DEC_INIT_BASE);
    set.traps = 0;
    set.digits = DECNUMDIGITS;

    decNumberFromString(&currentNum, currentValueString, &set);
    decNumberFromString(&decrementNum, decrementValueString, &set);

    decNumber resultNum;
    decNumberSubtract(&resultNum, &currentNum, &decrementNum, &set);

    if (!decNumberIsNegative(&resultNum)) {
        decNumberToString(&resultNum, resultStr);
        RedisModuleCallReply *srep = RedisModule_Call(ctx, "HSET", "ssc", argv[1], argv[2], resultStr);
        RMUTIL_ASSERT_NOERROR(ctx, srep);

        RedisModule_ReplyWithStringBuffer(ctx, resultStr, strlen(resultStr));
        return REDISMODULE_OK;
    }

    if (RedisModule_CallReplyType(currentValueReply) == REDISMODULE_REPLY_NULL) {
        RedisModule_ReplyWithNull(ctx);
        return REDISMODULE_OK;
    }

    RedisModule_ReplyWithSimpleString(ctx, "NOP");
    return REDISMODULE_OK;
}


int RedisModule_OnLoad(RedisModuleCtx *ctx) {
    if (RedisModule_Init(ctx, "balance", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR) {
        return REDISMODULE_ERR;
    }
    RMUtil_RegisterWriteCmd(ctx, "balance.decrement", decrementCommand);
    return REDISMODULE_OK;
}
Run Code Online (Sandbox Code Playgroud)

如何构建和运行

我建议克隆https://github.com/RedisLabs/RedisModulesSDK。有一个示例文件夹。将 module.c 替换为上述模块代码。将以下文件从 decNumber C 库复制到示例文件夹:

  • decContext.h
  • decContext.c
  • 十进制数.h
  • decNumber.c
  • decNumberLocal.h

修改示例文件夹中的 Makefile,以使以 module.so 开头的行如下所示:

module.so: module.o decNumber.o decContext.o
    $(LD) -o $@ module.o decNumber.o decContext.o $(SHOBJ_LDFLAGS) $(LIBS) -L$(RMUTIL_LIBDIR) -lrmutil -lc 
Run Code Online (Sandbox Code Playgroud)

在基本目录中输入以下命令:

make clean
make
Run Code Online (Sandbox Code Playgroud)

然后您可以使用以下命令进行测试:

redis-server --loadmodule ./module.so
Run Code Online (Sandbox Code Playgroud)

这就是您要找的吗?