色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Spring Boot實(shí)現(xiàn)接口冪等性的4種方案

Android編程精選 ? 來源:博客園 ? 作者:指尖上的代碼go ? 2022-11-08 10:21 ? 次閱讀

	


		

		

一、什么是冪等性

冪等是一個(gè)數(shù)學(xué)與計(jì)算機(jī)學(xué)概念,在數(shù)學(xué)中某一元運(yùn)算為冪等時(shí),其作用在任一元素兩次后會(huì)和其作用一次的結(jié)果相同。在計(jì)算機(jī)中編程中,一個(gè)冪等操作的特點(diǎn)是其任意多次執(zhí)行所產(chǎn)生的影響均與一次執(zhí)行的影響相同。冪等函數(shù)或冪等方法是指可以使用相同參數(shù)重復(fù)執(zhí)行,并能獲得相同結(jié)果的函數(shù)。這些函數(shù)不會(huì)影響系統(tǒng)狀態(tài),也不用擔(dān)心重復(fù)執(zhí)行會(huì)對系統(tǒng)造成改變。

二、什么是接口冪等性

在HTTP/1.1中,對冪等性進(jìn)行了定義。它描述了一次和多次請求某一個(gè)資源對于資源本身應(yīng)該具有同樣的結(jié)果(網(wǎng)絡(luò)超時(shí)等問題除外),即第一次請求的時(shí)候?qū)Y源產(chǎn)生了副作用,但是以后的多次請求都不會(huì)再對資源產(chǎn)生副作用。這里的副作用是不會(huì)對結(jié)果產(chǎn)生破壞或者產(chǎn)生不可預(yù)料的結(jié)果。也就是說,其任意多次執(zhí)行對資源本身所產(chǎn)生的影響均與一次執(zhí)行的影響相同。

三、為什么需要實(shí)現(xiàn)冪等性

在接口調(diào)用時(shí)一般情況下都能正常返回信息不會(huì)重復(fù)提交,不過在遇見以下情況時(shí)可以就會(huì)出現(xiàn)問題,如:
  • 前端重復(fù)提交表單:在填寫一些表格時(shí)候,用戶填寫完成提交,很多時(shí)候會(huì)因網(wǎng)絡(luò)波動(dòng)沒有及時(shí)對用戶做出提交成功響應(yīng),致使用戶認(rèn)為沒有成功提交,然后一直點(diǎn)提交按鈕,這時(shí)就會(huì)發(fā)生重復(fù)提交表單請求。
  • 用戶惡意進(jìn)行刷單:例如在實(shí)現(xiàn)用戶投票這種功能時(shí),如果用戶針對一個(gè)用戶進(jìn)行重復(fù)提交投票,這樣會(huì)導(dǎo)致接口接收到用戶重復(fù)提交的投票信息,這樣會(huì)使投票結(jié)果與事實(shí)嚴(yán)重不符。
  • 接口超時(shí)重復(fù)提交:很多時(shí)候 HTTP 客戶端工具都默認(rèn)開啟超時(shí)重試的機(jī)制,尤其是第三方調(diào)用接口時(shí)候,為了防止網(wǎng)絡(luò)波動(dòng)超時(shí)等造成的請求失敗,都會(huì)添加重試機(jī)制,導(dǎo)致一個(gè)請求提交多次。
  • 消息進(jìn)行重復(fù)消費(fèi):當(dāng)使用 MQ 消息中間件時(shí)候,如果發(fā)生消息中間件出現(xiàn)錯(cuò)誤未及時(shí)提交消費(fèi)信息,導(dǎo)致發(fā)生重復(fù)消費(fèi)。
使用冪等性最大的優(yōu)勢在于使接口保證任何冪等性操作,免去因重試等造成系統(tǒng)產(chǎn)生的未知的問題。

四、引入冪等性后對系統(tǒng)的影響

冪等性是為了簡化客戶端邏輯處理,能放置重復(fù)提交等操作,但卻增加了服務(wù)端的邏輯復(fù)雜性和成本,其主要是:
  • 把并行執(zhí)行的功能改為串行執(zhí)行,降低了執(zhí)行效率。
  • 增加了額外控制冪等的業(yè)務(wù)邏輯,復(fù)雜化了業(yè)務(wù)功能;
所以在使用時(shí)候需要考慮是否引入冪等性的必要性,根據(jù)實(shí)際業(yè)務(wù)場景具體分析,除了業(yè)務(wù)上的特殊要求外,一般情況下不需要引入的接口冪等性。

五、Restful API 接口的冪等性

