日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

系统幂等性设计与实践

發布時間:2025/3/19 windows 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 系统幂等性设计与实践 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

冪等性

什么是冪等性

HTTP/1.1中對冪等性的定義是:一次和多次請求某一個資源**對于資源本身**應該具有同樣的結果(網絡超時等問題除外)。也就是說,**其任意多次執行對資源本身所產生的影響均與一次執行的影響相同**。

簡單來說,是指無論調用多少次都不會有不同結果的 HTTP 方法。

什么情況下需要冪等

業務開發中,經常會遇到重復提交的情況,無論是由于網絡問題無法收到請求結果而重新發起請求,或是前端的操作抖動而造成重復提交情況。 在交易系統,支付系統這種重復提交造成的問題有尤其明顯,比如:

1.?用戶在APP上連續點擊了多次提交訂單,后臺應該只產生一個訂單;
2.?向支付寶發起支付請求,由于網絡問題或系統BUG重發,支付寶應該只扣一次錢。?**很顯然,聲明冪等的服務認為,外部調用者會存在多次調用的情況,為了防止外部多次調用對系統數據狀態的發生多次改變,將服務設計成冪等。**

解決方案

1.?樂觀鎖:基于版本號version實現, 在更新數據那一刻校驗數據(會出現ABA問題)

2.?布式鎖:redis 或 zookeeper 實現

3.?version令牌: 防止頁面重復提交

4.?防重表:防止新增臟數據

5.?消息隊列:把請求快速緩沖起來,然后異步任務處理,優點:提高吞吐量,不足:不能及時響應返回對應結果,需要后續接口監聽異步接口

實現冪等性

本次采用version令牌的方式實現冪等性,即采用 redis + version機制攔截器實現接口冪等性校驗;

實現思路:

-?首先網關是全部請求的入口點,為了保證冪等性,即需要全局統一的version機制,先獲取version,并且把version放入到redis中,然后請求業務接口時候,將上一步獲取的version,放到header中(或者參數中)進行請求
-?服務端接收到對應的請求,首先采用攔截器的方式攔截對應參數,去redis中查找是否有存在該version
-?如果存在,執行業務邏輯之前在刪除version,那么如果重復提交,由于version被刪除,則返回給客戶端提示 參數異常
-?如果本身就不存在,直接說明參數不合法

打開項目: common-spring-boot-starter

1.定義需要掃描的注解

com.open.capacity.common.annotation.ApiIdempotent

package com.open.capacity.common.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /\*\* \* 定義接口 冪等的注解 \*/ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface ApiIdempotent { }

2.定義需要啟動冪等攔截器的注解,采用Import的方式

com.open.capacity.common.annotation.EnableApiIdempotent

package com.open.capacity.common.annotation; import com.open.capacity.common.selector.ApiIdempotentImportSelector; import org.springframework.context.annotation.Import; import java.lang.annotation.\*; /\*\* \* 啟動冪等攔截器 \* @author gitgeek \* @create 2019年9月5日 \* 自動裝配starter \* 選擇器 \*/ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(ApiIdempotentImportSelector.class) public @interface EnableApiIdempotent { }

3.導入的選擇器(這里填寫好要導入的全類名就行),導入ApiIdempotentConfig

com.open.capacity.common.selector.ApiIdempotentImportSelector

package com.open.capacity.common.selector; import org.springframework.context.annotation.ImportSelector; import org.springframework.core.type.AnnotationMetadata; /\*\* \* \*/ public class ApiIdempotentImportSelector implements ImportSelector { /\*\* \* Select and return the names of which class(es) should be imported based on \* the {@link AnnotationMetadata} of the importing @{@link Configuration} class. \* \* @param importingClassMetadata \*/ @Override public String\[\] selectImports(AnnotationMetadata importingClassMetadata) { return new String\[\]{ "com.open.capacity.common.config.ApiIdempotentConfig" }; } }

4.ApiIdempotentConfig自動配置類,定義好ApiIdempotentInterceptor攔截器

com.open.capacity.common.config.ApiIdempotentConfig

package com.open.capacity.common.config; import com.open.capacity.common.interceptor.ApiIdempotentInterceptor; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import javax.annotation.Resource; @Configuration @ConditionalOnClass(WebMvcConfigurer.class) public class ApiIdempotentConfig implements WebMvcConfigurer { @Resource private RedisTemplate redisTemplate ; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new ApiIdempotentInterceptor(redisTemplate)).addPathPatterns("/\*\*") ; } }

5.ApiIdempotentInterceptor攔截器,對ApiIdempotent注解的方法 或者類進行攔截冪等接口

com.open.capacity.common.interceptor.ApiIdempotentInterceptor

