我需要能够在Redis中进行以下操作:
简单地说,这是一个“平衡”:如果我在此字段中有足够的空间,则可以使用它,否则,不可以。有时候,它必须减少很多余额
为此,我制作了一个LUA脚本来计算减量的结果,然后使用该结果修改字段。我选择此解决方案是因为:
我面临的问题:
输入“值”的格式如下: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
答案 0 :(得分:3)
如您的答案注释中所指出的那样,编写自己的模块将是非常适合您要求的一种选择。
这样的模块将用C编写。因此需要一个十进制库来满足金融应用程序的数学要求。
这里,我使用decNumber C库,该库最初由IBM编写。我使用以下链接进行测试:
演示
在查看代码之前,先做一个小演示:
如您所见,它可以任意精度工作。
诸如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(¤tNum, currentValueString, &set);
decNumberFromString(&decrementNum, decrementValueString, &set);
decNumber resultNum;
decNumberSubtract(&resultNum, ¤tNum, &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库复制到示例文件夹:
修改示例文件夹中的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脚本发送结果和您以前用于重做的事件了:
5)利润。
请注意,操作4应该在调用另一个操作2之前完成,您可以在redis中设置一个较旧的信号灯,例如item,以防止(如果操作4未完成,则阻止操作2运行的“忙”键,在启动第2步时将其清除,在第4步完成后将其清除,也可以在其上设置逐出操作,因此,如果出现问题,逐出操作将作为超时开始另一个迭代。)