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

news2024/9/21 10:29:35

在分布式系统中,幂等性(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(); // 唯一消息ID
    if (!isProcessed(messageId)) {
        processOrder(record.value());
        markAsProcessed(messageId);
    }
}

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

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

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


总结

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

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

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

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

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

相关文章

Hutool:Java开发者的瑞士军刀

有想念&#xff0c;才是团圆&#xff1b;在一起&#xff0c;便是中秋。 在Java的世界里&#xff0c;有这样一个工具库&#xff0c;它小巧而强大&#xff0c;功能丰富且易于使用&#xff0c;它就是Hutool。Hutool是一个Java工具包&#xff0c;旨在减少Java开发人员在开发过程中…

Oracle数据库逻辑与物理结构操作

一、实验步骤 1、查询所有DBA和USER开头的静态数据字典 2、查询所有V$动态性能视图 3、查询当前数据库中的表空间信息 ①查询和TABLESPACE相关的数据字典 ②通过动态性能视图查询表空间信息 ③通过数据字典查询表空间信息 4、操作数据文件 &#xff08;1&#xff09;向 ORC…

JavaWeb JavaScript 11.XML —— 配置文件

生活想埋没我&#xff0c;没想到我是颗种子 —— 24.9.19 一、XML 1.什么是XML XML是EXtensible Markup Languge的缩写&#xff0c;翻译过来就是可扩展标记语言。所以很明显&#xff0c;XML和HTML一样都是标记语言&#xff0c;也就是说它们的基本语法都是标签 可扩展 三个字…

网络:UDP协议

个人主页 &#xff1a; 个人主页 个人专栏 &#xff1a; 《数据结构》 《C语言》《C》《Linux》 文章目录 前言UDP协议报头和有效载荷分离的问题有效载荷向上交付的问题&#xff0c;也就是交给哪个进程&#xff1f;怎么确定把报文收全了&#xff1f;UDP报头是如何封装的呢&…

JavaWeb纯小白笔记02:Tomcat的使用:发布项目的三种方式、配置虚拟主机、配置用户名和密码

通过Tomcat进行发布项目的目的是为了提供项目的访问能力&#xff1a;Tomcat作为Web服务器&#xff0c;能够处理HTTP请求和响应&#xff0c;将项目的内容提供给用户进行访问和使用。 一.Tomcat发布项目的三种方式&#xff1a; 第一种&#xff1a;直接在Tomcat文件夹里的webapp…

数学建模 第一讲 - 概论

一、什么是数学模型 一个栗子 例 1.1 一只装满水的圆柱型桶&#xff0c;底半径为 1米&#xff0c;高为 2米&#xff0c;底部有一直径为 0.1 米的洞。问桶流空要多少时间? 数学模型是对于一个特定的对象为了一个特定目标&#xff0c;根据事物的内在规律&#xff0c;作出一些必…

防止用户过于轻松采集网页内容的方法

面对AI&#xff0c;所有禁止采集网页内容的功能都是徒劳&#xff0c;最不济截图后采集文字总简单了吧&#xff1f;能做的就是增加一点点人工采集的难度。 以下总结一下 一、注册用户 必须注册才能浏览全部内容&#xff0c;那么这样就可以针对用户控制其浏览次数&#xff0c;浏…

ActiveMQ、RabbitMQ 和 Kafka 在 Spring Boot 中的实战

在现代的微服务架构和分布式系统中&#xff0c;消息队列 是一种常见的异步通信工具。消息队列允许应用程序之间通过 生产者-消费者模型 进行松耦合、异步交互。在 Spring Boot 中&#xff0c;我们可以通过简单的配置来集成不同的消息队列系统&#xff0c;包括 ActiveMQ、Rabbit…

多层感知机paddle

多层感知机——paddle部分 本文部分为paddle框架以及部分理论分析&#xff0c;torch框架对应代码可见多层感知机 import paddle print("paddle version:",paddle.__version__)paddle version: 2.6.1多层感知机&#xff08;MLP&#xff0c;也称为神经网络&#xff0…

