什么是幂等性?

 一.幂等性

什么是幂等性?

在计算机科学和数学领域中,” 幂等性 “虽然源于相同的概念,但其应用和具体含义有所不同

在数学中:幂等性是一个代数性质,描述的是一个操作或函数在多次应用后结果不变的特性

在分布式系统中:幂等性是一个重要的设计原则,它描述的是对某一资源发起一次请求和多次请求,其产生的效果都是相同的,不会因操作次数的增加而改变资源的最终状态

幂等性主要解决什么问题?

幂等性的提出主要解决分布式系统中重复提交问题,比如:像不法分子通过提交重复订单恶意刷单;由于网络波动,系统卡顿,用户多次点击支付按钮,导致重复支付;如果这些问题不能有效的解决,会对系统正常运行带来干扰,也可能带来严重的经济损失

为了解决这一系列问题,目前提出了三种行之有效的解决方案

1.数据库约束:利用数据库的唯一索引,主键等特性,保证数据的唯一性。数据库会对这种重复数据插入进行拦截,避免因为重复操作而导致数据冗余或错误

2.乐观锁:常用于数据库操作中,在更新数据时,会根据预先设置的乐观锁状态来判断是否被其他操作修改过,如果状态符合预期,则进行更新,反之,需重新进行处理,以保证数据一致性和操作的幂等性

3.唯一序列号:在进行操作时,为每个请求分配一个唯一的序列号。操作时判断与该序列号相等则执行

通过一个简单的订单系统来展示如何实现这三种方案

场景描述

假设我们有一个订单系统,用户可以提交订单,我们需要保证以下幂等性:

1.用户不能重复提交相同的订单

2.在更新订单状态时,避免并发冲突

3.通过唯一序列号确保操作的幂等性

1.利用数据库约束实现幂等性

通过在数据库中设置唯一索引或主键约束,确保数据的唯一性,当重复数据尝试插入时,数据库会抛出异常,从而阻止重复操作

数据库设计

CREATE TABLE orders(
    order_id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    order_number VARCHAR(50) NOT NULL UNIQUE,  --唯一订单号
    amount DECIMAL(10,2) NOT NULL,
    status VARCHAR(20) DEFAULT 'PENDING',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Java代码实现

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

//定义数据库连接和配置
public class OrderService{
    private static final string  DB_URL = "jdbc:mysql://localhost:3306/your_database";
    private static final String USER = "your_username";//数据库用户名
    private static final String PASS = "your_password";//数据库密码
 //创建订单的方法   
public void createOrder(String orderNumber,int userId,double amount){
    
    //使用参数化查询;(?,?,?)占位符预防SQL注入攻击;插入订单需要提供订单号,用户ID以及订单金额
    String sql = "INSERT INTO orders(order_number,user_id,amount) VALUES(?,?,?)";
    try(Connection conn = DriverManager.getConnection(DB_URL,USER,PASS);
    PreparedStatement pstmt = conn.prepareStatement(sql)){
       //将方法参数绑定到SQL语句的占位符中
       pstmt.setString(1,orderNumber);
       pstmt.setString(1,orderNumber);
       pstmt.setDouble(3,amount);
       //调用executeUpdate()方法执行插入操作
       pstmt.executeUpdate();
       System.out.println("订单创建成功!");
    }catch(SQLException e){
       if(e.getErrorCode() == 1062){  //禁止插入重复数据
           System.out.println("订单已存在,无法重新创建!");
    }else{
           System。out.println("数据库错误:" + e.getMessage());
    }

    }

}
//主方法创建实例,调用createOrder方法两次,第一次正常插入订单,第二次尝试插入相同订单号
public static void main(String[] args){
     OrderService service = new OrderService();
     service.createOrder("ORDER123",1,100.0);
     service.createOrder("ORDER123",1,100.0); //重复提交 
}
}



}

在数据表设计中

order_number字段被设置为唯一,如果没有设置唯一性约束,这段代码就无法正确实现幂等性

结果:

  • 第一次提交成功
  • 第二次提交时,数据库会抛出唯一性约束错误,阻止重复插入

