一、什么是幂等
幂等指的是相同的请求执行一次和执行多次的效果是一致的,不会因为多次请求产生副作用。
对于一些数据更新操作,例如修改用户信息、下单等操作,如果发生网络故障或者客户端异常,造成了多次请求,如果系统没有幂等措施,可能导致数据多次更新,最终产生异常数据。
幂等可以有效解决重复执行的问题。
二、为什么需要幂等
网络通信中常常出现不稳定的情况,例如网络中断、超时等问题,而相同操作请求多次则会产生意料之外的结果。在银行支付、物流发货、物资调拨等场景中,重复操作可能会导致金钱流、库存等数据的不一致,因此在这些场景中需要通过幂等来保证数据的一致性、正确性和安全性。
三、java中的实现方法
1、Token检测
Token是服务器返回的一段随机生成的字符串,客户端在每次请求时都必须带上这个Token,服务器在接收到Token后将Token与之前存储的Token比较,如果相同,则代表请求已经被处理,是重复请求,直接返回结果即可。
代码示例:
public class IdempotentTokenInterceptor extends HandlerInterceptorAdapter {
private TokenProvider tokenProvider;
public IdempotentTokenInterceptor(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("X-api-token");
if(token == null) {
throw new BusinessException("Missing X-api-token header!");
}
if(!tokenProvider.validateToken(token)) {
throw new BusinessException("Invalid X-api-token!");
}
return true;
}
}
public class TokenProvider {
public String generateToken() {
String token = UUID.randomUUID().toString();
//save to cache or database
return token;
}
public boolean validateToken(String token) {
//从缓存中获取,并删除Token
return true;
}
}
public class BusinessController {
private TokenProvider tokenProvider;
public BusinessController(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
@PostMapping("/api")
public ApiResponse businessApi() {
String token = tokenProvider.generateToken();
//save token to cache or database
//process business logic
return ApiResponse.ok();
}
}
2、数据库唯一约束
在数据库表中设置一个唯一约束,例如唯一索引,保证相同的请求只处理一次,后续的请求会抛出数据库异常。
代码示例:
public class BusinessController {
@Autowired
private BusinessService businessService;
@PostMapping("/api")
public ApiResponse businessApi(@RequestBody BusinessRequest request) {
businessService.processData(request);
return ApiResponse.ok();
}
}
@Service
public class BusinessService {
@Autowired
private BusinessDao businessDao;
public void processData(BusinessRequest request) {
//检查是否已经处理
boolean processed = businessDao.checkIfProcessed(request);
if(processed) {
throw new BusinessException("Request has been processed!");
}
//处理请求
businessDao.process(request);
}
}
@Repository
public class BusinessDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public boolean checkIfProcessed(BusinessRequest request) {
String sql = "SELECT COUNT(*) FROM business_table WHERE request_id=?";
int count = jdbcTemplate.queryForObject(sql, new Object[]{request.getRequestId()}, Integer.class);
return count > 0;
}
public void process(BusinessRequest request) {
String sql = "INSERT INTO business_table (request_id, data) VALUES (?,?)";
jdbcTemplate.update(sql, request.getRequestId(), request.getData());
}
}
3、拦截器
拦截器是一种在请求被处理之前或之后,拦截并处理请求的机制。通过在拦截器中实现幂等处理,可以保证相同请求只处理一次。
代码示例:
public class IdempotentInterceptor extends HandlerInterceptorAdapter {
@Autowired
private IdempotentKeyGenerator idempotentKeyGenerator;
@Autowired
private CacheManager cacheManager;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//获取幂等Key
String idempotentKey = idempotentKeyGenerator.generate(request);
if(StringUtils.isNotBlank(idempotentKey)) {
Cache cache = cacheManager.getCache("idempotent");
ValueWrapper valueWrapper = cache.get(idempotentKey);
if(valueWrapper != null && valueWrapper.get() != null) {
throw new BusinessException("Request has been processed!");
}
cache.put(idempotentKey, idempotentKey);
}
return true;
}
}
public interface IdempotentKeyGenerator {
String generate(HttpServletRequest request);
}
@Component
public class DefaultIdempotentKeyGenerator implements IdempotentKeyGenerator {
@Override
public String generate(HttpServletRequest request) {
return request.getHeader("X-idempotent-key");
}
}
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
Cache idempotentCache = new ConcurrentMapCache("idempotent", false);
cacheManager.setCaches(Arrays.asList(idempotentCache));
return cacheManager;
}
}
@Controller
public class BusinessController {
@Autowired
private BusinessService businessService;
@PostMapping("/api")
public ApiResponse businessApi(@RequestBody BusinessRequest request) {
businessService.processData(request);
return ApiResponse.ok();
}
}
@Service
public class BusinessService {
public void processData(BusinessRequest request) {
//处理请求
}
}
四、总结
幂等可以帮助我们解决重复操作的问题,确保请求的正确性和唯一性,提高系统的稳定性和安全性。在Java中,有多种实现幂等的方式,例如Token检测、数据库唯一约束、拦截器等,我们可以根据实际需求和场景选择合适的方式。
原创文章,作者:小蓝,如若转载,请注明出处:https://www.506064.com/n/301302.html
微信扫一扫
支付宝扫一扫