現(xiàn)在流行的 Restful 推薦的幾種 HTTP 接口方法中,分別存在冪等行與不能保證冪等的方法,如下:

  • √ 滿足冪等
  • x 不滿足冪等
  • - 可能滿足也可能不滿足冪等,根據(jù)實(shí)際業(yè)務(wù)邏輯有關(guān)
方法類型 是否冪等 描述
Get Get 方法用于獲取資源。其一般不會(huì)也不應(yīng)當(dāng)對系統(tǒng)資源進(jìn)行改變,所以是冪等的。
Post × Post 方法一般用于創(chuàng)建新的資源。其每次執(zhí)行都會(huì)新增數(shù)據(jù),所以不是冪等的。
Put - Put 方法一般用于修改資源。該操作則分情況來判斷是不是滿足冪等,更新操作中直接根據(jù)某個(gè)值進(jìn)行更新,也能保持冪等。不過執(zhí)行累加操作的更新是非冪等。
Delete - Delete 方法一般用于刪除資源。該操作則分情況來判斷是不是滿足冪等,當(dāng)根據(jù)唯一值進(jìn)行刪除時(shí),刪除同一個(gè)數(shù)據(jù)多次執(zhí)行效果一樣。不過需要注意,帶查詢條件的刪除則就不一定滿足冪等了。例如在根據(jù)條件刪除一批數(shù)據(jù)后,這時(shí)候新增加了一條數(shù)據(jù)也滿足條件,然后又執(zhí)行了一次刪除,那么將會(huì)導(dǎo)致新增加的這條滿足條件數(shù)據(jù)也被刪除。

六、如何實(shí)現(xiàn)冪等性

方案一:數(shù)據(jù)庫唯一主鍵

方案描述

數(shù)據(jù)庫唯一主鍵的實(shí)現(xiàn)主要是利用數(shù)據(jù)庫中主鍵唯一約束的特性,一般來說唯一主鍵比較適用于“插入”時(shí)的冪等性,其能保證一張表中只能存在一條帶該唯一主鍵的記錄。使用數(shù)據(jù)庫唯一主鍵完成冪等性時(shí)需要注意的是,該主鍵一般來說并不是使用數(shù)據(jù)庫中自增主鍵,而是使用分布式 ID 充當(dāng)主鍵(可以參考 Java 中分布式 ID 的設(shè)計(jì)方案 這篇文章),這樣才能能保證在分布式環(huán)境下 ID 的全局唯一性。

適用操作:

  • 插入操作
  • 刪除操作

使用限制:

  • 需要生成全局唯一主鍵 ID;

主要流程:

679fad9c-5e9d-11ed-8abf-dac502259ad0.png

主要流程:

  • ① 客戶端執(zhí)行創(chuàng)建請求,調(diào)用服務(wù)端接口。
  • ② 服務(wù)端執(zhí)行業(yè)務(wù)邏輯,生成一個(gè)分布式 ID,將該 ID 充當(dāng)待插入數(shù)據(jù)的主鍵,然后執(zhí)數(shù)據(jù)插入操作,運(yùn)行對應(yīng)的 SQL 語句。
  • ③ 服務(wù)端將該條數(shù)據(jù)插入數(shù)據(jù)庫中,如果插入成功則表示沒有重復(fù)調(diào)用接口。如果拋出主鍵重復(fù)異常,則表示數(shù)據(jù)庫中已經(jīng)存在該條記錄,返回錯(cuò)誤信息到客戶端。

方案二:數(shù)據(jù)庫樂觀鎖

方案描述:

數(shù)據(jù)庫樂觀鎖方案一般只能適用于執(zhí)行“更新操作”的過程,我們可以提前在對應(yīng)的數(shù)據(jù)表中多添加一個(gè)字段,充當(dāng)當(dāng)前數(shù)據(jù)的版本標(biāo)識。這樣每次對該數(shù)據(jù)庫該表的這條數(shù)據(jù)執(zhí)行更新時(shí),都會(huì)將該版本標(biāo)識作為一個(gè)條件,值為上次待更新數(shù)據(jù)中的版本標(biāo)識的值。

適用操作:

  • 更新操作

使用限制:

  • 需要數(shù)據(jù)庫對應(yīng)業(yè)務(wù)表中添加額外字段;

描述示例:

例如,存在如下的數(shù)據(jù)表中:

id name price
1 小米手機(jī) 1000
2 蘋果手機(jī) 2500
3 華為手機(jī) 1600

為了每次執(zhí)行更新時(shí)防止重復(fù)更新,確定更新的一定是要更新的內(nèi)容,我們通常都會(huì)添加一個(gè) version 字段記錄當(dāng)前的記錄版本,這樣在更新時(shí)候?qū)⒃撝祹希敲粗灰獔?zhí)行更新操作就能確定一定更新的是某個(gè)對應(yīng)版本下的信息。

