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

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

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

為什么有公司規(guī)定所有接口都必須用Post?

jf_ro2CN3Fa ? 來源:撿田螺的小男孩 ? 2023-01-03 18:31 ? 次閱讀

1. 接口參數(shù)校驗

2. 修改老接口時,注意接口的兼容性

3. 設(shè)計接口時,充分考慮接口的可擴展性

4. 接口考慮是否需要防重處理

5. 重點接口,考慮線程池隔離

6. 調(diào)用第三方接口要考慮異常和超時處理

7. 接口實現(xiàn)考慮熔斷和降級

8. 日志打印好,接口的關(guān)鍵代碼,要有日志保駕護(hù)航

9. 接口的功能定義要具備單一性

10. 接口有些場景,使用異步更合理

11. 優(yōu)化接口耗時,遠(yuǎn)程串行考慮改并行調(diào)用

12. 接口合并或者說考慮批量處理思想

13. 接口實現(xiàn)過程中,恰當(dāng)使用緩存

14. 接口考慮熱點數(shù)據(jù)隔離性

15. 可變參數(shù)配置化,比如紅包皮膚切換等

16. 接口考慮冪等性

17. 讀寫分離,優(yōu)先考慮讀從庫,注意主從延遲問題

18. 接口注意返回的數(shù)據(jù)量,如果數(shù)據(jù)量大需要分頁

19. 好的接口實現(xiàn),離不開 SQL 優(yōu)化

20. 代碼鎖的粒度控制好

21. 接口狀態(tài)和錯誤需要統(tǒng)一明確

22. 接口要考慮異常處理

23. 優(yōu)化程序邏輯

24. 接口實現(xiàn)過程中,注意大文件、大事務(wù)、大對象

25. 你的接口,需要考慮限流

26. 代碼實現(xiàn)時,注意運行時異常(比如空指針、下標(biāo)越界等)

27. 保證接口安全性

28. 分布式事務(wù),如何保證

29. 事務(wù)失效的一些經(jīng)典場景

30. 掌握常用的設(shè)計模式

31. 寫代碼時,考慮線性安全問題

32. 接口定義清晰易懂,命名規(guī)范

33. 接口的版本控制

34. 注意代碼規(guī)范問題

35. 保證接口正確性,其實就是保證更少的 bug

36. 學(xué)會溝通,跟前端溝通,跟產(chǎn)品溝通

作為后端開發(fā),不管是什么語言,Java、Go還是C++,其背后的后端思想都是類似的。

后端開發(fā)工程師,主要工作就是:如何把一個接口設(shè)計好 。

今天就給大家介紹,設(shè)計好接口的 36 個錦囊。

1. 接口參數(shù)校驗

入?yún)⒊鰠⑿r炇敲總€程序員必備的基本素養(yǎng)。設(shè)計接口,必須先校驗參數(shù)。

比如入?yún)⑹欠裨试S為空,入?yún)㈤L度是否符合預(yù)期長度。這個要養(yǎng)成習(xí)慣,日常開發(fā)中,很多低級 bug 都是不校驗參數(shù)導(dǎo)致的。

比如你的數(shù)據(jù)庫表字段設(shè)置為varchar(16),對方傳了一個 32 位的字符串過來,如果你不校驗參數(shù),插入數(shù)據(jù)庫就直接異常了 。

出參也是,比如你定義的接口報文,參數(shù)是不為空的,但是你的接口返回參數(shù)沒有做校驗,因為程序某些原因,返回別人一個null值。

2. 修改老接口時,注意接口的兼容性

很多 bug 都是因為修改了對外老接口但是卻不做兼容 導(dǎo)致的。關(guān)鍵這個問題多數(shù)是比較嚴(yán)重的,可能直接導(dǎo)致系統(tǒng)發(fā)版失敗。新手程序員很容易犯這個錯誤。

所以,如果你的需求是在原來接口上做修改,尤其這個接口是對外提供服務(wù)的話,一定要考慮接口兼容。

舉個例子吧,比如 dubbo 接口,原本是只接收 A、B 參數(shù),現(xiàn)在加了一個參數(shù) C,就可以考慮這樣處理:

//老接口
voidoldService(A,B){
//兼容新接口,傳個null代替C
newService(A,B,null);
}

//新接口,暫時不能刪掉老接口,需要做兼容。
voidnewService(A,B,C){
...
}

3. 設(shè)計接口時,充分考慮接口的可擴展性

要根據(jù)實際業(yè)務(wù)場景設(shè)計接口,充分考慮接口的可擴展性。

比如你接到一個需求:用戶添加或者修改員工時,需要刷臉。那你是反手提供一個員工管理的提交刷臉信息接口呢?還是先思考:提交刷臉是不是通用流程呢?比如轉(zhuǎn)賬或者一鍵貼現(xiàn)需要接入刷臉的話,你是否需要重新實現(xiàn)一個接口呢?還是當(dāng)前按業(yè)務(wù)類型劃分模塊,復(fù)用這個接口就好,保留接口的可擴展性。