package com.open.capacity.common.interceptor; import com.open.capacity.common.annotation.ApiIdempotent; import lombok.AllArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Method; @AllArgsConstructor public class ApiIdempotentInterceptor implements HandlerInterceptor { private static final String VERSION\_NAME = "version"; private RedisTemplate redisTemplate ; public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)) { return true; } HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); // TODO: 2019-08-27 獲取目標方法上的冪等注解 ApiIdempotent methodAnnotation = method.getAnnotation(ApiIdempotent.class); if (methodAnnotation != null) { checkApiIdempotent(request);// 冪等性校驗, 校驗通過則放行, 校驗失敗則拋出異常, 并通過統一異常處理返回友好提示 } return true; } private void checkApiIdempotent(HttpServletRequest request) { String version = request.getHeader(VERSION\_NAME); if (StringUtils.isBlank(version)) {// header中不存在version version = request.getParameter(VERSION\_NAME); if (StringUtils.isBlank(version)) {// parameter中也不存在version throw new IllegalArgumentException("無效的參數"); } } if (!redisTemplate.hasKey(version)) { throw new IllegalArgumentException("不存在對應的參數"); } Boolean bool = redisTemplate.delete(version); if (!bool) { throw new IllegalArgumentException("沒有刪除對應的version"); } } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }

## 如何使用

1.UserCenterApp 用戶中心,在啟動類上加@EnableApiIdempotent啟動冪等攔截器,然后通過@ApiIdempotent注解

**com.open.capacity.UserCenterApp**

package com.open.capacity; import com.open.capacity.common.annotation.EnableApiIdempotent; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.context.annotation.Configuration; import com.open.capacity.common.port.PortApplicationEnvironmentPreparedEventListener; import com.open.capacity.log.annotation.EnableLogging; /\*\* \* @author 作者 owen E-mail: 624191343@qq.com \* @version 創建時間:2018年4月5日 下午19:52:21 \* 類說明 \*/ @Configuration @EnableLogging @EnableDiscoveryClient @SpringBootApplication @EnableApiIdempotent public class UserCenterApp { public static void main(String\[\] args) { // 固定端口啟動 // SpringApplication.run(UserCenterApp.class, args); //隨機端口啟動 SpringApplication app = new SpringApplication(UserCenterApp.class); app.addListeners(new PortApplicationEnvironmentPreparedEventListener()); app.run(args); } }

2.SysUserController 控制層

@ApiIdempotent 標記了該方法需要接口冪等

**com.open.capacity.user.controller.SysUserController**

/\*\* \* @author 作者 owen E-mail: 624191343@qq.com \* @version 創建時間:2017年11月12日 上午22:57:51 \*用戶 \*/ @Slf4j @RestController @Api(tags = "USER API") public class SysUserController { @Autowired private SysUserService sysUserService; /\*\* \* 測試冪等接口 \* @param sysUser \* @return \*/ @PostMapping("/users/save") @ApiIdempotent public Result save(@RequestBody SysUser sysUser) { return sysUserService.saveOrUpdate(sysUser); } }

## 整體流程

1.首先進去網關,**api-gateway**項目,先通過 getVersion 獲取對應的版本號,這個版本號可以根據自己業務修改對應的格式

**com.open.capacity.client.controller.UserController**

/\*\* \* @author 作者 owen E-mail: 624191343@qq.com \* @version 創建時間:2018年4月5日 下午19:52:21 \*/ @RestController public class UserController { @GetMapping("/getVersion") public Result token() { String str = RandomUtil.randomString(24); StrBuilder token = new StrBuilder(); token.append(str); redisTemplate.opsForValue().set(token.toString(), token.toString(),300); return Result.succeed(token.toString(),""); } } curl -i -X GET \\ 'http://127.0.0.1:9200/getVersion' { "datas": "8329lw34ii7ctsgibdfdkm2z", "resp\_code": 0, "resp\_msg": "" }

2.請求冪等接口,這里單獨寫一個接口;@ApiIdempotent被該注解標記的接口,需要在在頭部或者在參數加入version參數,否則無法過接口;

com.open.capacity.user.controller.SysUserController

/\*\* \* @author 作者 owen E-mail: 624191343@qq.com \* @version 創建時間:2017年11月12日 上午22:57:51 \*用戶 \*/ @Slf4j @RestController @Api(tags = "USER API") public class SysUserController { @Autowired private SysUserService sysUserService; /\*\* \* 測試冪等接口 \* @param sysUser \* @return \*/ @PostMapping("/users/save") @ApiIdempotent public Result save(@RequestBody SysUser sysUser) { return sysUserService.saveOrUpdate(sysUser); } } curl -i -X POST \\ -H "Content-Type:application/json" \\ -H "version:qcrro9jkymsx2t5b6ij3lc0p" \\ -d \\ '{ "id": "", "username": "admin", "nickname": "admin", "phone": "15914395926", "sex": "0", "roleId": "1" }' \\ 'http://127.0.0.1:9200/api-user/users/save'

總結

以上是生活随笔為你收集整理的系统幂等性设计与实践的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。