一、什么是幂等
幂等指的是相同的请求执行一次和执行多次的效果是一致的,不会因为多次请求产生副作用。
对于一些数据更新操作,例如修改用户信息、下单等操作,如果发生网络故障或者客户端异常,造成了多次请求,如果系统没有幂等措施,可能导致数据多次更新,最终产生异常数据。
幂等可以有效解决重复执行的问题。
二、为什么需要幂等
网络通信中常常出现不稳定的情况,例如网络中断、超时等问题,而相同操作请求多次则会产生意料之外的结果。在银行支付、物流发货、物资调拨等场景中,重复操作可能会导致金钱流、库存等数据的不一致,因此在这些场景中需要通过幂等来保证数据的一致性、正确性和安全性。
三、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检测、数据库唯一约束、拦截器等,我们可以根据实际需求和场景选择合适的方式。