如果按模塊劃分的話,未來如果其他場景比如一鍵貼現(xiàn)接入刷臉的話,不用再搞一套新的接口,只需要新增枚舉,然后復(fù)用刷臉通過流程接口,實現(xiàn)一鍵貼現(xiàn)刷臉的差異化即可。

e94d3342-88a4-11ed-bfe3-dac502259ad0.png

4. 接口考慮是否需要防重處理

如果前端重復(fù)請求,你的邏輯如何處理?是不是考慮接口去重處理。

當(dāng)然,如果是查詢類的請求,其實不用防重。如果是更新修改類的話,尤其金融轉(zhuǎn)賬類的,就要過濾重復(fù)請求了。

簡單點,你可以使用 Redis 防重復(fù)請求,同樣的請求方,一定時間間隔內(nèi)的相同請求,考慮是否過濾。當(dāng)然,轉(zhuǎn)賬類接口,并發(fā)不高的話,推薦使用數(shù)據(jù)庫防重 表,以唯一流 水號作為主鍵或者唯一索引 。

e9591f22-88a4-11ed-bfe3-dac502259ad0.png

5. 重點接口,考慮線程池隔離

一些登錄、轉(zhuǎn)賬交易、下單等重要接口,考慮線程池隔離。

如果你所有業(yè)務(wù)都共用一個線程池,有些業(yè)務(wù)出 bug 導(dǎo)致線程池阻塞打滿的話,那就杯具了,所有業(yè)務(wù)都影響了

因此進(jìn)行線程池隔離,重要業(yè)務(wù)分配多一點的核心線程,就能更好保護(hù)重要業(yè)務(wù)。

e975dec8-88a4-11ed-bfe3-dac502259ad0.png

6. 調(diào)用第三方接口要考慮異常和超時處理

如果你調(diào)用第三方接口,或者分布式遠(yuǎn)程服務(wù)的話,需要考慮:

異常處理

比如,你調(diào)別人的接口,如果異常了,怎么處理,是重試還是當(dāng)做失敗還是告警處理。

接口超時

沒法預(yù)估對方接口多久返回,一般設(shè)置個超時斷開時間,以保護(hù)你的接口。之前見過一個生產(chǎn)問題 ,就是 http 調(diào)用不設(shè)置超時時間,最后響應(yīng)方進(jìn)程假死,請求一直占著線程不釋放,拖垮線程池。

重試次數(shù)

你的接口調(diào)失敗,需不需要重試?重試幾次?需要站在業(yè)務(wù)角度思考這個問題。

7. 接口實現(xiàn)考慮熔斷和降級

當(dāng)前互聯(lián)網(wǎng)系統(tǒng)一般都是分布式部署的。而分布式系統(tǒng)中經(jīng)常會出現(xiàn)某個基礎(chǔ)服務(wù)不可用,最終導(dǎo)致整個系統(tǒng)不可用的情況,這種現(xiàn)象被稱為服務(wù)雪崩效應(yīng) 。

比如分布式調(diào)用鏈路A->B->C....,下圖所示:

e9c0e49a-88a4-11ed-bfe3-dac502259ad0.png

如果服務(wù) C 出現(xiàn)問題,比如是因為慢 SQL 導(dǎo)致調(diào)用緩慢 ,那將導(dǎo)致 B 也會延遲,從而 A 也會延遲。堵住的 A 請求會消耗占用系統(tǒng)的線程、IO 等資源。當(dāng)請求 A 的服務(wù)越來越多,占用計算機的資源也越來越多,最終會導(dǎo)致系統(tǒng)瓶頸出現(xiàn),造成其他的請求同樣不可用,最后導(dǎo)致業(yè)務(wù)系統(tǒng)崩潰。

為了應(yīng)對服務(wù)雪崩,常見的做法是熔斷和降級 。最簡單是加開關(guān)控制,當(dāng)下游系統(tǒng)出問題時,開關(guān)降級,不再調(diào)用下游系統(tǒng)。還可以選用開源組件Hystrix。

8. 日志打印好,接口的關(guān)鍵代碼,要有日志保駕護(hù)航

關(guān)鍵業(yè)務(wù)代碼無論身處何地,都應(yīng)該有足夠的日志保駕護(hù)航。

比如:你實現(xiàn)轉(zhuǎn)賬業(yè)務(wù),轉(zhuǎn)個幾百萬,然后轉(zhuǎn)失敗了,接著客戶投訴,然后你還沒有打印到日志,想想那種水深火熱的困境下,你卻毫無辦法。。。

那么,你的轉(zhuǎn)賬業(yè)務(wù)都需要哪些日志信息呢?至少,方法調(diào)用前,入?yún)⑿枰蛴“?,接口調(diào)用后,需要捕獲一下異常吧,同時打印異常相關(guān)日志,如下:

publicvoidtransfer(TransferDTOtransferDTO){
log.info("invoketranferbegin");
//打印入?yún)?log.info("invoketranfer,paramters:{}",transferDTO);
try{
res=transferService.transfer(transferDTO);
}catch(Exceptione){
log.error("transferfail,account:{}",
transferDTO.getAccount())
log.error("transferfail,exception:{}",e);
}
log.info("invoketranferend");
}