id name price version
1 小米手機(jī) 1000 10
2 蘋果手機(jī) 2500 21
3 華為手機(jī) 1600 5

這樣每次執(zhí)行更新時(shí)候,都要指定要更新的版本號,如下操作就能準(zhǔn)確更新 version=5 的信息:
UPDATE my_table SET price=price+50,version=version+1 WHERE id=1 AND version=5
上面 WHERE 后面跟著條件 id=1 AND version=5 被執(zhí)行后,id=1 的 version 被更新為 6,所以如果重復(fù)執(zhí)行該條 SQL 語句將不生效,因?yàn)?id=1 AND version=5 的數(shù)據(jù)已經(jīng)不存在,這樣就能保住更新的冪等,多次更新對結(jié)果不會(huì)產(chǎn)生影響。

方案三:防重 Token 令牌

方案描述:

針對客戶端連續(xù)點(diǎn)擊或者調(diào)用方的超時(shí)重試等情況,例如提交訂單,此種操作就可以用 Token 的機(jī)制實(shí)現(xiàn)防止重復(fù)提交。簡單的說就是調(diào)用方在調(diào)用接口的時(shí)候先向后端請求一個(gè)全局 ID(Token),請求的時(shí)候攜帶這個(gè)全局 ID 一起請求(Token 最好將其放到 Headers 中),后端需要對這個(gè) Token 作為 Key,用戶信息作為 Value 到 Redis 中進(jìn)行鍵值內(nèi)容校驗(yàn),如果 Key 存在且 Value 匹配就執(zhí)行刪除命令,然后正常執(zhí)行后面的業(yè)務(wù)邏輯。如果不存在對應(yīng)的 Key 或 Value 不匹配就返回重復(fù)執(zhí)行的錯(cuò)誤信息,這樣來保證冪等操作。

適用操作:

  • 插入操作
  • 更新操作
  • 刪除操作

使用限制:

  • 需要生成全局唯一 Token 串;
  • 需要使用第三方組件 Redis 進(jìn)行數(shù)據(jù)效驗(yàn);

主要流程:

67b8df1a-5e9d-11ed-8abf-dac502259ad0.png

  • ① 服務(wù)端提供獲取 Token 的接口,該 Token 可以是一個(gè)序列號,也可以是一個(gè)分布式 ID 或者 UUID 串。

  • ② 客戶端調(diào)用接口獲取 Token,這時(shí)候服務(wù)端會(huì)生成一個(gè) Token 串。

  • ③ 然后將該串存入 Redis 數(shù)據(jù)庫中,以該 Token 作為 Redis 的鍵(注意設(shè)置過期時(shí)間)。

  • ④ 將 Token 返回到客戶端,客戶端拿到后應(yīng)存到表單隱藏域中。

  • ⑤ 客戶端在執(zhí)行提交表單時(shí),把 Token 存入到 Headers 中,執(zhí)行業(yè)務(wù)請求帶上該 Headers。

  • ⑥ 服務(wù)端接收到請求后從 Headers 中拿到 Token,然后根據(jù) Token 到 Redis 中查找該 key 是否存在。

  • ⑦ 服務(wù)端根據(jù) Redis 中是否存該 key 進(jìn)行判斷,如果存在就將該 key 刪除,然后正常執(zhí)行業(yè)務(wù)邏輯。如果不存在就拋異常,返回重復(fù)提交的錯(cuò)誤信息。

注意,在并發(fā)情況下,執(zhí)行 Redis 查找數(shù)據(jù)與刪除需要保證原子性,否則很可能在并發(fā)下無法保證冪等性。其實(shí)現(xiàn)方法可以使用分布式鎖或者使用 Lua 表達(dá)式來注銷查詢與刪除操作。

方案四、下游傳遞唯一序列號

方案描述:

所謂請求序列號,其實(shí)就是每次向服務(wù)端請求時(shí)候附帶一個(gè)短時(shí)間內(nèi)唯一不重復(fù)的序列號,該序列號可以是一個(gè)有序 ID,也可以是一個(gè)訂單號,一般由下游生成,在調(diào)用上游服務(wù)端接口時(shí)附加該序列號和用于認(rèn)證的 ID。當(dāng)上游服務(wù)器收到請求信息后拿取該 序列號 和下游 認(rèn)證ID 進(jìn)行組合,形成用于操作 Redis 的 Key,然后到 Redis 中查詢是否存在對應(yīng)的 Key 的鍵值對,根據(jù)其結(jié)果:
  • 如果存在,就說明已經(jīng)對該下游的該序列號的請求進(jìn)行了業(yè)務(wù)處理,這時(shí)可以直接響應(yīng)重復(fù)請求的錯(cuò)誤信息。
  • 如果不存在,就以該 Key 作為 Redis 的鍵,以下游關(guān)鍵信息作為存儲的值(例如下游商傳遞的一些業(yè)務(wù)邏輯信息),將該鍵值對存儲到 Redis 中 ,然后再正常執(zhí)行對應(yīng)的業(yè)務(wù)邏輯即可。

適用操作:

  • 插入操作
  • 更新操作
  • 刪除操作

使用限制:

  • 要求第三方傳遞唯一序列號;
  • 需要使用第三方組件 Redis 進(jìn)行數(shù)據(jù)效驗(yàn);

主要流程:

主要步驟:

  • ① 下游服務(wù)生成分布式 ID 作為序列號,然后執(zhí)行請求調(diào)用上游接口,并附帶“唯一序列號”與請求的“認(rèn)證憑據(jù)ID”。

  • ② 上游服務(wù)進(jìn)行安全效驗(yàn),檢測下游傳遞的參數(shù)中是否存在“序列號”和“憑據(jù)ID”。

  • ③ 上游服務(wù)到 Redis 中檢測是否存在對應(yīng)的“序列號”與“認(rèn)證ID”組成的 Key,如果存在就拋出重復(fù)執(zhí)行的異常信息,然后響應(yīng)下游對應(yīng)的錯(cuò)誤信息。如果不存在就以該“序列號”和“認(rèn)證ID”組合作為 Key,以下游關(guān)鍵信息作為 Value,進(jìn)而存儲到 Redis 中,然后正常執(zhí)行接來來的業(yè)務(wù)邏輯。

上面步驟中插入數(shù)據(jù)到 Redis 一定要設(shè)置過期時(shí)間。這樣能保證在這個(gè)時(shí)間范圍內(nèi),如果重復(fù)調(diào)用接口,則能夠進(jìn)行判斷識別。如果不設(shè)置過期時(shí)間,很可能導(dǎo)致數(shù)據(jù)無限量的存入 Redis,致使 Redis 不能正常工作。

七、實(shí)現(xiàn)接口冪等示例

這里使用防重 Token 令牌方案,該方案能保證在不同請求動(dòng)作下的冪等性,實(shí)現(xiàn)邏輯可以看上面寫的”防重 Token 令牌”方案,接下來寫下實(shí)現(xiàn)這個(gè)邏輯的代碼。

1、Maven 引入相關(guān)依賴

這里使用 Maven 工具管理依賴,這里在 pom.xml 中引入 SpringBoot、Redis、lombok 相關(guān)依賴。
"http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">  <modelVersion>4.0.0modelVersion>

<parent> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-parentartifactId> <version>2.3.4.RELEASEversion> parent>

<groupId>mydlq.clubgroupId> <artifactId>springboot-idempotent-tokenartifactId> <version>0.0.1version> <name>springboot-idempotent-tokenname> <description>Idempotent Demodescription>

<properties> <java.version>1.8java.version> properties>

<dependencies> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-webartifactId> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-data-redisartifactId> dependency> <dependency> <groupId>org.apache.commonsgroupId> <artifactId>commons-pool2artifactId> dependency> <dependency> <groupId>org.projectlombokgroupId> <artifactId>lombokartifactId> dependency> dependencies>

<build> <plugins> <plugin> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-maven-pluginartifactId> plugin> plugins> build>

2、配置連接 Redis 的參數(shù)

在 application 配置文件中配置連接 Redis 的參數(shù)。Spring Boot 基礎(chǔ)就不介紹了,最新教程推薦看下面的教程。

如下:

spring: redis:  ssl: false  host: 127.0.0.1  port: 6379  database: 0  timeout: 1000  password:  lettuce:   pool:    max-active: 100    max-wait: -1    min-idle: 0    max-idle: 20

3、創(chuàng)建與驗(yàn)證 Token 工具類

創(chuàng)建用于操作 Token 相關(guān)的 Service 類,里面存在 Token 創(chuàng)建與驗(yàn)證方法,其中:

  • Token 創(chuàng)建方法:使用 UUID 工具創(chuàng)建 Token 串,設(shè)置以 “idempotent_token:“+“Token串” 作為 Key,以用戶信息當(dāng)成 Value,將信息存入 Redis 中。
  • Token 驗(yàn)證方法:接收 Token 串參數(shù),加上 Key 前綴形成 Key,再傳入 value 值,執(zhí)行 Lua 表達(dá)式(Lua 表達(dá)式能保證命令執(zhí)行的原子性)進(jìn)行查找對應(yīng) Key 與刪除操作。執(zhí)行完成后驗(yàn)證命令的返回結(jié)果,如果結(jié)果不為空且非0,則驗(yàn)證成功,否則失敗。
