自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

在分布式系統(tǒng)中,SpringBoot 實(shí)現(xiàn)接口冪等性

開(kāi)發(fā) 架構(gòu)
下面我將介紹如何在 SpringBoot + MySQL + MybatisPlus + Druid 的環(huán)境下實(shí)現(xiàn)接口冪等性。

在分布式系統(tǒng)中,接口冪等性是一個(gè)非常重要的概念,它保證了在同樣的條件下,同一請(qǐng)求的多次執(zhí)行所產(chǎn)生的效果都是相同的。在實(shí)際開(kāi)發(fā)中,為了防止重復(fù)提交或者重復(fù)操作帶來(lái)的問(wèn)題,我們需要考慮如何實(shí)現(xiàn)接口冪等性。

下面我將介紹如何在 SpringBoot + MySQL + MybatisPlus + Druid 的環(huán)境下實(shí)現(xiàn)接口冪等性。

  1. 什么是接口冪等性?

接口冪等性是指,對(duì)于相同的輸入,接口的輸出結(jié)果應(yīng)該相同。換句話說(shuō),如果接口已經(jīng)處理了一個(gè)請(qǐng)求并返回了結(jié)果,那么在相同的輸入條件下,該接口的后續(xù)請(qǐng)求應(yīng)該返回相同的結(jié)果,而不會(huì)產(chǎn)生任何新的副作用。

  1. 如何實(shí)現(xiàn)接口冪等性?

要實(shí)現(xiàn)接口冪等性,需要考慮以下幾個(gè)方面:

  • 請(qǐng)求唯一標(biāo)識(shí):每個(gè)請(qǐng)求都應(yīng)該有一個(gè)唯一的標(biāo)識(shí),可以是請(qǐng)求參數(shù)的組合或者是一個(gè)單獨(dú)的參數(shù)。
  • 冪等性校驗(yàn):每次請(qǐng)求到達(dá)服務(wù)器時(shí),服務(wù)器需要判斷該請(qǐng)求是否已經(jīng)被處理過(guò),如果已經(jīng)被處理過(guò),則直接返回處理結(jié)果,否則執(zhí)行請(qǐng)求操作,并記錄請(qǐng)求的唯一標(biāo)識(shí),以便后續(xù)的冪等性校驗(yàn)。

在 SpringBoot + MySQL + MybatisPlus + Druid 的環(huán)境下,我們可以通過(guò)以下方式實(shí)現(xiàn)接口冪等性:

  • 在請(qǐng)求參數(shù)中添加一個(gè)冪等性校驗(yàn)碼(比如 UUID),用于唯一標(biāo)識(shí)每個(gè)請(qǐng)求。
  • 在請(qǐng)求處理前,先查詢冪等性校驗(yàn)碼是否已經(jīng)存在于數(shù)據(jù)庫(kù)中,如果存在則說(shuō)明該請(qǐng)求已經(jīng)被處理過(guò),直接返回結(jié)果。
  • 如果冪等性校驗(yàn)碼不存在于數(shù)據(jù)庫(kù)中,則執(zhí)行請(qǐng)求操作,并將冪等性校驗(yàn)碼插入到數(shù)據(jù)庫(kù)中。

下面是實(shí)現(xiàn)接口冪等性的示例代碼:

在請(qǐng)求參數(shù)中添加一個(gè)冪等性校驗(yàn)碼:

public class RequestDTO {
private String idempotenceKey;
// other request fields and methods
}

在 MybatisPlus 中創(chuàng)建對(duì)應(yīng)的實(shí)體類:

@Data
@TableName("idempotence_key")
public class IdempotenceKey {
@TableId(type = IdType.ASSIGN_UUID)
private String id;
private String key;
private Date createTime;
}

在 Controller 中實(shí)現(xiàn)冪等性校驗(yàn):

@RestController
public class UserController {
@Autowired
private UserService userService;

@PostMapping("/user")
public String createUser(@RequestBody RequestDTO request) {
// 冪等性校驗(yàn)
if (checkIdempotence(request.getIdempotenceKey())) {
return "success";
}
// 執(zhí)行請(qǐng)求操作
userService.createUser(request);
// 插入冪等性校驗(yàn)碼
saveIdempotence(request.getIdempotenceKey());
return "success";
}
}