9. 接口的功能定義要具備單一性

單一性是指接口做的事情比較單一、專一。比如一個登錄接口,它做的事情就只是校驗賬戶名密碼,然后返回登錄成功以及userId即可。但是如果為了減少接口交互,把一些注冊、一些配置查詢等全放到登錄接口,就不太妥。

其實這也是微服務(wù)一些思想,接口的功能單一、明確。比如訂單服務(wù)、積分、商品信息相關(guān)的接口都是劃分開的。將來拆分微服務(wù)的話,是不是就比較簡便啦。

10. 接口有些場景,使用異步更合理

舉個簡單的例子,比如實現(xiàn)一個用戶注冊的接口,用戶注冊成功時,發(fā)個郵件或者短信去通知用戶。這個郵件或者發(fā)短信,就更適合異步處理。因為總不能一個通知類的失敗,導(dǎo)致注冊失敗吧。

至于做異步的方式,簡單的就是用線程池 。還可以使用消息隊列,就是用戶注冊成功后,生產(chǎn)者產(chǎn)生一個注冊成功的消息,消費者拉到注冊成功的消息,就發(fā)送通知。

e9cd2b2e-88a4-11ed-bfe3-dac502259ad0.png

不是所有的接口都適合設(shè)計為同步接口。比如你要做一個轉(zhuǎn)賬的功能,如果是單筆的轉(zhuǎn)賬,你是可以把接口設(shè)計同步。用戶發(fā)起轉(zhuǎn)賬時,客戶端再靜靜等待轉(zhuǎn)賬結(jié)果就好。如果是批量轉(zhuǎn)賬,一個批次一千筆,甚至一萬筆的,你則可以把接口設(shè)計為異步。就是用戶發(fā)起批量轉(zhuǎn)賬時,持久化成功就先返回受理成功。然后用戶隔十分鐘或者十五分鐘再來查轉(zhuǎn)賬結(jié)果就好。又或者,批量轉(zhuǎn)賬成功后,再回調(diào)上游系統(tǒng)。

e9e0b3ba-88a4-11ed-bfe3-dac502259ad0.png

11. 優(yōu)化接口耗時,遠(yuǎn)程串行考慮改并行調(diào)用

假設(shè)我們設(shè)計一個 APP 首頁的接口,它需要查用戶信息、需要查 banner 信息、需要查彈窗信息等等。那是一個一個接口串行調(diào),還是并行調(diào)用呢?

e9e96d8e-88a4-11ed-bfe3-dac502259ad0.png

如果是串行一個一個查,比如查用戶信息 200ms,查 banner 信息 100ms、查彈窗信息 50ms,那一共就耗時350ms了,如果還查其他信息,那耗時就更大了。這種場景是可以改為并行調(diào)用的。也就是說查用戶信息、查 banner 信息、查彈窗信息,可以同時發(fā)起。

e9f956f4-88a4-11ed-bfe3-dac502259ad0.png

12. 接口合并或者說考慮批量處理思想

數(shù)據(jù)庫操作或者是遠(yuǎn)程調(diào)用時,能批量操作就不要 for 循環(huán)調(diào)用。

一個簡單例子,我們平時一個列表明細(xì)數(shù)據(jù)插入數(shù)據(jù)庫時,不要在 for 循環(huán)一條一條插入,建議一個批次幾百條,進(jìn)行批量插入。同理遠(yuǎn)程調(diào)用也類似想法,比如你查詢營銷標(biāo)簽是否命中,可以一個標(biāo)簽一個標(biāo)簽去查,也可以批量標(biāo)簽去查,那批量進(jìn)行,效率就更高嘛。

//反例
for(inti=0;i

小伙伴們是否了解過kafka為什么這么快呢?其實其中一點原因,就是 kafka 使用批量消息 提升服務(wù)端處理能力。

13. 接口實現(xiàn)過程中,恰當(dāng)使用緩存

哪些場景適合使用緩存?讀多寫少且數(shù)據(jù)時效要求越低的場景 。

緩存用得好,可以承載更多的請求,提升查詢效率,減少數(shù)據(jù)庫的壓力。

比如一些平時變動很小或者說幾乎不會變的商品信息,可以放到緩存,請求過來時,先查詢緩存,如果沒有再查數(shù)據(jù)庫,并且把數(shù)據(jù)庫的數(shù)據(jù)更新到緩存。但是,使用緩存增加了需要考慮這些點:緩存和數(shù)據(jù)庫一致性如何保證、集群、緩存擊穿、緩存雪崩、緩存穿透等問題。

保證數(shù)據(jù)庫和緩存一致性:緩存延時雙刪、刪除緩存重試機制、讀取 biglog 異步刪除緩存

緩存擊穿:設(shè)置數(shù)據(jù)永不過期。

緩存雪崩:Redis 集群高可用、均勻設(shè)置過期時間。

緩存穿透:接口層校驗、查詢?yōu)榭赵O(shè)置個默認(rèn)空值標(biāo)記、布隆過濾器。

