当前位置: 首页 > article >正文

【Redis】事务因WATCH的键被修改而失败 事务队列中的操作被自动丢弃 UNWATCH的应用场景

文章目录

  • 事务因WATCH的键被修改而失败 事务队列中的操作被自动丢弃 重新执行事务会导致额外的开销
      • 1. **减少事务冲突的概率**
      • 2. **避免频繁重试的开销**
      • 3. **使用Lua脚本替代事务**
      • 4. **乐观锁机制**
      • 5. **批量操作**
      • 6. **分布式锁**
      • 7. **监控和调优**
      • 总结
      • **`WATCH` 的作用**
      • **`UNWATCH` 的作用**
  • **`UNWATCH` 的应用场景**
        • 1. **显式取消监控**
        • 2. **事务失败后的清理**
        • 3. **长生命周期连接中的监控管理**
        • 4. **避免监控状态的误用**
      • **总结**

在这里插入图片描述

事务因WATCH的键被修改而失败 事务队列中的操作被自动丢弃 重新执行事务会导致额外的开销


1. 减少事务冲突的概率

  • 优化数据模型:尽量减少对同一个键的频繁修改。可以通过将数据分散到多个键(例如哈希分片)来降低冲突概率。
  • 减少事务范围:只将必要的操作放入事务中,避免长时间持有WATCH的键。
  • 使用更细粒度的锁:如果可能,将一个大事务拆分为多个小事务,减少冲突的可能性。

2. 避免频繁重试的开销

  • 指数退避重试:在事务失败后,采用指数退避策略(Exponential Backoff)进行重试,避免在高并发下频繁重试导致的开销。
    • 例如:第一次失败后等待100ms,第二次失败后等待200ms,第三次失败后等待400ms,以此类推。
  • 限制重试次数:设置最大重试次数,超过次数后放弃事务或记录日志,避免无限重试。
  • 异步重试:将失败的事务放入一个队列中,由后台任务异步重试,避免阻塞主线程。

3. 使用Lua脚本替代事务

Redis支持执行Lua脚本,脚本中的操作是原子性的,且不需要WATCH机制。通过将事务逻辑封装在Lua脚本中,可以避免事务冲突和重试的开销。

优点

  • Lua脚本在Redis中是原子执行的,不需要WATCH
  • 减少了客户端与服务器之间的通信开销。
  • 避免了事务失败后的重试问题。

示例

local key = KEYS[1]
local value = redis.call('GET', key)
if not value then
    value = 0
end
redis.call('SET', key, value + 1)
return value + 1

在C++中调用Lua脚本:

redisReply* reply = (redisReply*)redisCommand(context, "EVAL %s 1 mykey", luaScript);
if (reply) {
    printf("Result: %s\n", reply->str);
    freeReplyObject(reply);
}

4. 乐观锁机制

如果必须使用WATCH,可以通过引入版本号或时间戳来实现乐观锁,减少冲突概率。

实现方式

  • 在键的值中存储一个版本号或时间戳。
  • 在事务中检查版本号或时间戳是否发生变化,如果变化则放弃事务。

示例

void optimisticTransaction(redisContext* context, const std::string& key) {
    int retries = 3;
    while (retries--) {
        redisReply* reply = (redisReply*)redisCommand(context, "WATCH %s", key.c_str());
        freeReplyObject(reply);

        reply = (redisReply*)redisCommand(context, "GET %s", key.c_str());
        int version = reply ? atoi(reply->str) : 0;
        freeReplyObject(reply);

        reply = (redisReply*)redisCommand(context, "MULTI");
        freeReplyObject(reply);

        // 添加事务操作
        reply = (redisReply*)redisCommand(context, "INCR %s", key.c_str());
        freeReplyObject(reply);

        reply = (redisReply*)redisCommand(context, "EXEC");
        if (reply != nullptr) {
            // 事务成功
            freeReplyObject(reply);
            break;
        } else {
            // 事务失败,重试
            freeReplyObject(reply);
            std::this_thread::sleep_for(std::chrono::milliseconds(100 * (3 - retries))); // 指数退避
        }
    }
}

5. 批量操作

如果事务中的操作是独立的,可以将多个操作合并为一个批量操作,减少事务的使用频率。

