日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
搞了個(gè)線上故障,被老板罵了....

大家好,我是Tom哥。

前幾天跟一位小伙伴聊天,心情特別沮喪,剛被老板罵完.....

差點(diǎn)丟了飯碗,還好老板沒(méi)說(shuō) “滾”。

就今年這就業(yè)行情,滿眼都是淚哇。

小伙伴在一家初創(chuàng)公司,團(tuán)隊(duì)規(guī)模很小,老板為了節(jié)省成本,也沒(méi)配置什么豪華陣容。

他的工作時(shí)間也不長(zhǎng),負(fù)責(zé)交易訂單,前幾天接到用戶投訴,「我的訂單列表」有多條一模一樣的訂單。

雖沒(méi)造成什么資損,但嚴(yán)重影響用戶體驗(yàn)。

看到這里,有經(jīng)驗(yàn)的同學(xué)可能猜到,應(yīng)該是接口沒(méi)做防重控制。

日常開發(fā)中,重復(fù)提交也是蠻常見問(wèn)題。

比如:用戶提交一個(gè)表單,鼠標(biāo)點(diǎn)的太快,正好前端又是個(gè)新兵蛋子,沒(méi)做任何控制,瞬間就會(huì)有多個(gè)請(qǐng)求發(fā)到后端系統(tǒng)。

如果后端同學(xué)也沒(méi)做兜底方案的話,悲劇就發(fā)生了。

常見的解決方案是借助數(shù)據(jù)庫(kù)自身的「唯一索引約束」,來(lái)保證數(shù)據(jù)的準(zhǔn)確性,這種方案一般在插入場(chǎng)景用的多些。

變種方案可以考慮單獨(dú)創(chuàng)建一個(gè)防重表。

本文的案例有點(diǎn)特殊,訂單號(hào)是后端系統(tǒng)生成的,前后兩次請(qǐng)求無(wú)法區(qū)分重復(fù)狀態(tài),所以系統(tǒng)會(huì)創(chuàng)建兩條不同訂單 ID 記錄,繞過(guò)了「唯一索引約束」這個(gè)限制,這.....

另外,MySQL 性能也單薄了點(diǎn),單機(jī) QPS 在「千」維度,如果是面對(duì)一個(gè)高并發(fā)接口,性能也有點(diǎn)吃緊。

接下來(lái),我們就來(lái)講下,借助 Redis 來(lái)實(shí)現(xiàn)接口防重復(fù)提交。

技術(shù)方案

首先,我們來(lái)看下整理的流程,如下圖所示:

大致步驟:

1、客戶端發(fā)送請(qǐng)求到服務(wù)端。

2、服務(wù)端接收請(qǐng)求,然后從請(qǐng)求參數(shù)中提取唯一標(biāo)識(shí)。這個(gè)標(biāo)識(shí)可以沒(méi)有什么特殊業(yè)務(wù)含義,client 端隨機(jī)生成即可。

3、服務(wù)端系統(tǒng)將唯一標(biāo)識(shí)先嘗試寫入 Redis 緩存中,可以認(rèn)為是加鎖操作。

4、加鎖失敗,說(shuō)明請(qǐng)求還在處理,此次是重復(fù)請(qǐng)求,可以丟棄。

5、加鎖成功,繼續(xù)后面正常業(yè)務(wù)邏輯處理。

6、業(yè)務(wù)邏輯處理完成后,刪除加鎖的標(biāo)記。

7、最后,將處理成功的結(jié)果返回給客戶端。

注意事項(xiàng):

  • 重復(fù)提交場(chǎng)景一般都是在極短時(shí)間內(nèi),同時(shí)發(fā)送了多次請(qǐng)求(比如:頁(yè)面表單重復(fù)提交),我們只認(rèn)第一次請(qǐng)求為有效請(qǐng)求。
  • 鎖用完后,要記得手動(dòng)刪除。為了防止鎖沒(méi)有正常釋放,我們可以為鎖設(shè)置一個(gè)極短的過(guò)期時(shí)間(比如 10 秒)。

項(xiàng)目實(shí)戰(zhàn)

1、引入 redis 組件

實(shí)戰(zhàn)的項(xiàng)目采用 Spring Boot 搭建,這里需要引入 Redis 相關(guān)依賴。