一般用Redis分布式緩存,當(dāng)然有些時候也可以考慮使用本地緩存,如Guava Cache、Caffeine等。使用本地緩存有些缺點,就是無法進(jìn)行大數(shù)據(jù)存儲,并且應(yīng)用進(jìn)程的重啟,緩存會失效。

14. 接口考慮熱點數(shù)據(jù)隔離性

瞬時間的高并發(fā),可能會打垮你的系統(tǒng)??梢宰鲆恍狳c數(shù)據(jù)的隔離。比如業(yè)務(wù)隔離、系統(tǒng)隔離、用戶隔離、數(shù)據(jù)隔離 等。

業(yè)務(wù)隔離,比如 12306 的分時段售票,將熱點數(shù)據(jù)分散處理,降低系統(tǒng)負(fù)載壓力。

系統(tǒng)隔離:比如把系統(tǒng)分成了用戶、商品、社區(qū)三個板塊。這三個塊分別使用不同的域名、服務(wù)器和數(shù)據(jù)庫,做到從接入層到應(yīng)用層再到數(shù)據(jù)層三層完全隔離。

用戶隔離:重點用戶請求到配置更好的機器。

數(shù)據(jù)隔離:使用單獨的緩存集群或者數(shù)據(jù)庫服務(wù)熱點數(shù)據(jù)。

15. 可變參數(shù)配置化,比如紅包皮膚切換等

假如產(chǎn)品經(jīng)理提了個紅包需求,圣誕節(jié)的時候,紅包皮膚為圣誕節(jié)相關(guān)的,春節(jié)的時候,為春節(jié)紅包皮膚等。

如果在代碼寫死控制,可有類似以下代碼:

if(duringChristmas){
img=redPacketChristmasSkin;
}elseif(duringSpringFestival){
img=redSpringFestivalSkin;
}

如果到了元宵節(jié)的時候,運營小姐姐突然又有想法,紅包皮膚換成燈籠相關(guān)的,這時候,是不是要去修改代碼了,重新發(fā)布了?

從一開始接口設(shè)計時,可以實現(xiàn)一張紅包皮膚的配置表 ,將紅包皮膚做成配置化。更換紅包皮膚,只需修改一下表數(shù)據(jù)就好了。

當(dāng)然,還有一些場景適合一些配置化的參數(shù):一個分頁多少數(shù)量控制、某個搶紅包多久時間過期這些,都可以搞到參數(shù)配置化表里面。這也是擴展性思想的一種體現(xiàn)。

16. 接口考慮冪等性

接口是需要考慮冪等性的,尤其搶紅包、轉(zhuǎn)賬這些重要接口。最直觀的業(yè)務(wù)場景,就是用戶連著點擊兩次 ,你的接口有沒有 hold 住 。或者消息隊列出現(xiàn)重復(fù)消費的情況,你的業(yè)務(wù)邏輯怎么控制?

回憶下,什么是冪等?

計算機科學(xué)中,冪等表示一次和多次請求某一個資源應(yīng)該具有同樣的副作用,或者說,多次請求所產(chǎn)生的影響與一次請求執(zhí)行的影響效果相同。

大家別搞混哈,防重和冪等設(shè)計其實是有區(qū)別的 。防重主要為了避免產(chǎn)生重復(fù)數(shù)據(jù),把重復(fù)請求攔截下來即可。而冪等設(shè)計除了攔截已經(jīng)處理的請求,還要求每次相同的請求都返回一樣的效果。不過呢,很多時候,它們的處理流程、方案是類似的。

接口冪等實現(xiàn)方案主要有 8 種:

select + insert + 主鍵/唯一索引沖突

直接 insert + 主鍵/唯一索引沖突

狀態(tài)機冪等

抽取防重表

token 令牌

悲觀鎖

樂觀鎖

分布式鎖

17. 讀寫分離,優(yōu)先考慮讀從庫,注意主從延遲問題

我們的數(shù)據(jù)庫都是集群部署的,有主庫也有從庫,當(dāng)前一般都是讀寫分離的。

比如寫入數(shù)據(jù),肯定是寫入主庫,但是對于讀取實時性要求不高的數(shù)據(jù),則優(yōu)先考慮讀從庫,因為可以分擔(dān)主庫的壓力。

如果讀取從庫的話,需要考慮主從延遲的問題。

18. 接口注意返回的數(shù)據(jù)量,如果數(shù)據(jù)量大需要分頁

一個接口返回報文,不應(yīng)該包含過多的數(shù)據(jù)量。

過多的數(shù)據(jù)量不僅處理復(fù)雜,并且數(shù)據(jù)量傳輸?shù)膲毫σ卜浅4蟆?/p>

如果數(shù)量實在是比較大,可以分頁返回,如果是功能不相關(guān)的報文,那應(yīng)該考慮接口拆分。

19. 好的接口實現(xiàn),離不開 SQL 優(yōu)化