示例

  • 使用MGETMSET等批量命令。
  • 使用Pipeline将多个命令一次性发送到Redis,减少通信开销。

6. 分布式锁

如果事务冲突非常频繁,可以考虑使用分布式锁(如Redlock)来保护关键资源,确保同一时间只有一个客户端可以修改数据。

注意

  • 分布式锁会引入额外的复杂性和性能开销,需谨慎使用。
  • 仅在必要时使用分布式锁,避免过度设计。

7. 监控和调优

  • 监控事务冲突率:通过监控工具(如Redis的INFO命令)观察事务冲突的频率,定位热点键。
  • 动态调整重试策略:根据监控结果动态调整重试次数和退避时间。

总结

  • 优先使用Lua脚本:Lua脚本可以避免事务冲突和重试开销,是最推荐的解决方案。
  • 优化数据模型和事务范围:减少事务冲突的概率。
  • 指数退避和异步重试:降低重试带来的开销。
  • 乐观锁和批量操作:进一步优化性能。

通过以上方法,可以有效减少因WATCH失败导致的事务重试开销,提升系统性能。
UNWATCH 是 Redis 中与 WATCH 配合使用的命令,用于取消对所有键的监控。理解 UNWATCH 的应用场景需要先明确 WATCH 的作用。


WATCH 的作用

  • WATCH 用于监控一个或多个键,如果在事务执行期间(即 MULTIEXEC 之间),这些键被其他客户端修改,当前客户端的事务会失败(EXEC 返回 nil)。
  • WATCH 提供了一种乐观锁机制,确保事务的原子性。

UNWATCH 的作用

  • UNWATCH 用于取消对所有键的监控。
  • 调用 UNWATCH 后,之前通过 WATCH 监控的键将不再被监控,即使这些键被其他客户端修改,也不会影响当前客户端的事务。

UNWATCH 的应用场景

1. 显式取消监控

在某些场景下,客户端可能希望显式地取消对键的监控,而不是等待事务执行完成(EXECDISCARD)后自动取消监控。

示例场景

  • 客户端在监控某些键后,发现某些条件不满足,决定不执行事务。
  • 此时可以调用 UNWATCH 取消监控,避免不必要的资源占用。

代码示例

redisReply* reply = (redisReply*)redisCommand(context, "WATCH mykey");
freeReplyObject(reply);

// 检查某些条件
if (someConditionIsNotMet) {
    // 取消监控
    reply = (redisReply*)redisCommand(context, "UNWATCH");
    freeReplyObject(reply);
    return;
}

// 执行事务
reply = (redisCommand(context, "MULTI"));
freeReplyObject(reply);
reply = (redisCommand(context, "INCR mykey"));
freeReplyObject(reply);
reply = (redisCommand(context, "EXEC"));
freeReplyObject(reply);

2. 事务失败后的清理

当事务因 WATCH 的键被修改而失败时,Redis 会自动取消对所有键的监控。但在某些情况下,客户端可能希望显式调用 UNWATCH 来确保状态清理。

示例场景

  • 事务失败后,客户端可能需要执行一些清理操作,此时可以显式调用 UNWATCH 以确保没有残留的监控状态。

代码示例

redisReply* reply = (redisReply*)redisCommand(context, "WATCH mykey");
freeReplyObject(reply);

reply = (redisReply*)redisCommand(context, "MULTI");
freeReplyObject(reply);
reply = (redisReply*)redisCommand(context, "INCR mykey");
freeReplyObject(reply);
reply = (redisReply*)redisCommand(context, "EXEC");

if (reply == nullptr) {
    // 事务失败,显式取消监控
    reply = (redisReply*)redisCommand(context, "UNWATCH");
    freeReplyObject(reply);
} else {
    // 事务成功
    freeReplyObject(reply);
}

3. 长生命周期连接中的监控管理

在长生命周期的连接中(例如连接池中的连接),客户端可能需要多次使用 WATCH 和事务。为了避免前一次的监控状态影响后续操作,可以在每次事务结束后显式调用 UNWATCH

示例场景

  • 连接池中的连接被多个任务共享,每个任务可能使用不同的键。
  • 为了避免前一个任务的监控状态影响后续任务,可以在任务结束时调用 UNWATCH