2.利用乐观锁实现幂等性

在数据库中增加一个版本号字段(如version),每次更新数据时,检查版本号是否与预期一致,如果一致,则更新数据并递增版本号

数据库设计(为了实现乐观锁,数据表中需要有一个版本号字段 version)

ALTER TABLE orders ADD COLUMN version INT DEFAULT 1;

 Java代码实现

public void updateOrderStatus(String orderNumber,String newStatus){
 //将订单的状态更新为newStatus,并递增版本号,只有当订单号匹配且版本号匹配,才会执行该操作 
 String sql = "UPDATE orders SET status = ?,version = version + 1 WHERE order_number = ? AND version = ?"; 
  String querySql = "SELECT version FROM orders WHERE order_number = ?";
  try(Connection conn = DriverManager.getConnection(DB_URL,USER,PASS)){}
  //查询当前订单的版本号
   PreparedStatement queryStmt = conn.prepareStatement(querySql)
     queryStmt.setString(1,orderNumber);
     ResultSet rs = queryStmt.executeQuery();
     if(rs.next()){
        int currentVersion = rs.getInt("version");
         //使用PreparedStatement执行更新操作
         try (PreparedStatement updateStmt = conn.prepareStatement(sql)) {
              updateStmt.setString(1, newStatus); //新的订单状态
              updateStmt.setString(2, orderNumber); //订单号
              updateStmt.setInt(3, currentVersion); //当前版本号
               //调用executeUpdate()方法执行更新操作
              int rowsAffected = updateStmt.executeUpdate();
     if (rowsAffected == 0) {
        System.out.println("订单状态更新失败,数据已被其他操作修改!");
     } else {
        System.out.println("订单状态更新成功!");
     }
     }
     } else {
        System.out.println("订单不存在!");
     }
     }

   } catch (SQLException e) {
        System.out.println("数据库错误:" + e.getMessage());
    }
}
//创建OrderService实例,调用updateOrderStatus方法两次,模拟并发更新
public static void main(String[] args) {
    OrderService service = new OrderService();
    service.updateOrderStatus("ORDER123", "COMPLETED");
    service.updateOrderStatus("ORDER123", "CANCELLED");  // 并发更新
}
  
 ...

结果:

第一次调用:

  • 查询到当前版本号(假设为1)
  • 更新成功,版本号递增为2
  • 打印:“订单状态更新成功!”

第二次调用:

  • 查询到当前版本号(现在是2)
  • 尝试更新时,版本号不匹配(预期版本号为1,实际版本号为2)
  • 打印:“订单状态更新失败,数据已被其他操作修改!”

3.利用唯一序列号实现幂等性

为每个请求分配一个唯一的序列号(如UUID),并利用数据库的NOT EXISTS条件,在执行操作时,检查该系列号是否已存在,如果存在,则跳过操作

数据库设计(为了实现幂等性,需要设计两个表)

1.订单表:(存储订单的基本信息)