在 Service 中實(shí)現(xiàn)冪等性校驗(yàn)和插入冪等性校驗(yàn)碼:

@Service
public class UserService {
@Autowired
private IdempotenceKeyMapper idempotenceKeyMapper;

public void createUser(RequestDTO request) {
// 創(chuàng)建用戶
// ...
}

private boolean checkIdempotence(String key) {
IdempotenceKey idempotenceKey = idempotenceKeyMapper.selectOne(new LambdaQueryWrapper<IdempotenceKey>().eq(IdempotenceKey::getKey, key));
return idempotenceKey != null;
}

private void saveIdempotence(String key) {
IdempotenceKey idempotenceKey = new IdempotenceKey();
idempotenceKey.setKey(key);
idempotenceKey.setCreateTime(new Date());
idempotenceKeyMapper.insert(idempotenceKey);
}
}

這里使用了 MybatisPlus 的 LambdaQueryWrapper 進(jìn)行查詢,并使用自動(dòng)生成的 UUID 作為冪等性校驗(yàn)碼。

全局實(shí)現(xiàn)冪等性校驗(yàn)可以使用AOP(面向切面編程)來(lái)實(shí)現(xiàn),在方法執(zhí)行前先進(jìn)行冪等性校驗(yàn),如果已經(jīng)執(zhí)行過(guò)該方法,則直接返回結(jié)果。可以通過(guò)自定義注解來(lái)標(biāo)記需要進(jìn)行冪等性校驗(yàn)的方法。

以下是一個(gè)簡(jiǎn)單的示例代碼:

  1. 自定義注解 Idempotent:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
long expireSeconds() default 60;
}
  1. 編寫 AOP 切面,用于攔截帶有 @Idempotent 注解的方法:
@Aspect
@Component
public class IdempotentAspect {
@Autowired
private IdempotenceKeyMapper idempotenceKeyMapper;

@Pointcut("@annotation(com.example.demo.annotation.Idempotent)")
public void idempotentPointcut() {}

@Around("idempotentPointcut()")
public Object idempotentAround(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
Idempotent idempotent = method.getAnnotation(Idempotent.class);
String key = getKey(point);

if (StringUtils.isBlank(key)) {
throw new RuntimeException("冪等性校驗(yàn)碼不能為空");
}

if (checkIdempotence(key)) {
throw new RuntimeException("請(qǐng)勿重復(fù)操作");
}

saveIdempotence(key, idempotent.expireSeconds());
return point.proceed();
}

private boolean checkIdempotence(String key) {
IdempotenceKey idempotenceKey = idempotenceKeyMapper.selectOne(new LambdaQueryWrapper<IdempotenceKey>().eq(IdempotenceKey::getKey, key));
return idempotenceKey != null;
}

private void saveIdempotence(String key, long expireSeconds) {
IdempotenceKey idempotenceKey = new IdempotenceKey();
idempotenceKey.setKey(key);
idempotenceKey.setCreateTime(new Date());
idempotenceKey.setExpireTime(new Date(System.currentTimeMillis() + expireSeconds * 1000));
idempotenceKeyMapper.insert(idempotenceKey);
}

private String getKey(ProceedingJoinPoint point) {
Object[] args = point.getArgs();
if (args.length == 0) {
return null;
}
return args[0].toString();
}
}
  1. 在需要進(jìn)行冪等性校驗(yàn)的方法上添加 @Idempotent 注解:
@Service
public class UserService {
@Autowired
private IdempotenceKeyMapper idempotenceKeyMapper;

@Idempotent(expireSeconds = 60)
public void createUser(String username) {
// 創(chuàng)建用戶
// ...
}
}

通過(guò)以上方式,在方法執(zhí)行前會(huì)先進(jìn)行冪等性校驗(yàn),如果已經(jīng)執(zhí)行過(guò)該方法,則直接返回結(jié)果,不會(huì)再次執(zhí)行。

