接口幂等性详解

1. 什么是幂等性

幂等性指的是对同一个操作的多次执行所产生的影响与一次执行的影响相同。无论操作执行多少次,系统状态都应该保持一致。

在计算机科学和网络领域中,幂等性通常用来描述服务或操作的特性。对于RESTful API或HTTP方法,一个幂等操作的特点是:对于相同的输入,在多次执行之后,得到的结果是一致的。

例如,HTTP中的GET请求是幂等的,无论请求多少次,结果都是相同的。而POST请求则不一定是幂等的,因为每次执行时,可能会创建新的资源或状态,从而改变系统状态。

在处理分布式系统和网络通信时,保证操作的幂等性是非常重要的。因为网络中的请求可能因为重试、超时、丢包等原因被多次发送,而服务端必须能够正确处理这些重复的请求,而不会因为重复执行导致系统状态异常或资源多次创建等问题。

确保API的幂等性是设计和开发高质量、健壮系统的一个关键因素,可以减少因为重复操作导致的副作用,保障系统的可靠性和稳定性。

2. 什么是接口幂等性

接口幂等性是指针对同一个操作,无论执行多少次,系统的状态都保持一致。在Web开发中,特别是对于RESTful API,接口的幂等性是一种非常重要的性质。

一个幂等操作满足以下条件:

  1. 多次执行相同操作,产生的效果与执行一次相同操作的效果相同。
  2. 重复执行相同请求的结果与第一次执行该请求的结果相同。

这意味着无论对一个接口发送多少次相同的请求,系统都保持一致的状态,不会因为重复请求而导致状态的变化或资源的重复创建。在幂等性的设计中,服务端需要保证接收到重复请求时,不会因为重复执行操作而引起问题。

幂等性的设计对于解决由于网络延迟、超时、重试、系统问题等导致重复请求的情况非常重要。例如,对于一次性支付的操作,幂等性保证了即使用户在网络不稳定时多次点击支付按钮,系统也只会执行一次支付操作,避免重复扣款。

在RESTful API中,GET请求通常是幂等的,因为它只是获取资源而不会对资源状态做出改变;而POST请求一般不是幂等的,因为每次执行可能会创建新资源或改变系统状态。

确保接口的幂等性对于系统的正确性、可靠性和稳定性至关重要。
在这里插入图片描述

3. 如果忽略接口幂等性设计会出现哪些问题

忽略接口的幂等性设计可能导致以下问题:

  1. 重复操作导致资源状态异常: 如果接口不具有幂等性,重复执行同一个请求可能会导致资源状态的不一致或异常,例如多次创建相同的订单、重复扣款等。

  2. 重复创建资源: 对于非幂等的操作,可能会导致多次执行请求创建重复的资源,例如多次支付,每次请求都会创建新的支付记录。

  3. 系统副作用: 非幂等操作可能会对系统状态造成非预期的副作用,例如多次发送邮件、多次触发同一个事件,导致系统状态不一致。

  4. 用户体验下降: 如果接口不是幂等的,用户在网络波动或系统问题下多次发送相同请求时,可能会产生用户体验问题,例如重复下单、重复支付等。

  5. 系统负载增加: 重复请求可能导致系统负载增加,对服务器和数据库等资源产生不必要的压力,可能导致系统性能下降或宕机。

综上所述,忽略接口的幂等性设计可能导致系统状态不一致、资源重复创建、用户体验问题和系统负载增加等诸多问题,对系统的正确性和稳定性造成威胁。因此,确保接口的幂等性是设计和开发过程中的重要考虑因素。

4. 如何设计接口幂等性

4.1 使用Token+Redis

使用Token和Redis实现接口的幂等性通常涉及以下步骤:

  1. 生成并传递 Token:
    前端生成 Token: 在发送请求之前,前端生成一个唯一的 Token,通常是一个随机生成的字符串,比如 UUID,并将这个Token以客户ID为主键,以Token为值放入Redis
    Token 传递: 将 Token 放置于请求的 Header 或 Body 中。
  2. 服务端处理 Token:
    服务端接收 Token: 接收到请求后,服务端从请求中提取 Token。
  3. 验证 Token:
    检查 Token 是否存在于 Redis 中: 使用 Redis 数据库验证该 Token 是否已存在于数据库中。
  4. 保证幂等性:
    Token 存在: 如果 Token 已存在于 Redis 中,表示该请求已被处理过,服务端直接返回已存储的结果,不执行重复操作。(或者处理业务,并删除Token)
    Token 不存在: 如果 Token 不存在于 Redis 中,表示这是一个新的请求,服务端执行相应的业务逻辑,并将 Token 存储到 Redis,以防止重复请求。(或者直接返回)
    在这里插入图片描述示例伪代码:
    以下是一个简单的伪代码示例,演示了如何使用 Token+Redis 来确保接口的幂等性:
import redis.clients.jedis.Jedis;

public class IdempotentAPIHandler {
    private Jedis jedis;

    public IdempotentAPIHandler() {
        this.jedis = new Jedis("localhost", 6379); // 连接 Redis
    }

    public String processRequest(String token, String requestData) {
        if (checkTokenInRedis(token)) {
            // Token 存在,直接返回已存储的处理结果
            return jedis.get(token);
        } else {
            // Token 不存在,处理请求并将结果存储到 Redis
            String result = processRequestData(requestData); // 处理请求数据,获取结果
            jedis.set(token, result, "NX", "EX", 3600); // 设置 Token 并存储结果,设置过期时间为 1 小时
            return result;
        }
    }

    public boolean checkTokenInRedis(String token) {
        return jedis.exists(token); // 检查 Token 是否存在于 Redis
    }

    public String processRequestData(String requestData) {
        // 处理请求数据,返回处理结果
        return "Processed data: " + requestData;
    }

    public String generateToken() {
        // 生成 Token,通常是一个唯一的字符串
        return "generated_token"; // 这里可以使用 UUID.randomUUID().toString() 等生成唯一标识
    }

    public static void main(String[] args) {
        IdempotentAPIHandler handler = new IdempotentAPIHandler();
        String token = handler.generateToken(); // 生成 Token

        // 模拟处理 API 请求
        String requestData = "some_data";
        String result = handler.processRequest(token, requestData);
        System.out.println("Result: " + result);
    }
}

以上伪代码演示了如何在Java中使用 Token+Redis 实现接口的幂等性。在实际应用中,你需要根据具体的业务需求和框架,对这个示例进行适当调整。

4.2 使用数据库唯一索引

使用数据库唯一索引来实现接口的幂等性是一种可行的方法。这通常涉及在数据库表中创建唯一索引,以确保请求的唯一性。以下是一些步骤和概念:

  1. 设计数据库表结构:
    在数据库中创建一张表来记录请求,可以包括请求的唯一标识、请求内容以及处理结果等字段。

  2. 创建唯一索引:
    使用数据库的唯一索引特性,将请求的唯一标识字段设置为唯一索引。这样,当重复插入具有相同唯一标识的请求时,数据库会拒绝插入重复的记录。

  3. 插入请求前进行唯一索引检查:
    在处理请求前,首先查询数据库检查要插入的唯一标识是否已存在。如果存在,则表明这个请求已经被处理过,可以直接返回已存储的结果。

  4. 处理请求并插入数据库:
    如果唯一索引检查结果表明这个请求是新的,可以执行相应的业务逻辑,并将请求的唯一标识及处理结果插入到数据库中。

示例伪代码:
以下是一个简单的伪代码示例,演示了如何使用数据库的唯一索引来确保接口的幂等性:

// 假设这里是数据库操作对象,这里使用伪代码表示
class DatabaseOperation {
    // 执行插入操作,以请求的唯一标识作为唯一索引
    public boolean insertRequest(String uniqueIdentifier, String requestData) {
        // 在数据库中执行插入操作,使用唯一索引来保证唯一性
        // 如果遇到唯一索引重复错误(比如DuplicateEntry),说明请求已存在,返回 false
        // 如果成功插入,说明请求是新的,返回 true
    }

    // 根据唯一标识查询请求
    public String getRequestResult(String uniqueIdentifier) {
        // 根据唯一标识从数据库中获取请求处理结果
    }
}

// 在处理请求的服务类中
class IdempotentAPIHandler {
    private DatabaseOperation db;

    public IdempotentAPIHandler() {
        this.db = new DatabaseOperation(); // 初始化数据库操作对象
    }