import java.util.Arrays;import java.util.UUID;import java.util.concurrent.TimeUnit;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.data.redis.core.script.DefaultRedisScript;import org.springframework.data.redis.core.script.RedisScript;import org.springframework.stereotype.Service;

@Slf4j@Servicepublic class TokenUtilService {

@Autowired private StringRedisTemplate redisTemplate;

/** * 存入 Redis 的 Token 鍵的前綴 */ private static final String IDEMPOTENT_TOKEN_PREFIX = "idempotent_token:";

/** * 創(chuàng)建 Token 存入 Redis,并返回該 Token * * @param value 用于輔助驗(yàn)證的 value 值 * @return 生成的 Token 串 */ public String generateToken(String value) { // 實(shí)例化生成 ID 工具對象 String token = UUID.randomUUID().toString(); // 設(shè)置存入 Redis 的 Key String key = IDEMPOTENT_TOKEN_PREFIX + token; // 存儲 Token 到 Redis,且設(shè)置過期時(shí)間為5分鐘 redisTemplate.opsForValue().set(key, value, 5, TimeUnit.MINUTES); // 返回 Token return token; }

/** * 驗(yàn)證 Token 正確性 * * @param token token 字符串 * @param value value 存儲在Redis中的輔助驗(yàn)證信息 * @return 驗(yàn)證結(jié)果 */ public boolean validToken(String token, String value) { // 設(shè)置 Lua 腳本,其中 KEYS[1] 是 key,KEYS[2] 是 value String script = "if redis.call('get', KEYS[1]) == KEYS[2] then return redis.call('del', KEYS[1]) else return 0 end"; RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class); // 根據(jù) Key 前綴拼接 Key String key = IDEMPOTENT_TOKEN_PREFIX + token; // 執(zhí)行 Lua 腳本 Long result = redisTemplate.execute(redisScript, Arrays.asList(key, value)); // 根據(jù)返回結(jié)果判斷是否成功成功匹配并刪除 Redis 鍵值對,若果結(jié)果不為空和0,則驗(yàn)證通過 if (result != null && result != 0L) { log.info("驗(yàn)證 token={},key={},value={} 成功", token, key, value); return true; } log.info("驗(yàn)證 token={},key={},value={} 失敗", token, key, value); return false; }

}

							

4、創(chuàng)建測試的 Controller 類

創(chuàng)建用于測試的 Controller 類,里面有獲取 Token 與測試接口冪等性的接口,內(nèi)容如下:

import lombok.extern.slf4j.Slf4j;import mydlq.club.example.service.TokenUtilService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;

@Slf4j@RestControllerpublic class TokenController {

@Autowired private TokenUtilService tokenService;

/** * 獲取 Token 接口 * * @return Token 串 */ @GetMapping("/token") public String getToken() { // 獲取用戶信息(這里使用模擬數(shù)據(jù)) // 注:這里存儲該內(nèi)容只是舉例,其作用為輔助驗(yàn)證,使其驗(yàn)證邏輯更安全,如這里存儲用戶信息,其目的為: // - 1)、使用"token"驗(yàn)證 Redis 中是否存在對應(yīng)的 Key // - 2)、使用"用戶信息"驗(yàn)證 Redis 的 Value 是否匹配。 String userInfo = "mydlq"; // 獲取 Token 字符串,并返回 return tokenService.generateToken(userInfo); }

/** * 接口冪等性測試接口 * * @param token 冪等 Token 串 * @return 執(zhí)行結(jié)果 */ @PostMapping("/test") public String test(@RequestHeader(value = "token") String token) { // 獲取用戶信息(這里使用模擬數(shù)據(jù)) String userInfo = "mydlq"; // 根據(jù) Token 和與用戶相關(guān)的信息到 Redis 驗(yàn)證是否存在對應(yīng)的信息 boolean result = tokenService.validToken(token, userInfo); // 根據(jù)驗(yàn)證結(jié)果響應(yīng)不同信息 return result ? "正常調(diào)用" : "重復(fù)調(diào)用"; }

}

							

5、創(chuàng)建 SpringBoot 啟動(dòng)類

創(chuàng)建啟動(dòng)類,用于啟動(dòng) SpringBoot 應(yīng)用。基礎(chǔ)教程就不介紹了,建議看下下面的教程,很全了。

import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplicationpublic class Application {

public static void main(String[] args) { SpringApplication.run(Application.class, args); }

}

6、寫測試類進(jìn)行測試

寫個(gè)測試類進(jìn)行測試,多次訪問同一個(gè)接口,測試是否只有第一次能否執(zhí)行成功。

import org.junit.Assert;import org.junit.Test;import org.junit.runner.RunWith;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.http.MediaType;import org.springframework.test.context.junit4.SpringRunner;import org.springframework.test.web.servlet.MockMvc;import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;import org.springframework.test.web.servlet.setup.MockMvcBuilders;import org.springframework.web.context.WebApplicationContext;

@Slf4j@SpringBootTest@RunWith(SpringRunner.class)public class IdempotenceTest {

@Autowired private WebApplicationContext webApplicationContext;

@Test public void interfaceIdempotenceTest() throws Exception { // 初始化 MockMvc MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); // 調(diào)用獲取 Token 接口 String token = mockMvc.perform(MockMvcRequestBuilders.get("/token") .accept(MediaType.TEXT_HTML)) .andReturn() .getResponse().getContentAsString(); log.info("獲取的 Token 串:{}", token); // 循環(huán)調(diào)用 5 次進(jìn)行測試 for (int i = 1; i <= 5; i++) { log.info("第{}次調(diào)用測試接口", i); // 調(diào)用驗(yàn)證接口并打印結(jié)果 String result = mockMvc.perform(MockMvcRequestBuilders.post("/test") .header("token", token) .accept(MediaType.TEXT_HTML)) .andReturn().getResponse().getContentAsString(); log.info(result); // 結(jié)果斷言 if (i == 0) { Assert.assertEquals(result, "正常調(diào)用"); } else { Assert.assertEquals(result, "重復(fù)調(diào)用"); } } }

}

							

顯示如下:

[main] IdempotenceTest: 獲取的 Token 串:980ea707-ce2e-456e-a059-0a03332110b4[main] IdempotenceTest: 第1次調(diào)用測試接口[main] IdempotenceTest: 正常調(diào)用[main] IdempotenceTest: 第2次調(diào)用測試接口[main] IdempotenceTest: 重復(fù)調(diào)用[main] IdempotenceTest: 第3次調(diào)用測試接口[main] IdempotenceTest: 重復(fù)調(diào)用[main] IdempotenceTest: 第4次調(diào)用測試接口[main] IdempotenceTest: 重復(fù)調(diào)用[main] IdempotenceTest: 第5次調(diào)用測試接口[main] IdempotenceTest: 重復(fù)調(diào)用

							

八、最后總結(jié)

冪等性是開發(fā)當(dāng)中很常見也很重要的一個(gè)需求,尤其是支付、訂單等與金錢掛鉤的服務(wù),保證接口冪等性尤其重要。在實(shí)際開發(fā)中,我們需要針對不同的業(yè)務(wù)場景我們需要靈活的選擇冪等性的實(shí)現(xiàn)方式:

  • 對于下單等存在唯一主鍵的,可以使用“唯一主鍵方案”的方式實(shí)現(xiàn)。
  • 對于更新訂單狀態(tài)等相關(guān)的更新場景操作,使用“樂觀鎖方案”實(shí)現(xiàn)更為簡單。
  • 對于上下游這種,下游請求上游,上游服務(wù)可以使用“下游傳遞唯一序列號方案”更為合理。
  • 類似于前端重復(fù)提交、重復(fù)下單、沒有唯一ID號的場景,可以通過 Token 與 Redis 配合的“防重 Token 方案”實(shí)現(xiàn)更為快捷。
上面只是給與一些建議,再次強(qiáng)調(diào)一下,實(shí)現(xiàn)冪等性需要先理解自身業(yè)務(wù)需求,根據(jù)業(yè)務(wù)邏輯來實(shí)現(xiàn)這樣才合理,處理好其中的每一個(gè)結(jié)點(diǎn)細(xì)節(jié),完善整體的業(yè)務(wù)流程設(shè)計(jì),才能更好的保證系統(tǒng)的正常運(yùn)行。最后做一個(gè)簡單總結(jié)

方案名稱 適用方法 實(shí)現(xiàn)復(fù)雜度 方案缺點(diǎn)
數(shù)據(jù)庫唯一主鍵 插入操作 刪除操作 簡單 - 只能用于插入操作;- 只能用于存在唯一主鍵場景;
數(shù)據(jù)庫樂觀鎖 更新操作 簡單 - 只能用于更新操作;- 表中需要額外添加字段;
請求序列號 插入操作 更新操作 刪除操作 簡單 - 需要保證下游生成唯一序列號;- 需要 Redis 第三方存儲已經(jīng)請求的序列號;
防重 Token 令牌 插入操作 更新操作 刪除操作 適中 - 需要 Redis 第三方存儲生成的 Token 串;
審核編輯:湯梓紅

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • 接口
    +關(guān)注

    關(guān)注

    33

    文章

    8691

    瀏覽量

    151715
  • spring
    +關(guān)注

    關(guān)注

    0

    文章

    340

    瀏覽量

    14368
  • Boot
    +關(guān)注

    關(guān)注

    0

    文章

    150

    瀏覽量

    35898

原文標(biāo)題:Spring Boot 實(shí)現(xiàn)接口冪等性的 4 種方案

文章出處:【微信號:AndroidPush,微信公眾號:Android編程精選】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    Spring Boot如何實(shí)現(xiàn)異步任務(wù)

    Spring Boot 提供了多種方式來實(shí)現(xiàn)異步任務(wù),這里介紹三主要實(shí)現(xiàn)方式。 1、基于注解 @Async @Async 注解是
    的頭像 發(fā)表于 09-30 10:32 ?1460次閱讀

    啟動(dòng)Spring Boot項(xiàng)目應(yīng)用的三方法

    基礎(chǔ)。我們知道了Spring Boot是個(gè)什么了,那么我們又該如何啟動(dòng)Spring Boot應(yīng)用呢?這里小編給大家推薦常用的三方法。分別是
    發(fā)表于 01-14 17:33

    Spring Boot從零入門1 詳述

    在開始學(xué)習(xí)Spring Boot之前,我之前從未接觸過Spring相關(guān)的項(xiàng)目,Java基礎(chǔ)還是幾年前自學(xué)的,現(xiàn)在估計(jì)也忘得差不多了吧,寫Spring
    的頭像 發(fā)表于 12-10 22:18 ?662次閱讀

    Spring Boot特有的實(shí)踐

    Spring Boot是最流行的用于開發(fā)微服務(wù)的Java框架。在本文中,我將與你分享自2016年以來我在專業(yè)開發(fā)中使用Spring Boot所采用的最佳實(shí)踐。這些內(nèi)容是基于我的個(gè)人經(jīng)驗(yàn)
    的頭像 發(fā)表于 09-29 10:24 ?941次閱讀

    分析解決)的方法

    這個(gè)概念,是一個(gè)數(shù)學(xué)上的概念,即:f……(f(f(x))) = f(x)。用在計(jì)算機(jī)領(lǐng)域,指的是系統(tǒng)里的接口或方法對外的一承諾,使用相同參數(shù)對同一資源重復(fù)調(diào)用某個(gè)
    的頭像 發(fā)表于 10-14 10:08 ?1004次閱讀

    強(qiáng)大的Spring Boot 3.0要來了

    和 Bugfix。 Spring Boot 3.0 的開發(fā)工作始于實(shí)驗(yàn)Spring Native,旨在為 GraalVM 原生鏡像提供支持。 在該版本中,開發(fā)者現(xiàn)在可以使用標(biāo)準(zhǔn)
    的頭像 發(fā)表于 10-31 11:17 ?1943次閱讀

    什么是實(shí)現(xiàn)原理

    在編程中一個(gè)操作的特點(diǎn)是其任意多次執(zhí)行所產(chǎn)生的影響均與一次執(zhí)行的影響相同。函數(shù),或
    發(fā)表于 01-05 10:40 ?6232次閱讀

    Spring Boot Web相關(guān)的基礎(chǔ)知識

    Boot的第一個(gè)接口。接下來將會(huì)將會(huì)介紹使用Spring Boot開發(fā)Web應(yīng)用的相關(guān)內(nèi)容,其主要包括使用spring-boot-star
    的頭像 發(fā)表于 03-17 15:03 ?683次閱讀

    Spring Boot中整合兩定時(shí)任務(wù)的方法

    Spring + SpringMVC 環(huán)境中,一般來說,要實(shí)現(xiàn)定時(shí)任務(wù),我們有兩中方案,一是使用 Spring 自帶的定時(shí)任務(wù)處理器
    的頭像 發(fā)表于 04-07 14:55 ?1590次閱讀
    <b class='flag-5'>Spring</b> <b class='flag-5'>Boot</b>中整合兩<b class='flag-5'>種</b>定時(shí)任務(wù)的方法

    Spring Boot如何優(yōu)雅實(shí)現(xiàn)數(shù)據(jù)加密存儲、模糊匹配和脫敏

    近來我們都在圍繞著使用Spring Boot開發(fā)業(yè)務(wù)系統(tǒng)時(shí)如何保證數(shù)據(jù)安全這個(gè)主題展開總結(jié),當(dāng)下大部分的B/S架構(gòu)的系統(tǒng)也都是基于Spring B
    的頭像 發(fā)表于 06-19 14:42 ?2010次閱讀
    <b class='flag-5'>Spring</b> <b class='flag-5'>Boot</b>如何優(yōu)雅<b class='flag-5'>實(shí)現(xiàn)</b>數(shù)據(jù)加密存儲、模糊匹配和脫敏

    基于接口解決方案

    接口是指無論調(diào)用接口的次數(shù)是一次還是多次,對于同一資源的操作都只會(huì)產(chǎn)生一次結(jié)果。換句話說,多次重復(fù)調(diào)用相同的
    的頭像 發(fā)表于 09-30 16:27 ?456次閱讀
    基于<b class='flag-5'>接口</b><b class='flag-5'>冪</b><b class='flag-5'>等</b><b class='flag-5'>性</b>解決<b class='flag-5'>方案</b>

    Spring Boot Actuator快速入門

    不知道大家在寫 Spring Boot 項(xiàng)目的過程中,使用過 Spring Boot Actuator 嗎?知道 Spring
    的頭像 發(fā)表于 10-09 17:11 ?662次閱讀

    Spring Boot啟動(dòng) Eureka流程

    在上篇中已經(jīng)說過了 Eureka-Server 本質(zhì)上是一個(gè) web 應(yīng)用的項(xiàng)目,今天就來看看 Spring Boot 是怎么啟動(dòng) Eureka 的。 Spring Boot 啟動(dòng) E
    的頭像 發(fā)表于 10-10 11:40 ?913次閱讀
    <b class='flag-5'>Spring</b> <b class='flag-5'>Boot</b>啟動(dòng) Eureka流程

    Spring Boot的啟動(dòng)原理

    spring-boot-maven-plugin 的 maven 項(xiàng)目打包插件,可以方便的將 Spring Boot 項(xiàng)目打成 jar 包。這樣我們就不再需要部署 Tomcat 、Jetty
    的頭像 發(fā)表于 10-13 11:44 ?677次閱讀
    <b class='flag-5'>Spring</b> <b class='flag-5'>Boot</b>的啟動(dòng)原理

    Spring Boot 的設(shè)計(jì)目標(biāo)

    什么是Spring Boot Spring BootSpring 開源組織下的一個(gè)子項(xiàng)目,也是 S
    的頭像 發(fā)表于 10-13 14:56 ?609次閱讀
    <b class='flag-5'>Spring</b> <b class='flag-5'>Boot</b> 的設(shè)計(jì)目標(biāo)
    主站蜘蛛池模板: 第一次处破女18分钟免费 | 色一伦一情一区二区三区 | 一二三四在线播放免费观看中文版视频 | 国产成人免费在线 | 扒开她的黑森林让我添动态图 | 69久久国产精品热88人妻 | 被同桌摸出水来了好爽的视频 | 牲高潮99爽久久久久777 | 青青青青久久久久国产的 | 99精品热视频30在线热视频 | 狠狠色狠狠色综合曰曰 | 欧美日韩中文国产一区 | 被黑人掹躁10次高潮 | 日本最新在线不卡免费视频 | 色综合久久综合网观看 | 人妻 中文无码 中出 | 亚洲精品久久久久AV无码 | 国产AV白丝爆浆在线播放 | 亚洲乱码中文字幕久久孕妇黑人 | 强上轮流内射高NP男男 | 国产精品女上位好爽在线短片 | 青草影院天堂男人久久 | 美女脱三角裤 | 精品视频在线一区 | 野花视频在线观看免费最新动漫 | 免费国产成人高清在线观看视频 | 国产免费阿v精品视频网址 国产免费69成人精品视频 | 日美欧韩一区二去三区 | 91精品免费久久久久久久久 | 男女久久久国产一区二区三区 | 亚洲第一色网站 | 日本欧美午夜三级 | 国产精品伦理一二三区伦理 | 亚洲蜜桃AV色情精品成人 | 久久午夜夜伦痒痒想咳嗽P 久久午夜夜伦鲁鲁片无码免费 | 野花韩国高清完整版在线 | 国产精品爽爽久久久久久蜜桃网站 | 亚洲成年人影院 | 麻豆国产原创中文AV网站 | 甜性涩爱bt下载 | 香蕉人人超人人超碰超国产 |