我們做后端的,寫好一個接口,離不開 SQL 優(yōu)化。

SQL 優(yōu)化從以下這幾個維度思考:

explain 分析 SQL 查詢計劃(重點關(guān)注 type、extra、filtered 字段)。

show profile 分析,了解 SQL 執(zhí)行的線程的狀態(tài)以及消耗的時間。

索引優(yōu)化(覆蓋索引、最左前綴原則、隱式轉(zhuǎn)換、order by 以及 group by 的優(yōu)化、join 優(yōu)化)

大分頁問題優(yōu)化(延遲關(guān)聯(lián)、記錄上一頁最大 ID)

數(shù)據(jù)量太大(分庫分表 、同步到 es,用 es 查詢)

20. 代碼鎖的粒度控制好

什么是加鎖粒度呢?

其實就是你要鎖住的范圍是多大。比如你在家上衛(wèi)生間,你只要鎖住衛(wèi)生間就可以了吧,不需要將整個家都鎖起來不讓家人進(jìn)門吧,衛(wèi)生間就是你的加鎖粒度。

我們寫代碼時,如果不涉及到共享資源,就沒有必要鎖住。這就好像你上衛(wèi)生間,不用把整個家都鎖住,鎖住衛(wèi)生間門就可以了。

比如,在業(yè)務(wù)代碼中,有一個 ArrayList 因為涉及到多線程操作,所以需要加鎖操作,假設(shè)剛好又有一段比較耗時的操作(代碼中的slowNotShare方法)不涉及線程安全問題,你會如何加鎖呢?

反例:

//不涉及共享資源的慢方法
privatevoidslowNotShare(){
try{
TimeUnit.MILLISECONDS.sleep(100);
}catch(InterruptedExceptione){
}
}

//錯誤的加鎖方法
publicintwrong(){
longbeginTime=System.currentTimeMillis();
IntStream.rangeClosed(1,10000).parallel().forEach(i->{
//加鎖粒度太粗了,slowNotShare其實不涉及共享資源
synchronized(this){
slowNotShare();
data.add(i);
}
});
log.info("cosumetime:{}",System.currentTimeMillis()-beginTime);
returndata.size();
}

正例:

publicintright(){
longbeginTime=System.currentTimeMillis();
IntStream.rangeClosed(1,10000).parallel().forEach(i->{
slowNotShare();//可以不加鎖
//只對List這部分加鎖
synchronized(data){
data.add(i);
}
});
log.info("cosumetime:{}",System.currentTimeMillis()-beginTime);
returndata.size();
}

21. 接口狀態(tài)和錯誤需要統(tǒng)一明確

提供必要的接口調(diào)用狀態(tài)信息。比如一個轉(zhuǎn)賬接口調(diào)用是成功、失敗、處理中還是受理成功等,需要明確告訴客戶端。如果接口失敗,那么具體失敗的原因是什么。這些必要的信息都必須要告訴給客戶端,因此需要定義明確的錯誤碼和對應(yīng)的描述。同時,盡量對報錯信息封裝一下,不要把后端的異常信息完全拋出到客戶端。

ea49dcd2-88a4-11ed-bfe3-dac502259ad0.png

22. 接口要考慮異常處理

實現(xiàn)一個好的接口,離不開優(yōu)雅的異常處理。

對于異常處理,提十個小建議:

盡量不要使用e.printStackTrace(),而是使用log打印。因為e.printStackTrace()語句可能會導(dǎo)致內(nèi)存占滿。

catch住異常時,建議打印出具體的exception,利于更好定位問題。

不要用一個Exception捕捉所有可能的異常。

記得使用finally關(guān)閉流資源或者直接使用try-with-resource。

捕獲異常與拋出異常必須是完全匹配,或者捕獲異常是拋異常的父類。

捕獲到的異常,不能忽略它,至少打點日志吧。

注意異常對你的代碼層次結(jié)構(gòu)的侵染。

自定義封裝異常,不要丟棄原始異常的信息Throwable cause。

運行時異常RuntimeException ,不應(yīng)該通過catch的方式來處理,而是先預(yù)檢查,比如:NullPointerException處理。

注意異常匹配的順序,優(yōu)先捕獲具體的異常。

23. 優(yōu)化程序邏輯

優(yōu)化程序邏輯這塊還是挺重要的,也就是說,你實現(xiàn)的業(yè)務(wù)代碼,如果是比較復(fù)雜的話,建議把注釋寫清楚 。還有,代碼邏輯盡量清晰,代碼盡量高效。

比如,要使用用戶信息的屬性,根據(jù) session 已經(jīng)獲取到userId了,然后就把用戶信息從數(shù)據(jù)庫查詢出來,使用完后,后面可能又要用到用戶信息的屬性,有些小伙伴沒想太多,反手就把userId再傳進(jìn)去,再查一次數(shù)據(jù)庫。。。我在項目中,見過這種代碼。。。直接把用戶對象傳下來不好嘛。。。

反例:

publicResponsetest(Sessionsession){
UserInfouser=UserDao.queryByUserId(session.getUserId());

if(user==null){
reutrnnewResponse();
}

returndo(session.getUserId());
}

