一、什麼是冪等
冪等指的是相同的請求執行一次和執行多次的效果是一致的,不會因為多次請求產生副作用。
對於一些數據更新操作,例如修改用戶信息、下單等操作,如果發生網絡故障或者客戶端異常,造成了多次請求,如果系統沒有冪等措施,可能導致數據多次更新,最終產生異常數據。
冪等可以有效解決重複執行的問題。
二、為什麼需要冪等
網絡通信中常常出現不穩定的情況,例如網絡中斷、超時等問題,而相同操作請求多次則會產生意料之外的結果。在銀行支付、物流發貨、物資調撥等場景中,重複操作可能會導致金錢流、庫存等數據的不一致,因此在這些場景中需要通過冪等來保證數據的一致性、正確性和安全性。
三、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/zh-hk/n/301302.html