前言
作為后端研發(fā)人員,平時(shí)需要經(jīng)常做服務(wù)接口設(shè)計(jì)及開發(fā),需要與前端進(jìn)行接口聯(lián)調(diào),排查生產(chǎn)環(huán)境線上問題。因此,后端工程師,核心基本工作就是如何把一個(gè)接口設(shè)計(jì)好,以下梳理一些接口設(shè)計(jì)開發(fā)規(guī)范及注意事項(xiàng),希望對(duì)大家有所幫助。

1、接口參數(shù)校驗(yàn)(入?yún)⒑统鰠ⅲ?/h4>
接口入?yún)⒑统鰠⒍夹枰M(jìn)行校驗(yàn),
① 例如入?yún)⑹欠癫荒転榭眨雲(yún)?shù)據(jù)長(zhǎng)度,入?yún)⑹欠穹项A(yù)期規(guī)則,很多bug由于未做參數(shù)校驗(yàn)導(dǎo)致,對(duì)于可能改變的參數(shù)建議設(shè)計(jì)為對(duì)象類型;
② 對(duì)于返回值,當(dāng)返回值為空時(shí)是否返回為空串、空對(duì)象、空數(shù)組,需要與前端約定好。
2、接口老版本兼容性
C端服務(wù)接口,可能移動(dòng)端發(fā)版不會(huì)強(qiáng)升級(jí)或者存在前后端上線時(shí)間差異,就會(huì)導(dǎo)致線上環(huán)境存在使用老版本的用戶,如果新添加了參數(shù),需要考慮前端未傳入時(shí)給默認(rèn)值情況;例如
//老接口
void oldMethod(A,B){
//兼容新接口,傳個(gè)null或其他默認(rèn)值代替參數(shù)C
newService(A,B,null);
}
//新接口,暫時(shí)不能刪掉老接口,需要做兼容。
void newMethod(A,B,C){
...
}
3、接口擴(kuò)展性考慮
① 例如業(yè)務(wù)中,在用戶調(diào)用撥打電話接口之后會(huì)進(jìn)行消息推送,是直接就開發(fā)一個(gè)消息推送功能,還是將消息推送梳理為一個(gè)通用流程,在所有需要使用的地方進(jìn)行調(diào)用即可,保留擴(kuò)展性;
② 消息推送流程,設(shè)計(jì)為通用流程,同時(shí)采用接口定義,可以擴(kuò)展實(shí)現(xiàn)多種消息推送方式。
graph LR
傳入?yún)?shù) --> 構(gòu)建目標(biāo)用戶群 --> 調(diào)用消息中心接口發(fā)送 --> 記錄發(fā)送結(jié)果
4、接口防重處理
① 對(duì)于查詢類型、刪除類型接口,不論調(diào)用多少次,都是不會(huì)產(chǎn)生錯(cuò)誤的業(yè)務(wù)數(shù)據(jù),因此不用做防重處理;
② 對(duì)于新增和修改,例如轉(zhuǎn)賬或者提現(xiàn)類接口,重復(fù)提交就會(huì)多次轉(zhuǎn)賬和提現(xiàn),影響業(yè)務(wù)需要做防重處理,讓前端傳入請(qǐng)求序列號(hào),可以采用redis、LRUMap、數(shù)據(jù)庫(kù)防重表、分布式鎖等處理。
graph LR
id獲取全局請(qǐng)求token --> 寫入redis緩存 --> 請(qǐng)求時(shí)帶上token --> 后端刪除 --> 再次請(qǐng)求提示重復(fù)
5、核心接口,線程池隔離
登錄接口、首頁(yè)數(shù)據(jù)接口、轉(zhuǎn)賬提現(xiàn)接口等,都可能使用到線程池,某些普通接口也會(huì)使用線程池,如果不做線程池隔離,普通接口出bug線程池打滿,會(huì)導(dǎo)致登錄等主要業(yè)務(wù)受到影響。
graph TB
線程池隔離
subgraph 核心接口
登錄接口 --- 首頁(yè)加載
end
subgraph 普通接口
寫入日志 --- 消息推送
end
6、關(guān)鍵接口,日志打印
關(guān)鍵業(yè)務(wù)代碼,需要打印日志進(jìn)行保駕護(hù)航,在入?yún)⒑统鰠⑽恢没蛘咂渌P(guān)鍵位置,良好的日志打印具有如下好處:
① 方便排查定位線上問題,劃清問題責(zé)任;
② 生產(chǎn)環(huán)境不能直接debug,必須依靠日志查問題和具體異常。
7、三方接口異常、重試、超時(shí)
如果調(diào)用第三方接口,或者分布式遠(yuǎn)程服務(wù)的的話,需要考慮:
① 異常處理
比如,你調(diào)別人的接口,如果異常了,怎么處理,是重試還是當(dāng)做失敗還是告警處理。
② 接口超時(shí)
沒法預(yù)估對(duì)方接口一般多久返回,一般設(shè)置個(gè)超時(shí)斷開時(shí)間,以保護(hù)你的接口。之前見過一個(gè)生產(chǎn)問題,就是http調(diào)用不設(shè)置超時(shí)時(shí)間,最后響應(yīng)方進(jìn)程假死,請(qǐng)求一直占著線程不釋放,拖垮線程池。
③ 重試次數(shù)
你的接口調(diào)失敗,需不需要重試?重試幾次?需要站在業(yè)務(wù)上角度思考這個(gè)問題
8、接口功能單一性原則
單一性是指接口做的事情比較單一、專一。比如一個(gè)登陸接口,它做的事情就只是校驗(yàn)賬戶名密碼,然后返回登陸成功以及userId即可。但是如果你為了減少接口交互,把一些注冊(cè)、一些配置查詢等全放到登陸接口,就不太妥。
其實(shí)這也是微服務(wù)一些思想,接口的功能單一、明確。比如訂單服務(wù)、積分、商品信息相關(guān)的接口都是劃分開的。將來拆分微服務(wù)的話,是不是就比較簡(jiǎn)便啦。
9、接口部分場(chǎng)景采用異步處理
舉個(gè)簡(jiǎn)單的例子,比如你實(shí)現(xiàn)一個(gè)用戶注冊(cè)的接口。用戶注冊(cè)成功時(shí),發(fā)個(gè)郵件或者短信去通知用戶。這個(gè)郵件或者發(fā)短信,就更適合異步處理。因?yàn)榭偛荒芤粋€(gè)通知類的失敗,導(dǎo)致注冊(cè)失敗吧。
至于做異步的方式,簡(jiǎn)單的就是用線程池。還可以使用消息隊(duì)列,就是用戶注冊(cè)成功后,生產(chǎn)者產(chǎn)生一個(gè)注冊(cè)成功的消息,消費(fèi)者拉到注冊(cè)成功的消息,就發(fā)送通知。
graph LR
id2生產(chǎn)者生產(chǎn)消息 -- 注冊(cè)成功消息 --> 存儲(chǔ)端 --發(fā)送通知-->消費(fèi)者消費(fèi)
10、接口查詢優(yōu)化,串行改為并行
假設(shè)我們?cè)O(shè)計(jì)一個(gè)APP首頁(yè)的接口,它需要查用戶信息、需要查banner信息、需要查彈窗信息等等。那你是一個(gè)一個(gè)接口串行調(diào),還是并行調(diào)用呢?
可以使用CompletableFuture 并行調(diào)用提高性能。
// 查詢獲獎(jiǎng)經(jīng)歷
LambdaQueryWrapper<RewardExp> rewardExpQuery = new LambdaQueryWrapper<RewardExp>()
.eq(RewardExp::getResumeId, resume.getId())
.eq(RewardExp::getDelFlag, NORMAL)
.orderByDesc(RewardExp::getDate);
CompletableFuture<List<RewardExp>> rewardExpFuture = CompletableFuture.supplyAsync(() ->
rewardExpMapper.selectList(rewardExpQuery)
);
// 查詢資格證書
LambdaQueryWrapper<Credential> credentialQuery = new LambdaQueryWrapper<Credential>()
.eq(Credential::getResumeId, resume.getId())
.eq(Credential::getDelFlag, NORMAL)
.orderByDesc(Credential::getDate);
CompletableFuture<List<Credential>> credentialFuture = CompletableFuture.supplyAsync(() ->
credentialMapper.selectList(credentialQuery)
);
// 查詢工種
LambdaQueryWrapper<ResumeJobs> jobsQuery = new LambdaQueryWrapper<ResumeJobs>()
.eq(ResumeJobs::getResumeId, resume.getId()).eq(ResumeJobs::getDelFlag, NORMAL);
CompletableFuture<List<ResumeJobs>> jobsFuture = CompletableFuture.supplyAsync(() ->
resumeJobsMapper.selectList(jobsQuery)
);
CompletableFuture.allOf(rewardExpFuture, credentialFuture, jobsFuture).join();
11、接口合并與批處理
數(shù)據(jù)庫(kù)操作或或者是遠(yuǎn)程調(diào)用時(shí),能批量操作就不要for循環(huán)調(diào)用。一個(gè)簡(jiǎn)單例子,我們平時(shí)一個(gè)列表明細(xì)數(shù)據(jù)插入數(shù)據(jù)庫(kù)時(shí),不要在for循環(huán)一條一條插入,建議一個(gè)批次幾百條,進(jìn)行批量插入。同理遠(yuǎn)程調(diào)用也類似想法,比如你查詢營(yíng)銷標(biāo)簽是否命中,可以一個(gè)標(biāo)簽一個(gè)標(biāo)簽去查,也可以批量標(biāo)簽去查,那批量進(jìn)行,效率就更高。
//反例
for(int i=0;i<n;i++){
remoteSingleQuery(param)
}
//正例
remoteBatchQuery(param);
12、接口性能、Sql優(yōu)化
我們做后端的,寫好一個(gè)接口,離不開SQL優(yōu)化。
SQL優(yōu)化從這幾個(gè)維度思考:
① explain 分析SQL查詢計(jì)劃(重點(diǎn)關(guān)注type、extra、filtered字段)
② 索引優(yōu)化 (覆蓋索引、最左前綴原則、隱式轉(zhuǎn)換、order by以及group by的優(yōu)化、join優(yōu)化)
③ 大分頁(yè)問題優(yōu)化(延遲關(guān)聯(lián)、記錄上一頁(yè)最大ID)
④ 數(shù)據(jù)量太大(分庫(kù)分表、同步到es,用es查詢)