1,什么是幂等性
即相同的多次请求,只执行一次。
2,简述怎么保证幂等性?
单机部署的场景
使用唯一标识符:
在客户端发起请求时,附加一个唯一的请求标识符(如 UUID)。服务端接收到请求后,检查这个标识符是否已经处理过。如果已处理过,直接返回之前的结果;否则,处理请求并记录标识符。
分布式部署场景
借用脱离服务自身存储外的第三方工具,如:redis缓存,mysql中的唯一约束,s3存储等都可以
实现方法思路
1,前端
-
请求去重:
- 使用请求标识符(如UUID)来标识每个请求,确保在短时间内相同请求只发送一次。
- 在发送请求前,可以将请求记录在内存或缓存中,如果再次发送相同请求,直接返回之前的结果。
-
限制用户操作:
- 在用户界面中,禁用提交按钮或相应操作,直到请求完成,以防止用户多次点击。
- 重定向界面
- 在点击之后,跳转到新的界面(最好是静态界面),防止用户多次点击
-
处理响应:
- 对于重复请求的响应,应确保前端能够正确处理并显示,比如缓存结果,发现为同一请求时直接返回缓存。
2,后端
-
唯一请求标识:
- 在接收请求时,检查请求中是否包含唯一标识符。如果已经处理过该请求,返回之前的结果,而不是重新执行操作。如:使用数据库的唯一约束来防止重复插入(如使用唯一索引).
-
使用幂等的逻辑:
- 确保业务逻辑设计是幂等的,比如更新操作应始终更新到同一状态而不产生副作用。
-
设计接口时考虑状态:
- 对于状态转换,确保状态之间的转换是幂等的,例如从“待处理”到“已完成”的转换应能多次接受同一请求而不会产生额外副作用。
-
监控与日志:
- 记录每个请求及其处理结果,以便在需要时可以审计和排查问题。
3,举个栗子
以数据库为例
首先简单创建一张表
CREATE TABLE `roejrtest`.`uq` (
`id` int NOT NULL AUTO_INCREMENT,
`key` varchar(255) NULL,
`value` varchar(255) NULL,
`uuid` varchar(200) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `index_uq_uuid`(`uuid`)
);
创建接口进行测试(基础的就不全部展示了),有需要请查看https://blog.youkuaiyun.com/weixin_54925172/article/details/139564046?spm=1001.2014.3001.5501
controller类
package com.luojie.controller;
import com.luojie.controImpl.IdempotenceTestImpl;
import com.luojie.moudle.IdempotenceTestModule;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class IdempotenceTestController {
@Autowired
private IdempotenceTestImpl idempotenceTest;
@PostMapping("/idempotence/test")
public void idempotenceTest(@RequestHeader("x-uuid") String uuid, @RequestBody IdempotenceTestModule module) {
idempotenceTest.setup(uuid, module);
}
}
实现类
package com.luojie.controImpl;
import com.luojie.dao.mapper2.Mapper2;
import com.luojie.moudle.IdempotenceTestModule;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class IdempotenceTestImpl {
@Autowired
private Mapper2 mapper2;
public void setup(String uuid, IdempotenceTestModule module) {
// 检查是否已经存在
String getuuid = mapper2.getuuid(uuid);
if (StringUtils.isNotBlank(getuuid)) {
throw new IllegalArgumentException("已经处理过了");
}
// 不存在则继续
module.setUuid(uuid);
mapper2.insertUq(module);
}
}
测试,第一次请求时,检查未被拦截
后续请求时,被拦截