在實(shí)際應(yīng)用中,需要考慮一些特殊情況的處理,以提高冪等性校驗(yàn)的準(zhǔn)確性和可靠性。下面列舉一些可能遇到的情況:

  1. 請(qǐng)求超時(shí)處理:由于冪等性校驗(yàn)碼是有過(guò)期時(shí)間的,如果客戶端發(fā)起的請(qǐng)求在冪等性校驗(yàn)碼過(guò)期后才到達(dá)服務(wù)器,那么該請(qǐng)求就不應(yīng)該再被視為重復(fù)請(qǐng)求。為了解決這個(gè)問(wèn)題,可以在冪等性校驗(yàn)碼表中記錄請(qǐng)求的時(shí)間戳,并在校驗(yàn)冪等性校驗(yàn)碼時(shí)進(jìn)行時(shí)間戳比較,以判斷請(qǐng)求是否超時(shí)。

在冪等性校驗(yàn)碼表中添加一個(gè)請(qǐng)求時(shí)間戳的字段,將請(qǐng)求時(shí)間戳一并存儲(chǔ),以便在校驗(yàn)冪等性校驗(yàn)碼時(shí)進(jìn)行時(shí)間戳比較。

CREATE TABLE `idempotent_key` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',
`key` varchar(128) NOT NULL COMMENT '冪等性校驗(yàn)碼',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時(shí)間',
`expire_time` datetime NOT NULL COMMENT '過(guò)期時(shí)間',
`request_time` datetime NOT NULL COMMENT '請(qǐng)求時(shí)間',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_key` (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='冪等性校驗(yàn)碼表';

在進(jìn)行冪等性校驗(yàn)時(shí),需要先判斷冪等性校驗(yàn)碼是否過(guò)期,如果過(guò)期則不再進(jìn)行校驗(yàn)。

public void processRequest() {
String key = generateIdempotentKey();
LocalDateTime now = LocalDateTime.now();
LocalDateTime expireTime = now.plusMinutes(5);
LocalDateTime requestTime = now;

// 將冪等性校驗(yàn)碼和請(qǐng)求時(shí)間戳存入數(shù)據(jù)庫(kù)中
idempotentKeyDao.insert(key, expireTime, requestTime);

// 判斷請(qǐng)求是否過(guò)期
LocalDateTime threshold = now.minusMinutes(5);
if (requestTime.isBefore(threshold)) {
// 請(qǐng)求已經(jīng)過(guò)期,不再進(jìn)行冪等性校驗(yàn)
return;
}

// 進(jìn)行冪等性校驗(yàn)
boolean success = idempotentKeyDao.checkAndUpdate(key);
if (!success) {
// 冪等性校驗(yàn)失敗
return;
}

// 執(zhí)行業(yè)務(wù)操作
// ...
}
  1. 高并發(fā)下的冪等性校驗(yàn):在高并發(fā)場(chǎng)景下,多個(gè)請(qǐng)求可能同時(shí)到達(dá)服務(wù)器進(jìn)行冪等性校驗(yàn),這時(shí)需要保證校驗(yàn)的準(zhǔn)確性和唯一性??梢酝ㄟ^(guò)對(duì)冪等性校驗(yàn)碼進(jìn)行唯一索引的方式來(lái)保證每個(gè)冪等性校驗(yàn)碼只會(huì)出現(xiàn)一次,避免多個(gè)請(qǐng)求同時(shí)通過(guò)校驗(yàn)。

在冪等性校驗(yàn)碼表的 key 字段上添加唯一索引,以保證每個(gè)冪等性校驗(yàn)碼只會(huì)出現(xiàn)一次。

ALTER TABLE `idempotent_key` ADD UNIQUE INDEX `uk_key` (`key`);

在進(jìn)行冪等性校驗(yàn)時(shí),需要使用數(shù)據(jù)庫(kù)的唯一索引進(jìn)行校驗(yàn)。

public boolean checkAndUpdate(String key) {
// 利用數(shù)據(jù)庫(kù)的唯一索引保證冪等性校驗(yàn)碼的唯一性
int affectedRows = jdbcTemplate.update(
"UPDATE idempotent_key SET request_count = request_count + 1 WHERE key = ?",
key);
return affectedRows == 1;
}
  1. 冪等性校驗(yàn)碼的重復(fù)利用:在一些場(chǎng)景下,比如一個(gè)請(qǐng)求執(zhí)行失敗需要重試,或者用戶進(jìn)行了一些撤銷操作后需要再次執(zhí)行該操作等,冪等性校驗(yàn)碼可能會(huì)被多次使用。為了避免重復(fù)利用同一個(gè)冪等性校驗(yàn)碼導(dǎo)致的校驗(yàn)失效,可以對(duì)冪等性校驗(yàn)碼進(jìn)行標(biāo)記,標(biāo)記該校驗(yàn)碼已被使用過(guò),避免再次使用。

在冪等性校驗(yàn)碼表中添加一個(gè) used 字段,標(biāo)記該冪等性校驗(yàn)碼是否已被使用過(guò)。

在進(jìn)行冪等性校驗(yàn)時(shí),需要判斷該冪等性校驗(yàn)碼是否已經(jīng)被使用過(guò),如果已經(jīng)被使用過(guò),則不再進(jìn)行校驗(yàn)。

public boolean checkAndUpdate(String key) {
// 判斷冪等性校驗(yàn)碼是否已經(jīng)被使用過(guò)
boolean used = jdbcTemplate.queryForObject(
"SELECT used FROM idempotent_key WHERE key = ?",
Boolean.class,
key);
if (used) {
// 冪等性校驗(yàn)碼已經(jīng)被使用過(guò),不再進(jìn)行校驗(yàn)
return true;
}

// 將冪等性校驗(yàn)碼標(biāo)記為已使用
int affectedRows = jdbcTemplate.update(
"UPDATE idempotent_key SET used = true WHERE key = ?",
key);
return affectedRows == 1;
}
  1. 冪等性校驗(yàn)碼的生成規(guī)則:冪等性校驗(yàn)碼的生成規(guī)則也需要考慮,應(yīng)該根據(jù)業(yè)務(wù)的特點(diǎn)來(lái)確定。可以采用隨機(jī)數(shù)、UUID、請(qǐng)求參數(shù)哈希等方式生成冪等性校驗(yàn)碼。需要保證冪等性校驗(yàn)碼在相同的請(qǐng)求條件下生成的結(jié)果一致。

在分布式環(huán)境下,需要保證不同實(shí)例之間共享冪等性校驗(yàn)碼的狀態(tài)??梢允褂?Redis 等分布式緩存來(lái)存儲(chǔ)冪等性校驗(yàn)碼狀態(tài)。

public boolean checkAndUpdate(String key) {
// 從 Redis 中獲取冪等性校驗(yàn)碼的狀態(tài)
boolean used = redisTemplate.opsForValue().get(key);
if (used) {
// 冪等性校驗(yàn)碼已經(jīng)被使用過(guò),不再進(jìn)行校驗(yàn)
return true;
}

// 將冪等性校驗(yàn)碼標(biāo)記為已使用
redisTemplate.opsForValue().set(key, true);

// 執(zhí)行業(yè)務(wù)操作
// ...

return true;
}

需要注意的是,由于 Redis 中存儲(chǔ)的數(shù)據(jù)可能會(huì)被意外刪除或過(guò)期,因此在使用 Redis 作為冪等性校驗(yàn)碼狀態(tài)存儲(chǔ)介質(zhì)時(shí),需要考慮數(shù)據(jù)丟失或過(guò)期的情況,確保系統(tǒng)的可靠性和正確性。

責(zé)任編輯:姜華 來(lái)源: 今日頭條
相關(guān)推薦

2021-01-13 11:23:59

分布式冪等性支付

2024-07-03 11:59:40

2025-02-14 14:22:40

2023-10-26 07:32:42

2023-01-13 07:39:07

2024-03-13 15:18:00

接口冪等性高并發(fā)

2021-12-01 10:13:48

場(chǎng)景分布式并發(fā)

2021-01-18 14:34:59

冪等性接口客戶端

2013-08-09 09:27:31

2025-02-23 08:00:00

冪等性Java開(kāi)發(fā)

2022-01-12 09:01:24

分布式系統(tǒng)容錯(cuò)服務(wù)

2024-01-31 09:54:51

Redis分布式

2024-06-03 14:17:00

2024-08-29 09:01:39

2024-06-24 01:00:00

2021-07-28 08:39:25

分布式架構(gòu)系統(tǒng)

2023-05-29 14:07:00

Zuul網(wǎng)關(guān)系統(tǒng)

2023-11-01 08:54:22

冪等性Python

2023-05-12 08:23:03

分布式系統(tǒng)網(wǎng)絡(luò)

2024-11-01 09:28:02

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)