代码示例

void executeTask(redisContext* context, const std::string& key) {
    // 监控键
    redisReply* reply = (redisReply*)redisCommand(context, "WATCH %s", key.c_str());
    freeReplyObject(reply);

    // 执行事务
    reply = (redisReply*)redisCommand(context, "MULTI");
    freeReplyObject(reply);
    reply = (redisReply*)redisCommand(context, "INCR %s", key.c_str());
    freeReplyObject(reply);
    reply = (redisReply*)redisCommand(context, "EXEC");

    if (reply == nullptr) {
        // 事务失败
        freeReplyObject(reply);
    } else {
        // 事务成功
        freeReplyObject(reply);
    }

    // 显式取消监控,确保连接可以安全地用于其他任务
    reply = (redisReply*)redisCommand(context, "UNWATCH");
    freeReplyObject(reply);
}

4. 避免监控状态的误用

在某些复杂的业务逻辑中,客户端可能会在多个地方使用 WATCH,如果不及时清理监控状态,可能会导致意外的行为。

示例场景

  • 客户端在多个函数中分别调用了 WATCH,但某些函数可能没有执行事务。
  • 为了避免监控状态的累积,可以在每个函数的最后调用 UNWATCH

代码示例

void functionA(redisContext* context) {
    redisReply* reply = (redisReply*)redisCommand(context, "WATCH key1");
    freeReplyObject(reply);

    // 某些逻辑
    if (someCondition) {
        return; // 提前返回
    }

    // 显式取消监控
    reply = (redisReply*)redisCommand(context, "UNWATCH");
    freeReplyObject(reply);
}

void functionB(redisContext* context) {
    redisReply* reply = (redisReply*)redisCommand(context, "WATCH key2");
    freeReplyObject(reply);

    // 某些逻辑
    if (someCondition) {
        return; // 提前返回
    }

    // 显式取消监控
    reply = (redisReply*)redisCommand(context, "UNWATCH");
    freeReplyObject(reply);
}

总结

UNWATCH 的主要应用场景包括:

  1. 显式取消监控,避免不必要的资源占用。
  2. 事务失败后的状态清理。
  3. 长生命周期连接中的监控管理。
  4. 避免监控状态的误用。

在复杂的业务逻辑中,合理使用 UNWATCH 可以确保 Redis 连接的状态清晰,避免意外的行为。


http://www.kler.cn/a/537039.html

相关文章:

  • 硬件工程师思考笔记02-器件的隐秘角落:磁珠与电阻噪声
  • 华水967数据结构2024真题(回忆版)
  • JVM执行流程与架构(对应不同版本JDK)
  • pycharm集成通义灵码应用
  • hot100(9)
  • 使用Deepseek搭建本地知识库-Cherry Studio
  • 视频编辑质量评价的开源项目 VE-Bench 介绍
  • 使用deepseek快速创作ppt
  • 基于物联网技术的智能寻车引导系统方案:工作原理、核心功能及系统架构
  • 如何设置Jsoup请求头模拟浏览器访问?
  • redis之AOF持久化过程
  • Plugin有什么作用?Plugin是什么?
  • 探索robots.txt:网站管理者的搜索引擎指南
  • yolov11模型在Android设备上运行【踩坑记录】
  • 【面试】Java高频面试题(2023最新版)
  • e2studio开发RA2E1(9)----定时器GPT配置输入捕获
  • 5.2Internet及其作用
  • EasyExcel 导出合并层级单元格
  • 技术选型对比:Redis 与 MySQL、Dubbo 与 Spring Cloud
  • Baumer工业相机堡盟相机的相机传感器芯片清洁指南
  • QT全局所有QSS样式实时切换
  • 《机器学习数学基础》补充资料:秩-零化度定理
  • 【AI应用】免费的文本转语音工具:微软 Edge TTS 和 开源版 ChatTTS 对比
  • FPGA实现SDI视频缩放转UltraScale GTH光口传输,基于GS2971+Aurora 8b/10b编解码架构,提供2套工程源码和技术支持
  • flutter安卓打包签名
  • 客户端脚本安全设置:如何保障您的Web应用免受攻击?