publicResponsedo(StringUserId){
//多查了一次數(shù)據(jù)庫
UserInfouser=UserDao.queryByUserId(session.getUserId());
......
returnnewResponse();
}

正例:

publicResponsetest(Sessionsession){
UserInfouser=UserDao.queryByUserId(session.getUserId());

if(user==null){
reutrnnewResponse();
}

returndo(session.getUserId());
}

//直接傳UserInfo對象過來即可,不用再多查一次數(shù)據(jù)庫
publicResponsedo(UserInfouser){
......
returnnewResponse();
}

當(dāng)然,這只是一些很小的一個例子,還有很多類似的例子,需要大家開發(fā)過程中,多點思考的哈。

24. 接口實現(xiàn)過程中,注意大文件、大事務(wù)、大對象

讀取大文件時,不要Files.readAllBytes直接讀取到內(nèi)存,這樣會 OOM 的,建議使用BufferedReader一行一行來。

大事務(wù)可能導(dǎo)致死鎖、回滾時間長、主從延遲等問題,開發(fā)中盡量避免大事務(wù)。

注意一些大對象的使用,因為大對象是直接進(jìn)入老年代的,可能會觸發(fā) fullGC。

25. 你的接口,需要考慮限流

如果你的系統(tǒng)每秒扛住的請求是 1000,如果一秒鐘來了十萬請求呢?換個角度就是說,高并發(fā)的時候,流量洪峰來了,超過系統(tǒng)的承載能力,怎么辦呢?

如果不采取措施,所有的請求打過來,系統(tǒng) CPU、內(nèi)存、Load 負(fù)載飚得很高,最后請求處理不過來,所有的請求無法正常響應(yīng)。

針對這種場景,我們可以采用限流方案。就是為了保護(hù)系統(tǒng),多余的請求,直接丟棄。

限流定義:

在計算機網(wǎng)絡(luò)中,限流就是控制網(wǎng)絡(luò)接口發(fā)送或接收請求的速率,它可防止 DoS 攻擊和限制 Web 爬蟲。限流,也稱流量控制。是指系統(tǒng)在面臨高并發(fā),或者大流量請求的情況下,限制新的請求對系統(tǒng)的訪問,從而保證系統(tǒng)的穩(wěn)定性。

可以使用 Guava 的RateLimiter單機版限流,也可以使用Redis分布式限流,還可以使用阿里開源組件sentinel限流。

26. 代碼實現(xiàn)時,注意運行時異常(比如空指針、下標(biāo)越界等)

日常開發(fā)中,我們需要采取措施規(guī)避數(shù)組邊界溢出、被零整除、空指針 等運行時錯誤。類似代碼比較常見:

Stringname=list.get(1).getName();//list可能越界,因為不一定有2個元素哈

應(yīng)該采取措施,預(yù)防一下數(shù)組邊界溢出。正例如下:

if(CollectionsUtil.isNotEmpty(list)&&list.size()>1){
Stringname=list.get(1).getName();
}

27. 保證接口安全性

如果你的 API 接口是對外提供的,需要保證接口的安全性。保證接口的安全性有 token 機制和接口簽名 。

token 機制身份驗證 方案還比較簡單的,就是:

ea8758d2-88a4-11ed-bfe3-dac502259ad0.png

客戶端發(fā)起請求,申請獲取 token。

服務(wù)端生成全局唯一的 token,保存到 redis 中(一般會設(shè)置一個過期時間),然后返回給客戶端。

客戶端帶著 token,發(fā)起請求。

服務(wù)端去 redis 確認(rèn) token 是否存在,一般用 redis.del(token) 的方式,如果存在會刪除成功,即處理業(yè)務(wù)邏輯,如果刪除失敗不處理業(yè)務(wù)邏輯,直接返回結(jié)果。

接口簽名 的方式,就是把接口請求相關(guān)信息(請求報文,包括請求時間戳、版本號、appid 等),客戶端私鑰加簽,然后服務(wù)端用公鑰驗簽,驗證通過才認(rèn)為是合法的、沒有被篡改過的請求。

除了加簽驗簽和 token 機制,接口報文一般是要加密的 。當(dāng)然,用 https 協(xié)議是會對報文加密的。如果是我們服務(wù)層的話,如何加解密呢?

可以參考 HTTPS 的原理,就是服務(wù)端把公鑰給客戶端,然后客戶端生成對稱密鑰,接著客戶端用服務(wù)端的公鑰加密對稱密鑰,再發(fā)到服務(wù)端,服務(wù)端用自己的私鑰解密,得到客戶端的對稱密鑰。這時候就可以愉快傳輸報文啦,客戶端用對稱密鑰加密請求報文 ,服務(wù)端用對應(yīng)的對稱密鑰解密報文 。

有時候,接口的安全性,還包括手機號、身份證等信息的脫敏 。就是說,用戶的隱私數(shù)據(jù),不能隨便暴露

28. 分布式事務(wù),如何保證

