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

时间:2019-04-26 17:35:46

标签: redis lua arbitrary-precision

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

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

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

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

我面临的问题:

  • 所用的lib不适合:仅用于整数,并且每次发送都太大(使用evalsha的事件,速度很慢)
  • How to include third party library when programming Lua script in Redis =>在那之后,我对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) => {

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

如果您有实现想法的想法,请张贴在堆栈交换中https://softwareengineering.stackexchange.com/questions/391529/what-architecture-is-the-most-adapted-for-a-shared-balance-in-nodejs-and-maybe

2 个答案:

答案 0 :(得分:3)

如您的答案注释中所指出的那样,编写自己的模块将是非常适合您要求的一种选择。

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

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

演示

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

balance decrement demo

如您所见,它可以任意精度工作。

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

模块源

#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;
}

如何构建和运行

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

  • decContext.h
  • decContext.c
  • decNumber.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 

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

make clean
make

您可以使用以下命令对其进行测试:

redis-server --loadmodule ./module.so

您正在寻找什么吗?

答案 1 :(得分:1)

也许从事件源模式中得到启发可以解决您的问题。实现原子性的另一种方法是将写入角色限制为仅一个处理器,其命令将始终按时间排序。 (就像带lua的redis一样)

1)您将存储在排序集中的余额变化的“事件”发送到redis(对于时间排序,时间戳是分数)。仅存储要执行的“命令”(而不存储计算的结果)。例如“ -1.545466”,“ + 2.07896”等...

2)然后,您可以通过Lua脚本从单个处理器使用这些事件(您必须确保只有一个计算项可以访问该数据,否则您将有麻烦),可以通过调用该循环的方式来调用这些事件。每n秒(您可以定义您的实时质量)脚本,例如Apache Storm(“喷口”)。 脚本应该返回从最早的时间戳到最新的时间戳的事件,还应该返回时间戳(分数)(如果没有它们,您将丢失“索引”),当然还有实际的余额。

您应该获得如下所示的值:

balance= +5
ZSET=
"-6" score 1557782182
"+2" score 1557782772
"+3" score 1678787878

3)在中间件服务器(唯一的允许修改余额的服务器)中,计算余额的更改(使用服务器中所需的任何lib / tech都应快如闪电)。您只需遍历事件以每次计算余额。请注意,由于这一点,您将在redis中进行较少的变异。

您应该得到结果

old_balance=5
new_balance=10
ZSET=
"-6" score 1557782182
"+2" score 1557782772
"+3" score 1678787878

4)一旦在服务器中计算了新的余额值,就该通过Lua脚本发送结果和您以前用于重做的事件了:

  • 更新余额值,因为仅允许一个过程对其进行修改,所以您不会遇到任何交易问题,还应始终对其进行正确的时间排序
  • 整理排序后的计算事件集(将使用第2步中使用的最旧和最新时间​​戳记),以使这些事件不会在下次lua调用时再次处理

5)利润。

请注意,操作4应该在调用另一个操作2之前完成,您可以在redis中设置一个较旧的信号灯,例如item,以防止(如果操作4未完成,则阻止操作2运行的“忙”键,在启动第2步时将其清除,在第4步完成后将其清除,也可以在其上设置逐出操作,因此,如果出现问题,逐出操作将作为超时开始另一个迭代。)

相关问题