Java 实现幂等性:原理与实践

在分布式系统中,幂等性(Idempotency)是一个非常重要的概念。幂等性操作指的是:无论这个操作执行多少次,结果都应该是相同的。这是为了避免重复执行操作引起数据的不一致,尤其是在网络抖动、服务重试等场景中尤为关键。

本文将通过一些实际的代码示例,介绍在 Java 中如何实现幂等性,结合常见的框架如 Spring BootRedis数据库 进行实现。


一、为什么需要幂等性?

在分布式环境下,由于 网络故障服务超时 或者 消息重复消费,同一个请求可能会被发送或处理多次。例如:

  1. 支付接口:用户点击支付按钮后,请求可能会因为超时被重复发起,导致订单被重复支付。
  2. 消息队列:在消息消费时,消费者可能会因为处理失败而重新消费同一条消息。

为了避免这些情况,确保某些操作具备幂等性显得尤为重要。


二、实现幂等性的常见方法

在 Java 中,常见的实现幂等性的方法包括:

  1. 唯一请求标识(Request ID)
  2. 数据库主键约束
  3. 基于 Redis 的幂等性
  4. Token 机制

1. 使用唯一请求标识(Request ID)

通过为每个请求生成一个 唯一的请求 ID,并在处理之前检查该 ID 是否已经处理过,从而避免重复处理。