    public String processRequest(String uniqueIdentifier, String requestData) {
        // 检查请求是否已存在
        if (db.getRequestResult(uniqueIdentifier) != null) {
            // 请求已存在,直接返回结果
            return db.getRequestResult(uniqueIdentifier);
        } else {
            // 请求是新的,处理请求并将结果插入到数据库
            String result = processRequestData(requestData); // 处理请求数据,获取结果
            db.insertRequest(uniqueIdentifier, result); // 插入请求到数据库
            return result;
        }
    }

    // 其他方法和业务逻辑
}

这个示例演示了如何利用数据库的唯一索引来确保接口的幂等性。在实际应用中,你需要根据具体的数据库和业务需求,调整这个示例。

4.3 使用分布式锁

使用分布式锁来确保接口的幂等性通常会涉及下列步骤:

  1. 获取分布式锁:
    在处理请求之前,尝试获取一个分布式锁,以确保在处理请求期间不会有其他请求来干扰。这样可以确保仅有一个请求能够进入关键的临界区。

  2. 处理请求:
    如果获取了分布式锁,就可以执行请求处理逻辑。这可以是具体的业务逻辑,确保在这个锁的作用下完成了唯一的处理操作。

  3. 释放分布式锁:
    在处理结束后,释放分布式锁,以允许其他请求进入临界区,继续执行接下来的操作。

示例伪代码:
以下是一个使用分布式锁实现接口幂等性的简单示例:

import redis.clients.jedis.Jedis;

public class IdempotentAPIHandler {
    private Jedis jedis;
    private String lockKey = "request_lock";

    public IdempotentAPIHandler() {
        this.jedis = new Jedis("localhost", 6379); // 连接 Redis
    }

    public String processRequest(String requestData) {
        String lockValue = acquireDistributedLock(); // 尝试获取分布式锁

        if (lockValue != null) {
            try {
                // 成功获取锁,处理请求
                String result = processRequestData(requestData); // 处理请求数据,获取结果
                return result;
            } finally {
                releaseDistributedLock(lockValue); // 处理结束后释放锁
            }
        } else {
            // 获取锁失败,可能有其他请求正在处理中
            return "Request is being processed. Please try again later.";
        }
    }

    public String acquireDistributedLock() {
        // 尝试获取分布式锁
        String lockValue = UUID.randomUUID().toString(); // 生成唯一的锁值
        String result = jedis.set(lockKey, lockValue, "NX", "EX", 60); // 设置锁,并设置过期时间
        if ("OK".equals(result)) {
            return lockValue; // 获取锁成功
        } else {
            return null; // 获取锁失败
        }
    }

    public void releaseDistributedLock(String lockValue) {
        // 释放分布式锁
        String currentValue = jedis.get(lockKey);
        if (currentValue != null && currentValue.equals(lockValue)) {
            jedis.del(lockKey); // 删除锁
        }
    }

    public String processRequestData(String requestData) {
        // 处理请求数据获取结果
        return "Processed data: " + requestData;
    }

    // 其他方法和业务逻辑
}

这个示例演示了如何使用Redis的分布式锁来确保在处理请求时只有一个请求能够进入关键的临界区,从而保证接口的幂等性。在实际应用中,你需要根据具体的业务需求和框架,对这个示例进行适当调整。

5. 案例:使用Token+Redis保证提交订单接口的幂等性

当设计一个电商网站的提交订单接口时,确保该接口的幂等性对于避免重复订单、重复支付等问题非常重要。以下是一个示例,使用Token+Redis实现提交订单接口的幂等性:

import redis.clients.jedis.Jedis;
import java.util.UUID;

public class OrderService {
    private Jedis jedis;

    public OrderService() {
        this.jedis = new Jedis("localhost", 6379); // 连接 Redis
    }

    public String submitOrder(String userId, String product) {
        String token = generateToken(userId, product); // 生成订单的唯一 Token

        if (checkIfOrderExists(token)) {
            // 订单已存在,直接返回已处理的结果
            return "Order already submitted.";
        } else {
            // 订单不存在,处理订单并存储到 Redis
            String result = processOrder(userId, product); // 处理订单逻辑,获取结果

            // 将订单信息存储到 Redis,并设置过期时间为一段合理时间,比如 24 小时
            jedis.set(token, result, "NX", "EX", 86400);

            return "Order submitted successfully.";
        }
    }

