一、幂等性原则
1.1 幂等性的定义
- 数学中:如果一个函数f满足f(f(x))=f(x),则称f是幂等的。
- 程序中:如果一个操作对系统状态的影响,无论执行多少次,结果都是相同的,那么这个操作就是幂等的。
1.2 幂等性与非幂等性操作的区别
- 幂等性操作的情况:
- GET请求:HTTP的GET请求是幂等的,无论调用多少次,服务器端的资源状态不会改变。
- DELETE请求:删除一个资源是幂等的,如果资源已经被删除,再次请求删除也不会改变资源状态。
- 数据库中的UPDATE语句:如果将某一列的值设置为固定的值,多次执行更新操作也会得到相同的结果。
- 非幂等性操作的例子:
- POST请求:HTTP的POST请求通常是非幂等的,每次请求都会创建一个新的资源或修改现有资源的状态。
- 支付操作:多次执行支付操作可能会导致重复支付的情况,因此支付通常是非幂等性操作。
- 数据库中的INSERT操作:插入新记录是非幂等的,重复执行插入操作会导致多个记录被插入。
- 区别总结:
- 幂等性操作:多次执行操作得到的结果与执行一次相同,不会改变系统状态。
- 非幂等性操作:多次执行操作可能会改变系统状态,结果不同于只执行一次操作。
二、程序中实现幂等性的策略
在程序中实现幂等性通常有以下几种基础策略:
- 唯一标识符(ID)
- 为每个操作分配一个唯一的标识符,确保每个操作只能被执行一次。
- 幂等性标记字段(Idempotency Token)
- 在每次执行操作时,传递一个幂等性标记字段到系统中。系统在接收到请求后,首先检查该标记字段是否已经被使用过,如果已经被使用过则不执行操作。
- 乐观锁版本控制
- 针对需要幂等性的操作,可以引入版本控制机制。每次执行操作时,需要提供当前版本号,系统根据版本号判断是否执行操作
- 状态检查
- 在执行操作之前,先检查系统的状态是否已经处于预期状态。如果系统已经处于预期状态,则操作可以执行;否则,不执行操作
- 事务管理
- 使用数据库事务来保证操作的原子性,如果操作失败,事务可以回滚到操作前的状态。
- 悲观锁
- 在操作开始时锁定资源,直到操作完成才释放资源,防止其他操作干扰。
当然,实际程序开发过程中还有许多别的方法,我们要合理的选型,方能写出健壮的代码。
三、干货:Java中实现一个无状态的幂等操作
在Java中实现无状态的幂等操作通常意味着操作不依赖于任何外部状态,并且每次执行的结果都是一致的。下面跟着我一起来完成。
#bytemd-mermaid-1728963583449-0{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#bytemd-mermaid-1728963583449-0 .error-icon{fill:#552222;}#bytemd-mermaid-1728963583449-0 .error-text{fill:#552222;stroke:#552222;}#bytemd-mermaid-1728963583449-0 .edge-thickness-normal{stroke-width:2px;}#bytemd-mermaid-1728963583449-0 .edge-thickness-thick{stroke-width:3.5px;}#bytemd-mermaid-1728963583449-0 .edge-pattern-solid{stroke-dasharray:0;}#bytemd-mermaid-1728963583449-0 .edge-pattern-dashed{stroke-dasharray:3;}#bytemd-mermaid-1728963583449-0 .edge-pattern-dotted{stroke-dasharray:2;}#bytemd-mermaid-1728963583449-0 .marker{fill:#333333;stroke:#333333;}#bytemd-mermaid-1728963583449-0 .marker.cross{stroke:#333333;}#bytemd-mermaid-1728963583449-0 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#bytemd-mermaid-1728963583449-0 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#bytemd-mermaid-1728963583449-0 .cluster-label text{fill:#333;}#bytemd-mermaid-1728963583449-0 .cluster-label span{color:#333;}#bytemd-mermaid-1728963583449-0 .label text,#bytemd-mermaid-1728963583449-0 span{fill:#333;color:#333;}#bytemd-mermaid-1728963583449-0 .node rect,#bytemd-mermaid-1728963583449-0 .node circle,#bytemd-mermaid-1728963583449-0 .node ellipse,#bytemd-mermaid-1728963583449-0 .node polygon,#bytemd-mermaid-1728963583449-0 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#bytemd-mermaid-1728963583449-0 .node .label{text-align:center;}#bytemd-mermaid-1728963583449-0 .node.clickable{cursor:pointer;}#bytemd-mermaid-1728963583449-0 .arrowheadPath{fill:#333333;}#bytemd-mermaid-1728963583449-0 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#bytemd-mermaid-1728963583449-0 .flowchart-link{stroke:#333333;fill:none;}#bytemd-mermaid-1728963583449-0 .edgeLabel{background-color:#e8e8e8;text-align:center;}#bytemd-mermaid-1728963583449-0 .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#bytemd-mermaid-1728963583449-0 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#bytemd-mermaid-1728963583449-0 .cluster text{fill:#333;}#bytemd-mermaid-1728963583449-0 .cluster span{color:#333;}#bytemd-mermaid-1728963583449-0 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#bytemd-mermaid-1728963583449-0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#bytemd-mermaid-1728963583449-0 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}
不存在
存在
定义服务类
使用线程安全的缓存
执行操作的方法
生成唯一操作标识符
检查缓存
执行实际的操作逻辑
存储操作结果到缓存
返回操作结果
下面来一个实现的类瞧瞧:
java
代码解读
复制代码
import java.util.concurrent.ConcurrentHashMap;
import java.util.UUID;
public class IdempotentService {
/**
* 使用线程安全的缓存来存储操作结果
*/
private ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<>();
public Object executeOperation(Object... parameters) {
// 生成全局唯一的操作标识符...这里只是案例,实际这个唯一生成的要分业务去处理
String operationId = UUID.randomUUID().toString();
// 检查操作是否已经执行过
if (cache.containsKey(operationId)) {
// 操作已执行,直接返回缓存的结果
return cache.get(operationId);
}
// 执行操作
Object result = performOperation(parameters);
// 将结果存储到缓存中
cache.put(operationId, result);
return result;
}
private Object performOperation(Object[] parameters) {
// 执行具体的操作逻辑
// ...
return "Operation Result";
}
}
四、总结
幂等性在编码中的重要性在于确保即使在分布式系统或高并发场景下,重复的请求或操作也不会导致不一致或意外的结果,从而保障系统的稳定性和数据的准确性。
程序员应该深刻理解幂等性原理,并在设计和实现系统功能时主动考虑和应用幂等性策略,以确保系统的健壮性和用户的信赖度。