分布式事務(wù):就是指事務(wù)的參與者、支持事務(wù)的服務(wù)器、資源服務(wù)器以及事務(wù)管理器分別位于不同的分布式系統(tǒng)的不同節(jié)點之上。簡單來說,分布式事務(wù)指的就是分布式系統(tǒng)中的事務(wù),它的存在就是為了保證不同數(shù)據(jù)庫節(jié)點的數(shù)據(jù)一致性。

分布式事務(wù)的幾種解決方案:

2PC(二階段提交)方案、3PC

TCC(Try、Confirm、Cancel)

本地消息表

最大努力通知

seata

29. 事務(wù)失效的一些經(jīng)典場景

我們的接口開發(fā)過程中,經(jīng)常需要使用到事務(wù)。所以需要避開事務(wù)失效的一些經(jīng)典場景。

方法的訪問權(quán)限必須是 public,其他 private 等權(quán)限,事務(wù)失效。

方法被定義成了 final 的,這樣會導(dǎo)致事務(wù)失效。

在同一個類中的方法直接內(nèi)部調(diào)用,會導(dǎo)致事務(wù)失效。

一個方法如果沒交給 spring 管理,就不會生成 spring 事務(wù)。

多線程調(diào)用,兩個方法不在同一個線程中,獲取到的數(shù)據(jù)庫連接不一樣的。

表的存儲引擎不支持事務(wù)。

如果自己 try...catch 誤吞了異常,事務(wù)失效。

錯誤的傳播特性。

30. 掌握常用的設(shè)計模式

把代碼寫好,還是需要熟練常用的設(shè)計模式,比如策略模式、工廠模式、模板方法模式、觀察者模式等等。

設(shè)計模式,是代碼設(shè)計經(jīng)驗的總結(jié)。使用設(shè)計模式可以可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。

31. 寫代碼時,考慮線性安全問題

高并發(fā) 情況下,HashMap可能會出現(xiàn)死循環(huán)。因為它是非線性安全的,可以考慮使用ConcurrentHashMap。所以這個也盡量養(yǎng)成習(xí)慣,不要上來反手就是一個new HashMap()。

Hashmap、Arraylist、LinkedList、TreeMap 等都是線性不安全的。

Vector、Hashtable、ConcurrentHashMap 等都是線性安全的。

32. 接口定義清晰易懂,命名規(guī)范

我們寫代碼,不僅僅是為了實現(xiàn)當(dāng)前的功能,也要有利于后面的維護(hù)。

說到維護(hù),代碼不僅僅是寫給自己看的,也是給別人看的。

所以接口定義要清晰易懂,命名規(guī)范。

33. 接口的版本控制

接口要做好版本控制。就是說,請求基礎(chǔ)報文,應(yīng)該包含version接口版本號字段,方便未來做接口兼容。其實這個點也算接口擴展性的一個體現(xiàn)點吧。

比如客戶端 APP 某個功能優(yōu)化了,新老版本會共存,這時候我們的version版本號就派上用場了,對version做升級,做好版本控制。

34. 注意代碼規(guī)范問題

注意一些常見的代碼壞味道:

大量重復(fù)代碼(抽共用方法,設(shè)計模式)。

方法參數(shù)過多(可封裝成一個 DTO 對象)。

方法過長(抽小函數(shù))。

判斷條件太多(優(yōu)化 if...else)。

不處理沒用的代碼。

不注重代碼格式。

避免過度設(shè)計。

35. 保證接口正確性,其實就是保證更少的 bug

保證接口的正確性,換個角度講,就是保證更少的bug,甚至是沒有bug。所以接口開發(fā)完后,一般需要開發(fā)自測一下 。

然后的話,接口的正確性還體現(xiàn)在,多線程并發(fā)的時候,保證數(shù)據(jù)的正確性 ,等等。比如做一筆轉(zhuǎn)賬交易,扣減余額的時候,可以通過 CAS 樂觀鎖的方式保證余額扣減正確。

如果你是實現(xiàn)秒殺接口,得防止超賣問題吧。可以使用 Redis 分布式鎖防止超賣問題。

36. 學(xué)會溝通,跟前端溝通,跟產(chǎn)品溝通

我把這一點放到最后,學(xué)會溝通是非常非常重要的。

比如你開發(fā)定義接口時,一定不能上來就自己埋頭把接口定義完了需要跟客戶端先對齊接口 。遇到一些難點時,跟技術(shù) leader 對齊方案。實現(xiàn)需求的過程中,有什么問題,及時跟產(chǎn)品溝通。

總之就是,開發(fā)接口過程中,一定要溝通好~

來源:撿田螺的小男孩 在此特別鳴謝!

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

    關(guān)注

    33

    文章

    8366

    瀏覽量

    150554
  • 數(shù)據(jù)庫
    +關(guān)注

    關(guān)注

    7

    文章

    3739

    瀏覽量

    64174
  • 指針
    +關(guān)注

    關(guān)注

    1

    文章

    476

    瀏覽量

    70481

原文標(biāo)題:為什么有公司規(guī)定所有接口都必須用Post?