CREATE TABLE orders (
    order_id INT AUTO_INCREMENT PRIMARY KEY,
    order_number VARCHAR(50) NOT NULL UNIQUE,--order_number是订单的唯一标识
    status VARCHAR(20) DEFAULT 'PENDING',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

2.操作记录表(用于记录对订单的操作) 

CREATE TABLE order_operations (
    operation_id VARCHAR(50) PRIMARY KEY,--唯一标识,使用UUID生成
    order_id INT NOT NULL,
    operation_type VARCHAR(20) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Java代码实现

import java.util.UUID;

public void processOrderOperation(String orderNumber, String operationType) {
    String operationId = UUID.randomUUID().toString();  // 生成唯一序列号
    //将操作记录插入到order_operations表中 
    String sql = "INSERT INTO order_operations (operation_id, order_id, operation_type) "
               + "SELECT ?, (SELECT order_id FROM orders WHERE order_number = ?), ? "
               + "WHERE NOT EXISTS (SELECT 1 FROM order_operations WHERE operation_id = ?)"; //使用WHERE NOT EXISTS子句,检查是否已经存在相同的operation_id
   //建立数据库连接并执行SQL语句 
   try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
         PreparedStatement pstmt = conn.prepareStatement(sql)) {
        pstmt.setString(1, operationId);
        pstmt.setString(2, orderNumber);
        pstmt.setString(3, operationType);
        pstmt.setString(4, operationId);
        int rowsAffected = pstmt.executeUpdate();
        if (rowsAffected > 0) {
            System.out.println("操作成功执行!");
        } else {
            System.out.println("操作已被处理,跳过重复执行!");
        }
    } catch (SQLException e) {
        System.out.println("数据库错误:" + e.getMessage());
    }
}
//创建OrderService实例,调用processOrderOperation方法两次,模拟重复操作
public static void main(String[] args) {
    OrderService service = new OrderService();
    service.processOrderOperation("ORDER123", "PAYMENT");
    service.processOrderOperation("ORDER123", "PAYMENT");  // 重复操作
}

结果:

第一次调用:

  • 生成一个新的operation_id
  • 插入操作记录成功,打印“操作成功执行!”

第二次调用

  • 生成一个新的operation_id
  • 由于幂等性检查(WHERE NOT EXISTS),不会重复插入操作记录,打印“操作已被处理,跳过重复执行!

幂等性保证:即使多次调用该方法,最终结果与调用一次相同,满足幂等性要求

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PyCharm Terminal 自动切换至虚拟环境

PyCharm 虚拟环境配置完毕后,打开终端,没有跟随虚拟环境切换,如图所示: 此时,需要手动将终端切换为 Command Prompt 模式 于是,自动切换至虚拟环境 每次手动切换,比较麻烦,可以单…

YOLOv12从入门到入土(含结构图)

论文链接:https://arxiv.org/abs/2502.12524 代码链接:https://github.com/sunsmarterjie/yolov12 文章摘要: 长期以来,增强YOLO框架的网络架构一直至关重要,但一直专注于基于cnn的改进,尽管注意力机制在建…

我用AI做数据分析之数据清洗

我用AI做数据分析之数据清洗 AI与数据分析的融合效果怎样? 这里描述自己在使用AI进行数据分析(数据清洗)过程中的几个小故事: 1. 变量名的翻译 有一个项目是某医生自己收集的数据,变量名使用的是中文,分…

解锁机器学习核心算法 | K-平均:揭开K-平均算法的神秘面纱

一、引言 机器学习算法种类繁多,它们各自有着独特的优势和应用场景。前面我们学习了线性回归算法、逻辑回归算法、决策树算法。而今天,我们要深入探讨的是其中一种经典且广泛应用的聚类算法 —— K - 平均算法(K-Means Algorithm&#xff09…

Bigemap Pro如何设置经纬网出图网格设置

第一步:打开bigemap pro软件,单击顶部网格选项第二栏,弹出经纬网设置对话框,如下图: 按作图需求自定义设置后,点击应用如下图: 第二步:设置好经纬网之后,进行作图&#x…

K8s 之端口暴露(The Port of K8s is Exposed)

K8s 之端口暴露 Kubernetes 是一个用于管理容器化应用程序的流行工具。然而,关于它的工作原理存在一些误解。最常见的误解之一是关于 Kubernetes Pod 中的端口暴露。本文将解释 Kubernetes 中端口暴露的真相。 1 误解 像许多 Kubernetes 新手一样,我最…

操作系统2.4

一、死锁,饥饿,死循环 死锁:各进程互相等待对方手里的资源,导致各进程都阻塞,无法向前推进的现象 饥饿:由于长期得不到想要的资源,某进程无法向前推进的现象,例如:短进…

解决DeepSeek服务器繁忙问题的实用指南

目录 简述 1. 关于服务器繁忙 1.1 服务器负载与资源限制 1.2 会话管理与连接机制 1.3 客户端配置与网络问题 2. 关于DeepSeek服务的备用选项 2.1 纳米AI搜索 2.2 硅基流动 2.3 秘塔AI搜索 2.4 字节跳动火山引擎 2.5 百度云千帆 2.6 英伟达NIM 2.7 Groq 2.8 Firew…

c++进阶———继承

1.引言 在一些大的项目中,我们可能要重复定义一些类,但是很麻烦,应该怎么办呢?举个简单的例子,我要做一个全校师生统计表,统计学号,教师编号,姓名,年龄,电话…

Android 平台GB28181设备接入实战指南

一、引言 随着视频监控技术的不断发展,国标 GB28181 协议在安防监控领域得到了广泛应用。该协议为不同厂家的视频监控设备之间的互联互通提供了统一的规范,使得设备的接入与管理变得更加简单和高效。在 Android 平台上实现 GB28181 设备接入&#xff0c…

细说Java 引用(强、软、弱、虚)和 GC 流程(一)

一、引用概览 1.1 引用简介 JDK1.2中引入了 Reference 抽象类及其子类,来满足不同场景的 JVM 垃圾回收工作: SoftReference 内存不足,GC发生时,引用的对象(没有强引用时)会被清理;高速缓存使用…

基于图像处理的裂缝检测与特征提取

一、引言 裂缝检测是基础设施监测中至关重要的一项任务,尤其是在土木工程和建筑工程领域。随着自动化技术的发展,传统的人工巡检方法逐渐被基于图像分析的自动化检测系统所取代。通过计算机视觉和图像处理技术,能够高效、精确地提取裂缝的几何特征,如长度、宽度、方向、面…

android ViewPager 管理 Fragment的预加载onCreate

一、前言 当ViewPager 加载多个 Fragment时候,怎么管理Fragment预加载。因为有些数据需要提前加载,第一个方便后面数据使用,提前初始化。或者预加载网络数据等。 二、实现示例 在onCreate方法进行数据预加载。如果在onCreateView函数里面&…

云计算架构学习之Ansible-playbook实战、Ansible-流程控制、Ansible-字典循环-roles角色

一、Ansible-playbook实战 1.Ansible-playbook安装软件 bash #编写yml [rootansible ansible]# cat wget.yml - hosts: backup tasks: - name: Install wget yum: name: wget state: present #检查playbook的语法 [rootansible ansible]…

Redis常用命令合集【二】

在合集【一】中已经介绍了redis中String类型和Hash类型,接下来就继续介绍剩下的List、Set、SortedSet类型。 1.List类型 Redis中的List类型与Java中的LinkedList类似,可以看做是一个双向链表结构。既可以支持正向检索和也可以支持反向检索。 特征也与…

挑战用Cursor实现“Cursor“的第二天

挑战用Cursor实现"Cursor"的第二天 项目地址 :https://github.com/Ez4Sterben/Ez4Code/tree/master 省流(困了想睡觉了,就不多描述了): 干了1小时,文件树没问题了,代码能编辑了&…

已解决IDEA无法输入中文问题(亲测有效)

前言 在使用IDEA的时候,比如我们想写个注释,可能不经意间,输入法就无法输入中文了,但是在其他地方打字,输入法仍然能够正常工作。这是什么原因呢,这篇文章带你解决这个问题! 快捷键 如果你的I…

阿里云上的网站配置HTTPS

1. 获取SSL证书 创建证书 下载证书 下载 上传 .key .pem 文件 到 阿里云服务器 /etc/nginx/ssl nginx.conf 配置 server { listen 443 ssl; server_name yuming; ssl_certificate /etc/nginx/ssl/*.pem; ssl_certificate_key /etc/nginx/ssl/*.key;

在Unity中用简单工厂模式模拟原神中的元素反应

1. 第一步创建3个脚本Factory(反应工厂),Reactions(具体反应),FactoryText(测试反应的脚本) 2.编写工厂脚本 using UnityEngine;// 定义一个元素反应的接口,所有具体的元…

数组和指针常见笔试题(深度剖析)

下边我来讲一下常见的面试题&#xff0c;过程很详细放心观看 #include<stdio.h>#include <string.h>int main() {char arr[] { a,b,c,d,e,f };printf("%d\n", strlen(arr));//随机值&#xff0c;因为strlen是遇到斜杠\0结束&#xff0c;统计\0之前的字符…