如何在 StackExchange.Redis 中实现带 XX 标志的 ZINCRBY?
可以直接实现,无须单独编写 Lua。StackExchange.Redis 从 2.12.27 起提供带 ValueCondition 参数的 SortedSetIncrement / SortedSetIncrementAsync 重载;旧版本可以通过 ExecuteAsync 直接发送 ZADD XX INCR。版本说明
新版本:使用条件递增重载
项目使用 StackExchange.Redis 2.12.27 或支持该重载的后续版本时,可以按下面的方式调用。db 是已有的 IDatabase 实例:
using StackExchange.Redis;
double? newScore = await db.SortedSetIncrementAsync(
key: "testSet",
member: "item1",
value: 5,
when: ValueCondition.Exists,
flags: CommandFlags.None);
if (newScore.HasValue)
{
// 递增成功,newScore.Value 是递增后的 score。
}
else
{
// 成员不存在,没有执行递增,也没有创建成员。
}
ValueCondition.Exists 对应 XX。这个重载通过 ZADD 执行条件递增,返回类型是 double?,可以区分递增后的分数为 0 和成员不存在、未执行操作这两种情况。普通的无条件重载仍使用 ZINCRBY。实现源码
旧版本:直接发送 ZADD XX INCR
如果当前 NuGet 包没有上述重载,可以使用通用命令接口:
using StackExchange.Redis;
RedisKey key = "testSet";
RedisValue member = "item1";
double increment = 5;
RedisResult result = await db.ExecuteAsync(
"ZADD", key, "XX", "INCR", increment, member);
double? newScore = result.IsNull
? (double?)null
: (double)result;
这次调用发送的命令是:
ZADD testSet XX INCR 5 item1
命令名与参数要分别传递,不要把整条命令拼进第一个字符串。键使用 RedisKey 类型,客户端才能识别它并计算 Redis Cluster 的路由槽位。命令接口、路由实现
处理返回值时,先检查 IsNull,再转换为 double。Redis 的空回复由 RedisResult 表示,不能只用 result == null 判断。RedisResult 源码
为什么使用 ZADD,而不是给 ZINCRBY 加 XX
ZINCRBY 没有 XX 选项。要实现条件递增,需要组合使用 ZADD 的两个选项:
XX:只更新已经存在的成员。INCR:将传入的 score 作为增量,而不是直接覆盖原分数。
如果 item1 原来的 score 是 10,执行示例命令后会返回 15;如果 item1 不存在,则返回空值,也不会添加成员。这里要求目标成员存在,仅有 testSet 这个键还不够。XX、INCR 选项从 Redis 3.0.2 起可用,且 INCR 模式一次只能传入一组 score/member。ZADD 命令文档
并发时需要注意什么
不要先在客户端调用 SortedSetScoreAsync 检查成员是否存在,再调用普通的 SortedSetIncrementAsync。其他客户端可能在两次调用之间删除成员,后续递增就会重新创建它。
ZADD XX INCR 在同一条命令中完成存在性判断和递增,避免了检查与修改之间的并发空隙。
另外,SortedSetAdd 即使带上存在性条件,执行的仍是设置 score;缺少 INCR 就不会累加。返回的 null 也不要转换成 0,因为递增成功后的 score 也可能是 0,转换后会混淆这两种结果。
备注:内容仅供参考。