文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    用DAQ助手必須用NI公司的采集卡嗎

    小弟剛接觸 labview ,請問用DAQ助手必須用NI公司的采集卡嗎? 求解答啊
    發(fā)表于 07-22 22:42

    請問用KEIL開發(fā)FRDM-KL25Z板子調(diào)試必須用JLINK嗎?

    請問用KEIL開發(fā)FRDM-KL25Z板子調(diào)試必須用JLINK嗎?可以直接用一根串口線進(jìn)行調(diào)試嗎?就是板子自帶的那個USB接口可以直接調(diào)試嗎?
    發(fā)表于 01-09 11:14

    移植 SimpliciTI是不是必須用TI的片子

    移植 SimpliciTI是不是必須用TI的片子?目前用的是CC1101,需要掛載到MCU上,MCU是不是必須采用TI的片子?我到官網(wǎng)上下載了IAR協(xié)議棧,發(fā)現(xiàn)IAR版本是要430的或者是8051核的~
    發(fā)表于 03-08 18:30

    求教,分割電源層出現(xiàn)告警!!!!!!!所有的角落都必須在平面內(nèi)的多邊形內(nèi)

    `求教,分割電源層出現(xiàn)告警!!!!!!!所有的角落都必須在平面內(nèi)的多邊形內(nèi)`
    發(fā)表于 07-04 09:40

    STM32什么時候必須用外部晶振?

    STM32內(nèi)部LSI和HSI,如果使用LSE和HSE,除了增加成本,板卡尺寸也增加,還可能遇到晶振起振問題。那么什么時候用內(nèi)部振蕩器就可以,什么時候必須用外部晶振?
    發(fā)表于 03-29 14:55

    SYS BIOS必須用IBL啟動嗎?

    SYS BIOS必須用IBL來啟動嗎? 描述這一部分的文檔在哪里呀?
    發(fā)表于 06-21 17:29

    曲線形狀滑塊設(shè)計是否所有的滑塊都必須等尺寸?

    你好,我們的目的是將CasSooLoad片段彎曲形狀(如附錄中所示),并有以下查詢。1 &是否任何指南文件可用于沿曲線路徑放置滑塊段?2、是否所有的滑塊都必須等尺寸?3對滑塊段的接地網(wǎng)寬度
    發(fā)表于 11-27 11:09

    請問ADF41020的輸入是否都必須是方波信號?

    ADF41020的4號引腳RFIN和8號引腳REFIN,輸入是否都必須是方波信號(數(shù)字信號)?
    發(fā)表于 12-20 09:12

    個產(chǎn)品必須用正負(fù)20V,供電是12V電池請幫忙提供個思路?

    個產(chǎn)品必須用正負(fù)20V,供電是12V電池,請幫忙提供個思路啊 請各路大神指教
    發(fā)表于 07-25 08:02

    為什么所有的電器中都必須有電阻?

    電阻的用途到底是什么?為什么所有的電器中都必須有電阻?電阻到底什么用?
    發(fā)表于 06-08 07:04

    在多層電路板里是不是所有電源都必須放在內(nèi)電層呢?

    在多層電路板里是不是所有電源都必須放在內(nèi)電層呢?
    發(fā)表于 05-06 10:20

    PHP中REQUEST和POST及GET什么區(qū)別

    []的功能,但是$_REQUEST[]比較慢。通過POST和GET方法提交的所有數(shù)據(jù)都可以通過$_REQUEST數(shù)組獲得。
    發(fā)表于 02-19 14:26 ?2次下載
    PHP中REQUEST和<b class='flag-5'>POST</b>及GET<b class='flag-5'>有</b>什么區(qū)別

    在物聯(lián)網(wǎng)的所有階段都必須考慮安全性

    從規(guī)劃、設(shè)計、實施、驗證、部署和運營開始,所有階段都必須考慮安全性。
    的頭像 發(fā)表于 05-14 15:11 ?2748次閱讀

    所有接口都用post請求的原因

    查看上面的區(qū)別,就會發(fā)現(xiàn)post在發(fā)送數(shù)據(jù)量大的請求時優(yōu)勢很顯示,get則更適合獲取靜態(tài)資源、簡單的查詢等接口。 我個人在開發(fā)接口的時候也會注意,將簡單的查詢請求使用get方法,其他增、刪、改、復(fù)雜的查詢請求都可以使用
    發(fā)表于 08-24 10:06 ?376次閱讀
    <b class='flag-5'>所有</b><b class='flag-5'>接口</b>都用<b class='flag-5'>post</b>請求的原因

    什么是恒流驅(qū)動?led必須用恒流電路嗎?

    什么是恒流驅(qū)動?led必須用恒流電路嗎? 恒流驅(qū)動是指在電路中保持恒定的電流,這種電流供給LED燈或其他電路組件。在LED照明應(yīng)用中,LED必須使用恒流電路,以確保LED燈的亮度和壽命。在這篇文章中
    的頭像 發(fā)表于 09-05 09:19 ?1.1w次閱讀