    private String generateToken(String userId, String product) {
        // 生成 Token,这里用 userId 和商品信息作为组合生成的 Token
        return "order_" + userId + "_" + product + "_" + UUID.randomUUID().toString();
    }

    private boolean checkIfOrderExists(String token) {
        // 检查订单是否已存在于 Redis 中
        return jedis.exists(token);
    }

    private String processOrder(String userId, String product) {
        // 处理订单的逻辑,可以包括数据库插入、支付等步骤
        return "Processed Order: " + product + " for User: " + userId;
    }

    public static void main(String[] args) {
        OrderService orderService = new OrderService();

        // 模拟订单提交
        String userId = "user123";
        String product = "ProductABC";
        String submissionResult = orderService.submitOrder(userId, product);
        System.out.println(submissionResult);
    }
}

这个示例演示了一个简单的订单提交接口,确保了订单的幂等性。在实际应用中,你需要根据实际业务需求和框架,对这个示例进行适当调整。

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

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

相关文章

049-第三代软件开发-软件部署脚本(一)

第三代软件开发-软件部署脚本(一) 文章目录 第三代软件开发-软件部署脚本(一)项目介绍软件部署脚本(一)其他方式 关键字: Qt、 Qml、 bash、 shell、 脚本 项目介绍 欢迎来到我们的 QML & C 项目!这个项目结合了 QML(Qt Meta-Object…

Java 最常见的面试题:常用的 jvm 调优的参数都有哪些?

Java 最常见的面试题:常用的 jvm 调优的参数都有哪些? 常用的Java虚拟机(JVM)调优参数有很多,以下是一些重要的参数: -Xms 和 -Xmx:这两个参数分别设置了JVM启动内存的最小值和最大值,单位通…

毅速丨为什么不锈钢材料在金属3D打印中应用广泛

不锈钢材料作为一种常见材料,在金属3D打印中应用广泛,可以说是目前使用率最高的材料,为什么不锈钢大受欢迎,主要由几点原因。 第一、工艺适合性 金属3D打印的工艺,如直接金属激光烧结(DMLS)或选…

Leetcode—2578.最小和分割【简单】

2023每日刷题&#xff08;二十三&#xff09; Leetcode—2578.最小和分割 实现代码 class Solution { public:int splitNum(int num) {vector<int> a;while(num) {a.push_back(num % 10);num / 10;}int n a.size();sort(a.begin(), a.begin() n);int num1 0;int num…

【Spring】bean的自动装配

目录 一.byName 二.byType 快捷书写 people1 package org.example;public class People1 {public void eat(){System.out.println("吃饭");} }people2 package org.example;public class People2 {public void sleep(){System.out.println("睡觉");} …

【SVN】

SVN 1 svn使用1.1 主干合并到分支1.2 分支合并到主干1.3 分支建立1.4 创建分支1.5 切换分支1.6 合并分支1.7 删除分支 2 概念理解 1 svn使用 1.1 主干合并到分支 首先&#xff0c;在本地trunk中先update一下&#xff0c;有冲突的解决冲突&#xff0c;保证trunk和repository已…

软件测试/测试开发丨接口测试学习笔记,TcpDump与WireShark

点此获取更多相关资料 本文为霍格沃兹测试开发学社学员学习笔记分享 原文链接&#xff1a;https://ceshiren.com/t/topic/27859 协议分析工具 网络监听&#xff1a;TcpDump WireShark 代理 Proxy 推荐工具&#xff1a;手工测试charles [全平台]、安全测试burpsuite [全平台 j…

10-Docker-分布式存储算法

01-哈希取余算法分区 哈希取余分区&#xff08;Hash Modulus Partitioning&#xff09;是一种在分布式计算和数据存储中常用的分区策略&#xff0c;其目的是将数据或计算任务分配到多个节点或服务器上&#xff0c;以实现负载均衡和提高性能。这种分区策略的核心思想是使用哈希…

java网络通信:Springboot整合Websocket

网络通信 什么是webSocket&#xff1f;WebSocket 原理springboot整合websocket过程 网络通信三要素&#xff1a;ip地址&#xff08;ipv4、ipv6&#xff09;、端口号&#xff08;应用程序的唯一标识&#xff09;、协议&#xff08;连接和通信的规则&#xff0c;常用&#xff1a;…

基于袋獾算法的无人机航迹规划-附代码

基于袋獾算法的无人机航迹规划 文章目录 基于袋獾算法的无人机航迹规划1.袋獾搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要&#xff1a;本文主要介绍利用袋獾算法来优化无人机航迹规划。 1.袋獾搜索算法 …

深入探讨 Presto 中的缓存

Presto是一种流行的开源分布式SQL引擎&#xff0c;使组织能够在多个数据源上大规模运行交互式分析查询。缓存是一种典型的提高 Presto 查询性能的优化技术。它为 Presto 平台提供了显着的性能和效率改进。 缓存通过将频繁访问的数据存储在内存或快速本地存储中&#xff0c;避免…

redis的数据类型及操作

一、redis 的数据库 Redis是一个字典结构的存储服务器。一个Redis实例提供了多个用来存储数据的字典,客户端可以指定将数据存在哪个字典中。这与关系型数据库例中可以创建多个数据库似。因此,可以将每个字典理解为一个独立的数据库。每个数据库对外都是以0开始的递增数字命名…

HTML的表单标签和无语义标签的讲解

HTML的表单标签 表单是让用户输入信息的重要途径, 分成两个部分: 表单域: 包含表单元素的区域. 重点是 form 标签. 表单控件: 输入框, 提交按钮等. 重点是 input 标签 form 标签 使用form进行前后端交互.把页面上,用户进行的操作/输入提交到服务器上 input 标签 有很多形态,能…

本地消息表分布式事务

BASE论文 论文链接&#xff1a;https://queue.acm.org/detail.cfm?id1394128 里面提到&#xff0c; The most critical factor in implementing the queue, however, is ensuring that the backing persistence is on the same resource as the database. This is necessary…

5G边缘计算网关的功能及作用

5G边缘计算网关具有多种功能。 首先&#xff0c;它支持智能云端控制&#xff0c;可以通过5G/4G/WIFI等无线网络将采集的数据直接上云&#xff0c;实现异地远程监测控制、预警通知、报告推送和设备连接等工作。 其次&#xff0c;5G边缘计算网关可以采集各种数据&#xff0c;包…

vue中 process.env 对象为空对象问题

问题&#xff1a;今天在处理vue项目环境问题的时候&#xff0c;发现直接打印 process 对象和打印 process.env 时 env 对象输出结果是不一样的&#xff0c;如下图所示&#xff1a; 在网上搜索了一番后发现还是有挺多朋友对此感到疑惑的&#xff0c;询问了同事&#xff0c;同…

数模之线性规划

线性规划 优化类问题&#xff1a;有限的资源&#xff0c;最大的收益 例子: 华强去水果摊找茬&#xff0c;水果摊上共3个瓜&#xff0c;华强总共有40点体力值,每劈一个瓜能带来40点挑衅值,每挑一个瓜问“你这瓜保熟吗”能带来30点挑衅值,劈瓜消耗20点体力值&#xff0c;问话消耗…

Vue3 简单实现虚拟Table,展示海量单词.利用WebAPI speechSynthesis,朗读英语单词

目录 本页面完整代码 视频演示 完整的页面代码 利用webapi speechSynthesis帮助我们自动郎读英语单词&#xff0c;可以利用这个API&#xff0c;做一些小说朗读或到账提示。 本页面完整代码 用Vue写了一个简单页面&#xff0c;里面还写了一个简单的虚拟Table支持海量数据展示…

ubuntu 18.04安装自己ko驱动 修改secure boot

因为本人老折腾自己的电脑&#xff0c;所以老重装系统&#xff0c;然后配置又不见了&#xff0c;这次配置赶紧记下来 insmod netlink_test.ko 报错&#xff1a;insmod: ERROR: could not insert module netlink_test.ko: Operation not permitted 添加 sudo insmod netlink_te…

XCTF刷题十一道(01)

文章目录 Training-WWW-RobotsPHP2unserialize3view-sourceget_postrobotsbackupcookiedisabled_buttonweak_authsimple_php Training-WWW-Robots robots.txt&#xff0c;防爬虫&#xff0c;访问urlrobots.txt PHP2 phps源码泄露 >phps文件就是php的源代码文件&#xff0…