org.springframework.boot
spring-boot-starter-data-redis


redis.clients
jedis

2、redis 變量配置

application.properties 配置文件中,添加redis相關(guān)服務(wù)配置。

spring.redis.host=127.0.0.1
spring.redis.port=6379

3、定義注解類

定義一個(gè)注解,配置在需要防重復(fù)的接口方法上,提高開發(fā)效率,同時(shí)降低代碼的耦合度。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented

public @interface IdempotentRule {

/**
* 業(yè)務(wù)自定義前綴
*/
String prefix() default "";

/**
* 業(yè)務(wù)重復(fù)標(biāo)識(shí)
*/
String key() default "";
}

4、接口攔截器

上面定義了IdempotentRule?注解,需要通過(guò)攔截器對(duì)正常的業(yè)務(wù)方法做攔截,增加一些特殊邏輯處理。

@Aspect
@Component
@Slf4j
public class IdempotentAspect {

@Autowired
private RedisTemplate idempotentRedisTemplate;

@Around("execution(public * *(..)) && @annotation(com.onyone.idempotent.annotation.IdempotentRule)")
public Object limit(ProceedingJoinPoint pjp) {
MethodSignature signature = (MethodSignature) pjp.getSignature();


Object[] params = pjp.getArgs();
String[] paramNames = signature.getParameterNames();

Method method = signature.getMethod();
IdempotentRule idempotentRule = method.getAnnotation(IdempotentRule.class);
String key = idempotentRule.key();
String prefix = idempotentRule.prefix();

ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext();
context.setVariable(paramNames[0], params[0]);
String repeatKey = (String) parser.parseExpression(key).getValue(context);

try {
// 先在緩存中做個(gè)標(biāo)記
Boolean lockResult = idempotentRedisTemplate.opsForValue().setIfAbsent(prefix + repeatKey, "正在處理....", 20, TimeUnit.SECONDS);
if (lockResult) {
// 業(yè)務(wù)邏輯處理
return pjp.proceed();
} else {
throw new Exception("重復(fù)提交..................");
}
} catch (Throwable e) {
e.printStackTrace();
} finally {
// 處理完成后,將標(biāo)記刪除
idempotentRedisTemplate.delete(prefix + repeatKey);
}

return null;
}


}

這里,比較特殊的是提取請(qǐng)求的唯一標(biāo)識(shí),由于不同的業(yè)務(wù)請(qǐng)求唯一標(biāo)識(shí)不一樣。

所以,這里采用 SPEL 表達(dá)式,將規(guī)則設(shè)置能力開放出去,由業(yè)務(wù)方自己定義,比如:

@IdempotentRule(key = "#userParam.cardNumber", prefix = "repeat_")。

攔截器根據(jù) SPEL 表達(dá)式( 如 "#userParam.cardNumber")以及請(qǐng)求參數(shù)對(duì)象,計(jì)算當(dāng)前請(qǐng)求唯一標(biāo)識(shí)的值,

然后將值寫入 Redis 中,并設(shè)置過(guò)時(shí)間。

如果設(shè)置成功,說(shuō)明是第一次請(qǐng)求,繼續(xù)下面的業(yè)務(wù)邏輯處理;否則,判定為重復(fù)請(qǐng)求,直接丟棄。

5、上層業(yè)務(wù)接口

@RestController
@RequestMapping("/user")
public class UserController {
/**
* 創(chuàng)建一個(gè)新的用戶
*/
@RequestMapping(value = "/create_user")
@IdempotentRule(key = "#userParam.cardNumber", prefix = "repeat_")
public String createUser(@RequestBody UserParam userParam) {
// 模擬業(yè)務(wù)處理

return "創(chuàng)建用戶成功!";
}
}
@Data
public class UserParam {
private String cardNumber;
private String name;
}

測(cè)試結(jié)果

1、構(gòu)造客戶端請(qǐng)求,第一次處理成功。

2、 Redis 緩存中,能查到請(qǐng)求設(shè)置的鎖標(biāo)記。

3、模擬重復(fù),連續(xù)多次快速提交請(qǐng)求,請(qǐng)求會(huì)被攔截,并拋出異常。


文章名稱:搞了個(gè)線上故障,被老板罵了....
文章來(lái)源:http://www.5511xx.com/article/dphhhoi.html