QEMU:模拟 ARM 大端字节序运行环境

文章目录 1. 前言2. ARM 大小端模拟测试2.1 裸机模拟测试2.1.1 大端模拟测试2.1.2 小端模拟测试 2.2 用户空间模拟测试2.2.1 大端模拟测试2.2.2 小端模拟测试 2.3 结论 3. 参考链接 1. 前言 限于作者能力水平&#xff0c;本文可能存在谬误&#xff0c;因此而给读者带来的损失&…

leetcode刷题3

文章目录 前言回文数1️⃣ 转成字符串2️⃣ 求出倒序数再比对 正则表达式匹配[hard]1️⃣ 动态规划 盛最多水的容器1️⃣ 遍历分类2️⃣ 双指针贪心 最长公共前缀1️⃣ 遍历&#xff08;zip解包&#xff09; 三数之和1️⃣ 双指针递归 最接近的三数之和1️⃣ 迭代一次双指针 电…

携手阿里云CEN:共创SD-WAN融合广域网

在9月19日举行的阿里云云栖大会上&#xff0c;犀思云作为SD-WAN领域的杰出代表及阿里云的SD-WAN重要合作伙伴&#xff0c;携手阿里云共同推出了创新的企业上云方案——Fusion WAN智连阿里云解决方案。这一创新方案不仅彰显了犀思云在SD-WAN技术领域的深厚积累&#xff0c;更体现…

前端web端项目运行的时候没有ip访问地址

我们发现 没有netWork 的地址 导致 团队内其他同学无法打开我们的地址 进行访问 在page.json 中的运行 指令中 添加 --host 记得加上空格 这样我们就可以看到这个地址了 团队其他同学 就可以访问我们这个地址了

Resnet50网络——口腔癌病变识别

一 数据准备 1.导入数据 import matplotlib.pyplot as plt import tensorflow as tf import warnings as w w.filterwarnings(ignore) # 支持中文 plt.rcParams[font.sans-serif] [SimHei] # 用来正常显示中文标签 plt.rcParams[axes.unicode_minus] False # 用来正常显示负…

2024华为杯研究生数学建模竞赛(研赛)选题建议+初步分析

难度&#xff1a;DE<C<F&#xff0c;开放度&#xff1a;CDE>F。 华为专项的题目&#xff08;A、B题&#xff09;暂不进行选题分析&#xff0c;不太建议大多数同学选择&#xff0c;对自己专业技能有很大自信的可以选择华为专项的题目。后续会直接更新A、B题思路&#…

计算机网络传输层---课后综合题

线路&#xff1a;TCP报文下放到物理层传输。 TCP报文段中&#xff0c;“序号”长度为32bit&#xff0c;为了让序列号不会循环&#xff0c;则最多能传输2^32B的数据&#xff0c;则最多能传输&#xff1a;2^32/1500B个报文 结果&#xff1a; 吞吐率一个周期内传输的数据/周期时间…

2024/9/19、20 数学20题

极大线性无关组&#xff1a;

基于C#+SQL Server2005(WinForm)图书管理系统

图书管理系统 一、 首先把数据库脚本贴出来(数据库名为library) USE [library] GO /****** Object: Table [dbo].[books] Script Date: 06/12/2016 11:27:12 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TABLE [dbo].[books]([bNum] [nvarchar](10…

Arthas sysprop(查看和修改JVM的系统属性)

文章目录 二、命令列表2.1 jvm相关命令2.1.4 sysprop&#xff08;查看和修改JVM的系统属性&#xff09;举例1&#xff1a;sysprop 查看所有系统属性举例2&#xff1a;sysprop java.version 查看单个属性&#xff0c;支持通过tab补全 二、命令列表 2.1 jvm相关命令 2.1.4 sysp…

STL-常用算法 遍历/查找/排序/拷贝和替换/算数生成/集合算法

STL常用算法 常用的遍历算法 for_each #define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std; #include<vector> #include<algorithm>void myPrint(int v) {cout << v << " "; }class MyPrint { public:void op…