高可用、高效能? 介面設計的 16 個原則
本文來自作者LY 在 GitChat 上分享「如何設計出高可用、高效能的介面」,「閱讀原文」檢視交流實錄
「文末高能」
編輯 | 嘉仔
發起這個 Chat 只是一時興起,想了一些點就寫出來了,但自己一讀,感覺一點乾貨都沒有,真是汗顏。但還是也希望此拙文能帶來一些你的思考,歡迎交流。
介面設計需要考慮哪些方面
-
介面的命名。
-
請求引數。
-
支援的協議。
-
TPS、併發數、響應時長。
-
資料儲存。DB選型、快取選型。
-
是否需要依賴於第三方。
-
介面是否拆分。
-
介面是否需要冪等。
-
防刷。
-
介面限流、降級。
-
負載均衡器支援。
-
如何部署。
-
是否需要服務治理。
-
是否存在單點。
-
介面是否資源包、預載入還是內建。
-
是否需要本地快取。
-
是否需要分散式快取、快取穿透怎麼辦。
-
是否需要白名單。
當我們設計介面,我們或多或少都會有上面列舉的一些考慮,我們只有想的更多才能讓讓我們的介面更加完善,我個人覺得100%完美的介面是不存在,只有適合才是最重要。
介面設計原則
原則一:必須符合Restful,統一返回格式,約定業務層錯誤編碼,每個編碼可以攜帶可選的錯誤資訊。
原則二: 命名必須規範、優雅。
原則三:單一性。
單一性是指介面要做的事情應該是一個比較單一的事情,比如登陸介面,登陸完成應該只是返回登陸成功以後一些使用者資訊即可,但很多人為了減少介面互動,返回一大堆額外的資料。
比如有人設計一個使用者列表介面,介面他返回每一條資料都是包含使用者了一大堆跟另外無關的資料,結果一問,原來其他無關的資料是他下一步想要獲取的,想達成資料的懶加載。
原則四:可擴充套件。
介面擴充套件性,是指設計介面的時候多想想多種情況,多考慮各個方面,其實我覺得單獨將擴充套件性放在這裡也是不妥的,感覺說的跟單一性有點相反的意思,其實這個不是這個意思。
這邊的擴充套件性是指我們的介面充分考慮客戶端,想想他們是如何呼叫的,他要怎樣使用我的程式碼,他會如何擴充套件我的程式碼,不要把過多的工作寫在你的接口裡面,而應該把更多的主動權交給客戶程式設計師。
如獲取不同的列表資料介面,我們不可能將每個列表都寫成一個介面。 還有一點,我這裡特別想指出來的是很多開發人員為了省事(姑且只能這麼理解),將介面設計當成只是 app 頁面展示。
這些人將一個頁面展示就用一個介面實現,而不考慮這些資料是不是屬於不同的模組、是不是屬於不同的展示範疇、結果下次視覺一改,整個介面又得重寫,不能複用。
原則五:必須有文件。
良好的介面設計,離不開清晰的介面文件表述。文件表述一定要足夠詳細
原則六:產品心。
為什麼我說要有產品心?因為我覺得很多人忽略了這一點。我來說一下假如開發一個app,如果一開始連個互動文件給你都沒有的話,你怎麼設計介面?
所以我覺得作為一個服務端後臺開發人員應該要有產品心,特別是對於互動文件應該好好理解,因為這些都會對我們的介面設計有很大的影響。
我在設計介面的時候就很常發現很多互動文件根本就走不通,產品沒有考慮到位,互動文件缺失,這時候作為一個開發要主動推動,完善。
原則七:第三方服務介面資料能快取就快取。
原則八:第三方服務需要做降級。
原則九:建議消除單點。
原則十:介面粒度要小。
原則十一:客戶端能處理的邏輯就不要給服務端處理,減少服務端壓力。
原則十二:資源預載入。
原則十三:不要過度設計。
原則十四:快取儘量不要穿透。
原則十五:介面能快取就快取。
原則十六:思辨大於執行
如何保證介面的高可用、高效能
上面也列舉很多需要考慮和設計的原則,其實還有很多方面,我這邊也不是特別全面。
居於上面列舉的這些考慮點,其實這邊說服務是更恰當,能把上面說的點做好,其實介面也是比較可靠,如何設計以及保證介面的高可用和高效能。可以思考一下以下幾個 point
高效能:如果我們發現這個介面tps和響應時間沒有達到我們的要求怎麼辦。
-
A:資料儲存方面:我們會想資料庫有沒有分庫、分表、有沒有做主從,有沒有讀寫分離、欄位是否有加索引、是否存在慢 sql,資料庫引擎是否選用合適、是不是用了事務;
其次我們會想到是不是引用了分散式快取、快取 key 大小是否合適,失效時間是否設定合理,會不會大量快取穿透、有沒有引入本地快取。
-
B:業務方面:是否有大量的計算、能否非同步處理。是否需要引入執行緒池或者 MQ 來非同步處理任務。有沒有必要將介面進行垂直拆分和水平拆分、將介面粒度變小。
-
C:其他方面:nginx 層面做快取、加機器、用 ssd,資源放 cdn,多機房部署、資原始檔預載入。
高可用:如何保證服務高可用,需要從幾個維度來實現:
-
A:消除單點,基於高可用第二位。
-
B:能做叢集的全部做叢集。譬如 Redis 叢集、mysql叢集、MongoDB副本集。
-
C:能做讀寫分離的都做讀寫分離。
-
D:異地多機房部署,接入 GSLB
-
E:必須有限流、降級機制。
-
F:監控。高可用的保證,基於第一位。
下圖是從一個基本的請求出發來梳理需要涉及到各個段,以及各個端能做的事情。談談介面服務,但不侷限於介面本身。
-
客戶端:資源預載入、限制請求、資料上報。我這邊就拿客戶端來舉個例子。介面服務所依賴的資源包或者一些公共配置預載入在本地,減少介面的互動,通過請求配置檔案是否更新,code是否是304等來;
介面做一些請求限制,比如搶紅包、搶券等,單位時間內N次點選只請求一次等;介面失敗資料上報來;這就是客戶端可以做到的對介面有幫助的事情
-
GSLB/HttpDNS:多機房部署、流量切換、域名劫持,一般技術和業務比較成熟的公司這一層。
-
資原始檔放CDN。
-
負載均衡器:lVS+Nginx是網際網路常用的做負載均衡,可以實現四層/七層負載均衡;這裡除了可以分流、轉發以外,我們用的更多的基於令牌桶限流、快取。
-
本地快取。本地快取能減少我們訪問DB或者分散式快取,本地快取推薦使用guava,guava裡面有很多特性很好用,例如基於令牌桶的限流;當快取失效時只穿透一個請求去訪問後端。
-
執行緒池。
-
模組拆分。將一個專案按功能模組拆分,一個介面也可以按業務粒度進行拆分。
-
資料中心。提供資料支撐,譬如黑名單。
-
資料庫。加索引、分庫、分表、讀寫分離
-
分散式快取。資料分片、拆分大key,並做叢集,採用分散式鎖
-
MQ。做介面拆分利器,非同步操作。
-
其他服務。限流、防刷以及降級(特別是第三方服務,保證第三方服務down掉不要影響我們自身的服務)。在這裡也需要考慮做第三方資料的快取或者持久化,譬如實名認證、身份證認證等。
-
監控。監控永遠是必須的,能讓你第一時間知道介面服務是否ok
個人小分享
1)介面Restful,統一返回格式,約定業務層錯誤編碼,每個編碼可以攜帶可選的錯誤資訊
在前司,客戶端和服務之間是有統一的資料返回格式,約定各層的編碼,可以通過編碼位數以及編碼就可以看出是那一層出問題。
我覺得這對我們定位問題以及維護來說具有莫大的意義,並對異常也進行捕捉,封裝成對應的 code,我之前閱讀一些人的程式碼發現其專案根本沒有做這一層,因為簡單而不做我覺得有失所望。
2)採用 hybird 模式
採用 hybird 模式涉及到資源預載入的問題,在很多專案裡面都大量使用,譬如前司的生活服務,就採用了 hybird 模式,先將資原始檔(包含圖片、前端頁面)打包放到伺服器並通過版本號進行管理,並通過一個總的配置檔案來管理,如果是H5頁面可以進行模板預先設計,down到本地。
配置檔案格式:
*檔案1* name:xxx url:http:xxxx md5:xxxx *檔案2* name:zzz url:http:zzzz md5:zzz
客戶端每次啟動應用或者定時請求總的配置檔案,通過http code是否是304判斷是否需要下載這個總的配置檔案,如果code是200,那麼下載這個配置,比較那個檔案發生變化,並將其下載。這樣的好處:
-
減少介面的互動;
-
資源預載入,節省流量,開啟頁面更加流暢,對於服務端來說字需要返回資料json串就行,而不需要其他,減少服務端壓力;
-
方便開發人員,資源管理更加簡潔,比如做活動需要的h5頁面,只需要前端上傳對應的h5資源包到服務端,不需要通過後端開發人員就可以搞定。
雖然這個原理很簡單,但是現在很多app還是沒有做這個,都是通過填寫一個url,載入網頁的方式去開啟,體驗性太不友好。
3)客戶端
客戶端跟服務端就是介面請求的關係,很多時候需要要求客戶端做一些資料快取的工作以及一些檢驗工作。在前司已經好幾次給客戶端的同學坑過了,客戶端同學介面亂呼叫,死迴圈呼叫。
一次是做一個關於事件提醒的功能,需要每天定時呼叫呼叫服務端一個介面,結果客戶端的同學寫了一個 bug 導致請求每隔一兩秒就呼叫一次,導致伺服器這邊此介面 pv 翻了N倍,而且這個 bug 通過測試同學很難測試出來;
還有一次發現服務端一段時間以後 UV 不見漲,但是PV卻漲的很猛,定位發現是客戶端同學A圖省事在一個方法裡面呼叫了N個介面,也就是模板方法。
因為版本更新,同學B需要做一個新的功能,然後也呼叫了A同學的介面導致,從而導致PV上升,其實B同學完全不需要呼叫這麼多介面。這些都是真實案例,所以這裡需要有一個監控介面異常的機制。
4)思辨大於執行
寫到這裡覺得這個非常重要,思辨大於執行,意味著我們不是一股腦就去幹,也不是不去幹,我們做事情需要思考、辨別;從而讓事情更高效、更好、更有力的執行。介面設計也一樣,需要我們去思辨。
5)本地快取、分散式快取以及非同步
快取在前司主要分為客戶端快取、CDN快取、本地快取(guava)、Redis快取。
在MZ早期是介面是採用 DB+本地快取的方式提供資料,但這種模式DB壓力大,介面吞吐量小,本地快取多機難一致性、更新不及時問題。
為了解決這些問題,引入分散式快取,並通過 Task 將業務資料刷到 Redis,介面只訪問 redis,不會訪問 DB,及時 DB 故障也不會影響功能。
不同的業務系統系統通過 MQ 來解耦,多機房不是通過 MQ 來實現資料的一直。
比如,評論,先通過寫 Redis,寫 MQ 來實現資料在多機房同步,再通過 task 將 Redis 中評論同步到 DB 中。
介面設計涉及方方面面,這邊也只談到一個大概,雖然有點泛泛而談,希望此拙文對你有所啟示。
6)資料庫
資料庫分庫分表,一般都是通過 userId 或者 imei 或者 mac 地址來分表,單表資料量控制在500w以內,這需要我們提前估算好資料量,儘量避免資料的遷移。
在前司,資料庫一般都是採用 mysql+MongoDB 兩種,MySQL儲存使用者的使用者資料,MongoDB 儲存業務資料,就像閱讀和生活服務裡面的業務資料就儲存在 MongoDB 裡面。
在資料庫這層,我們主要也是通過主從模式、讀寫分離、分庫、分表來實現資料的可用性。
7)業務
業務儘可能拆分、獨立部署、將專案按業務劃分、按功能劃分等。譬如生活服務,我們當時主要拆分成管理後臺 admin、任務 task、活動、web、資料展示模組。
8)資料中心
每個大一點的公司都有資料部門,我們這邊可以通過資料中心的資料分析來達到我們需要的資料。
比如黑名單,推廣效果、活動資料。我們可以通過這些完善我們的介面功能。之前在前司做了個數據處理後非同步載入到 Redis 來實現資料利用的專案。
以上都是我個人的一些拙見,請大家思辨。
近期熱文
自動化構建
從 Gradle 開始
「閱讀原文」看交流實錄,你想知道的都在這裡