代码示例:
import java.util.concurrent.ConcurrentHashMap;public class IdempotencyService {private final ConcurrentHashMap<String, Boolean> processedRequests = new ConcurrentHashMap<>();public boolean processRequest(String requestId, Runnable operation) {if (processedRequests.containsKey(requestId)) {System.out.println("Request already processed: " + requestId);return false; // 已处理,忽略该请求}processedRequests.put(requestId, true);operation.run();return true;}
}

解释

  • requestId 是由客户端生成的,每个请求都有唯一的 ID。
  • processedRequests 是一个线程安全的哈希表,用于存储已处理过的请求。
  • 如果请求已存在,则不执行操作,保证了幂等性。

2. 基于数据库的幂等性:唯一约束

另一种常见的幂等性实现是通过数据库中的 唯一约束。例如,在订单处理系统中,可以利用订单号作为唯一标识,如果重复处理请求,数据库会抛出异常,从而避免重复创建记录。

代码示例:
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;@Service
public class OrderService {public void createOrder(String orderId) {try {// 假设 orderId 是唯一的saveOrderToDatabase(orderId);System.out.println("Order created: " + orderId);} catch (DataIntegrityViolationException e) {System.out.println("Duplicate order detected: " + orderId);}}private void saveOrderToDatabase(String orderId) {// 保存订单到数据库,orderId 是唯一约束字段// INSERT INTO orders (order_id) VALUES (orderId);}
}

解释

  • 订单号(orderId) 在数据库中被设置为唯一索引,如果重复插入会抛出异常。
  • 捕获该异常并忽略后续处理,保证每个订单只处理一次。

3. 基于 Redis 实现幂等性

Redis 提供了高效的键值存储,我们可以利用 Redis 的 SETNX(SET if Not Exists) 命令来确保同一个操作只会执行一次。

代码示例:
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;@Service
public class RedisIdempotencyService {private final StringRedisTemplate redisTemplate;public RedisIdempotencyService(StringRedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;}public boolean processRequest(String requestId, Runnable operation) {Boolean isFirstProcess = redisTemplate.opsForValue().setIfAbsent(requestId, "processed", 10, TimeUnit.MINUTES);if (Boolean.FALSE.equals(isFirstProcess)) {System.out.println("Request already processed: " + requestId);return false; // 已处理}operation.run();return true;}
}

解释

  • setIfAbsent(SETNX) 确保当键不存在时才能设置该键,保证请求只处理一次。
  • 过期时间:为键设置一个合理的过期时间,防止因系统故障导致的资源泄漏。

4. Token 机制

Token 机制常用于防止 表单重复提交 的场景。客户端在每次请求时携带一个唯一的 Token,该 Token 只能使用一次。

代码示例:
import org.springframework.stereotype.Service;import java.util.HashSet;
import java.util.Set;@Service
public class TokenService {private final Set<String> usedTokens = new HashSet<>();public boolean validateAndProcess(String token, Runnable operation) {synchronized (usedTokens) {if (usedTokens.contains(token)) {System.out.println("Token already used: " + token);return false;}usedTokens.add(token);}operation.run();return true;}
}

解释

  • Token 是一个客户端生成的唯一标识,提交表单时一起发送给服务端。
  • 服务端在处理时检查该 Token 是否已使用,如果已使用,则不处理当前请求。

三、Spring Boot 实践:订单服务中的幂等性

在微服务架构中,幂等性往往应用于 订单创建支付处理 等业务场景。以下是一个使用 Spring Boot数据库唯一约束 来实现幂等订单处理的完整示例。

1. 创建订单请求控制器

import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/orders")
public class OrderController {private final OrderService orderService;public OrderController(OrderService orderService) {this.orderService = orderService;}@PostMapping("/create")public String createOrder(@RequestParam String orderId) {boolean success = orderService.createOrder(orderId);return success ? "Order created successfully" : "Duplicate order detected";}
}

2. 订单服务逻辑

import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;@Service
public class OrderService {public boolean createOrder(String orderId) {try {saveOrderToDatabase(orderId);System.out.println("Order created: " + orderId);return true;} catch (DataIntegrityViolationException e) {System.out.println("Duplicate order detected: " + orderId);return false;}}private void saveOrderToDatabase(String orderId) {// 将订单保存到数据库,假设订单号唯一// INSERT INTO orders (order_id) VALUES (orderId);}
}

3. 数据库设计

在订单表中,订单号 应该被设置为唯一索引,防止重复插入。

CREATE TABLE orders (id BIGINT AUTO_INCREMENT PRIMARY KEY,order_id VARCHAR(255) NOT NULL,UNIQUE (order_id)
);

解释

  • order_id 被设置为唯一约束,保证了重复订单不会插入。

四、分布式幂等性处理中的注意事项

在分布式环境中,幂等性的实现有一些需要特别注意的地方:

1. 消息队列中的幂等性

当使用 消息队列(如 Kafka、RabbitMQ)时,消费者需要具备幂等性,防止同一条消息被重复消费。可以通过以下几种方式实现:

  1. 消息唯一 ID:每条消息都带有一个唯一的 ID,消费者在处理消息时检查该 ID 是否已处理。
  2. 消费偏移量管理:通过记录消费的偏移量,确保每条消息只消费一次。
Kafka 消费者代码示例:
@KafkaListener(topics = "orders")
public void listen(ConsumerRecord<String, String> record) {String messageId = record.key(); // 唯一消息IDif (!isProcessed(messageId)) {processOrder(record.value());markAsProcessed(messageId);}
}

2. 数据库幂等性与分布式事务

在涉及多个微服务的分布式系统中,幂等性往往需要与 分布式事务 配合。例如,在 支付服务 中,支付的结果需要同时更新订单状态和账户余额。可以通过 **分布式

事务管理器** 或 Saga 模式 来确保事务一致性。


总结

幂等性是分布式系统中非常重要的设计原则。在 Java 中,可以通过 唯一标识数据库唯一约束Redis 锁Token 机制 来实现幂等性。在复杂的分布式系统中,还需要结合 消息队列分布式事务 的方案,确保操作的一致性和正确性。

  1. 唯一请求标识 是实现幂等性的基础,它可以保证每个操作只执行一次。
  2. 数据库的唯一约束Redis 的 SETNX 是常见的幂等性实现方式。
  3. 在分布式系统中,幂等性与 事务一致性 密不可分,尤其是在涉及消息队列和跨服务调用的场景中。

通过合理的幂等性设计,系统可以更好地应对各种异常情况,确保业务数据的一致性和可靠性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/146535.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

重生奇迹MU 强化玩法套路多 极品装备由你打造

欢迎来到重生奇迹MU的强化玩法指南&#xff01;想要打造极品装备吗&#xff1f;不可错过这篇文章&#xff0c;我们将为您揭开最多套路的强化技巧和窍门&#xff0c;帮您节省时间和资源&#xff0c;并带来最高效的升级结果。无论您是新手还是老玩家&#xff0c;本文适合所有级别…

AI浪潮新崛起:借助AI+实景/视频直播创新魅力,开启无人自动直播新时代!

AI浪潮新崛起&#xff1a;借助AI实景/视频直播创新魅力&#xff0c;开启无人自动直播新时代&#xff01; 在科技日新月异的今天&#xff0c;人工智能&#xff08;AI&#xff09;已不再仅仅是科幻电影中的桥段&#xff0c;它正以不可阻挡之势渗透到我们生活的方方面面&#xff…

工业物联网的海量数据如何呈现,可视化设计来助力

工业物联网产生的海量数据需要通过可视化设计来呈现&#xff0c;以帮助用户更好地理解和分析数据。 数据汇总和聚合&#xff1a; 对于大量的数据&#xff0c;可以通过汇总和聚合的方式来减少数据的数量&#xff0c;同时保留关键的信息。例如&#xff0c;将时间序列数据按照小…

Excel 冻结多行多列

背景 版本&#xff1a;office 2021 专业版 无法像下图内某些版本一样&#xff0c;识别选中框选的多行多列。 如下选中后毫无反应&#xff0c;点击【视图】->【冻结窗口】->【冻结窗格】后自动设置为冻结第一列。 操作 如下&#xff0c;要把前两排冻结起来。 选择 C1&a…

2024华为杯研赛D题保姆级教程思路分析+教程

2024年中国研究生数学建模竞赛D题保姆级教程思路分析 D题&#xff1a;大数据驱动的地理综合问题&#xff08;数学分析&#xff0c;统计学&#xff09; 关键词&#xff1a;地理、气候、统计&#xff08;细致到此题&#xff1a;统计指标、统计模型、统计结果解释&#xff09; …

视频压缩篇:适用于 Windows 的 10 款最佳视频压缩器

视频压缩器现在对许多想要减小视频大小的视频编辑者来说非常有名。但是&#xff0c;并非所有可以在网上找到的视频压缩器都能产生最佳输出。因此&#xff0c;我们搜索了可以无损压缩视频的最出色的视频压缩器应用程序。本文列出了您可以在离线、在线和手机上使用的十大最佳视频…

FastAdmin列表用echats渲染,使用表格的templateView实现一个图表渲染的功能

前言 FastAdmin中Bootstrap-table表格参数templateView拥有强大的自定义功能&#xff0c;这里我们使用templateView来实现一个图表渲染的功能。 首先我们itemtpl模板中的数据需要填充为一个JSON数据&#xff0c;包含column和data两列 ,chartdata为我们服务器返回的行中的数据。…

力扣-1035不相交的线(Java详细题解)

题目链接&#xff1a;力扣-1035不相交的线 前情提要&#xff1a; 因为本人最近都来刷dp类的题目所以该题就默认用dp方法来做。 dp五部曲。 1.确定dp数组和i下标的含义。 2.确定递推公式。 3.dp初始化。 4.确定dp的遍历顺序。 5.如果没有ac打印dp数组 利于debug。 每一…

nginx模块篇(四)

文章目录 四、Nginx的扩展模块4.1. Lua4.1.1 概念4.1.2 特性4.1.3 应用场景4.1.4 Lua的安装4.1.5 Lua的语法4.1.5.1 第一个Lua程序4.1.5.2 Lua的注释4.1.5.3 标识符4.1.5.4 关键字4.1.5.5 运算符4.1.5.6 全局变量&局部变量4.1.5.7 Lua数据类型nilbooleannumberstringtablef…

串口助手的qt实现思路

要求实现如下功能&#xff1a; 获取串口号&#xff1a; foreach (const QSerialPortInfo &serialPortInfo, QSerialPortInfo::availablePorts()) {qDebug() << "Port: " << serialPortInfo.portName(); // e.g. "COM1"qDebug() <<…

绿色数据中心:实现可持续发展和具备盈利能力的全闪存解决方案

数据中心成为了当今数字世界的支柱&#xff0c;负责存储、处理和分发驱动几乎所有数字服务产生&#xff08;从网上银行到即时消息&#xff09;的数据。这使得数字中心逐渐成为了现代商业基础设施的关键组成部分。 但是&#xff0c;随之而来的是&#xff0c;数据中心也已经成为…

基于asp.net固定资产管理系统设计与实现

博主介绍&#xff1a;专注于Java vue .net php phython 小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作 ☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不然下次找不到哟 我的博客空间发布了1000毕设题目 方便大家学习使用 感兴趣的…

如何着手创建企业数据目录?(三)权限管理及版本控制

前文导读&#xff1a; 《如何着手创建企业数据目录&#xff1f;&#xff08;一&#xff09;数据目录的设定》 《如何着手创建企业数据目录&#xff1f;&#xff08;二&#xff09;数据的命名与维护》 前面聊过了数据目录的设定、数据命名规则和维护规则&#xff0c;今天我们继续…

34.打字机效果 水平滚动贴合

打字机效果 创建打字机效果动画。 定义两个动画,typing 用于字符动画,blink 用于光标动画。使用 ::after 伪元素在容器元素中添加光标。使用 JavaScript 为内部元素设置文本,并设置包含字符数的 --characters 变量。这个变量用于文本动画。使用 white-space: nowrap 和 overflo…

【华为杯】2024数学建模研赛题目

2024数学建模研赛题目已经发布 各个赛题题目如下&#xff1a; A题 B题 C题 D题 E题 F题 赛题完整版在文末&#xff0c;点击下方名片。

离散型制造业MES系统主要功能介绍

一、离散型制造业的特点 离散型制造业是指生产过程中涉及多个独立工序或步骤&#xff0c;且这些工序之间相对独立、缺乏连续性的企业。其特点主要包括&#xff1a; 产品种类多&#xff0c;开发频繁&#xff1a; 离散型制造业通常需要进行多品种产品开发&#xff0c;产品种类繁…

OpenCV特征检测(2)边缘检测函数Canny()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 使用 Canny 算法 48在图像中查找边缘。 该函数使用 Canny 算法在输入图像中查找边缘&#xff0c;并在输出地图 edges 中标记它们。在 threshold1…

微服务架构---Ribbon\Feign

Ribbon(负载均衡) Ribbon概述 在 SpringCloud 中&#xff0c; Nacos⼀般配合Ribbon进行使用&#xff0c;Ribbon提供了客户端负载均衡的功能&#xff0c;Ribbon利用从Nacos中读取到的服务信息&#xff0c;在调用服务节点提供的服务时&#xff0c;会合理的进行负载。 Ribbon作…

Java内部类一口气讲完!( •̀ ω •́ )✧

Java 内部类 Java面向对象的设计 - Java 内部类 什么是内部类&#xff1f; 作为包的成员的类被称为顶级类。 一个类可以在另一个类中声明。这种类型的类称为内部类。 如果在另一个类中声明的类被显式或隐式声明为static&#xff0c;它被称为嵌套类&#xff0c;而不是内部类…

Apache Flink 流批融合技术介绍

摘要&#xff1a;本文整理自阿里云高级研发工程师、Apache Flink Contributor 周云峰老师在 Apache Asia CommunityOverCode 2024中的分享。内容主要分为以下三个部分&#xff1a; 从流批一体到流批融合流批融合的技术解决方案社区进展及未来展望 一、从流批一体到流批融合 1&…