日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

概要

本文是學習B站毛劍老師的《API 工程化分享》的學習筆記,分享了 gRPC 中的 Proto 管理方式,Proto 分倉源碼方式,Proto 獨立同步方式,Proto git submodules 方式,Proto 項目布局,Proto Errors,服務端和客戶端的 Proto Errors,Proto 文檔等等

目錄

  • Proto IDL Management
  • IDL Project Layout
  • IDL Errors
  • IDL Docs

Proto IDL Management

  • Proto IDL
  • Proto 管理方式
  • Proto 分倉源碼方式
  • Proto 獨立同步方式
  • Proto git submodules 方式

Proto IDL

gRPC 從協議緩沖區使用接口定義語言 (IDL)。協議緩沖區 IDL 是一種與平臺無關的自定義語言,具有開放規范。 開發人員會創作 .proto 文件,用于描述服務及其輸入和輸出。 然后,這些 .proto 文件可用于為客戶端和服務器生成特定于語言或平臺的存根,使多個不同的平臺可進行通信。 通過共享 .proto 文件,團隊可生成代碼來使用彼此的服務,而無需采用代碼依賴項。

Proto 管理方式

煎魚的一篇文章:真是頭疼,Proto 代碼到底放哪里?

文章中經過多輪討論對 Proto 的存儲方式和對應帶來的優缺點,一共有如下幾種方案:

  • 代碼倉庫
  • 獨立倉庫
  • 集中倉庫
  • 鏡像倉庫

鏡像倉庫

API 工程化分享

 

在我自己的微服務倉庫里面,有一個 Proto 目錄,就是放我自己的 Proto,然后在我提交我的微服務代碼到主干或者某個分支的時候,它可能觸發一個 mirror 叫做自動同步,會鏡像到這個集中的倉庫,它會幫你復制過去,相當于說我不需要把我的源碼的 Proto 開放給你,同時還會自動復制一份到集中的倉庫

在煎魚的文章里面的集中倉庫還是分了倉庫的,B站大倉是一個統一的倉庫。為什么呢?因為比方像谷歌云它整個對外的 API 會在一個倉庫,不然你讓用戶怎么找?到底要去哪個 GitHub 下去找?有這么多 project 怎么找?根本找不到,應該建統一的一個倉庫,一個項目就搞定了

我們最早衍生這個想法是因為無意中看到了 google APIs 這個倉庫。大倉可以解決很多問題,包括高度代碼共享,其實對于 API 文件也是一樣的,集中在一個 Repo 里面,很方便去檢索,去查閱,甚至看文檔,都很方便

我們不像其他公司喜歡弄一個 UI 的后臺,我們喜歡 Git,它很方便做擴展,包括 CICD 的流程,包括 coding style 的 check,包括兼容性的檢測,包括 code review 等等,你都可以基于 git 的擴展,gitlab 的擴展,GitHub 的一些 actions,做很多很多的工作

Proto 分倉源碼方式

過去為了統一檢索和規范 API,我們內部建立了一個統一的 bapis 倉庫,整合所有對內對外 API。它只是一個申明文件。

  • API 倉庫,方便跨部門協作;
  • 版本管理,基于 git 控制;
  • 規范化檢查,API lint;
  • API design review,變更 diff;
  • 權限管理,目錄 OWNERS;
API 工程化分享

 

集中式倉庫最大的風險是什么呢?是誰都可以更改

大倉的核心是放棄了讀權限的管理,針對寫操作是有微觀管理的,就是你可以看到我的 API 聲明,但是你實際上調用不了,但是對于遷入 check in,提到主干,你可以在不同層級加上 owner 文件,它里面會描述誰可以合并代碼,或者誰負責 review,兩個角色,那就可以方便利用 gitlab 的 hook 功能,然后用 owner 文件做一些細粒度的權限管理,針對目錄級別的權限管理

最終你的同事不能隨便遷入,就是說把文件的寫權限,merge 權限關閉掉,只允許通過 merge request 的評論區去回復一些指令,比方說 lgtm(looks good to me),表示 review 通過,然后你可以回復一個 Approve,表示這個代碼可以被成功 check in,這樣來做一些細粒度的權限檢驗

怎么遷入呢?我們的想法是在某一個微服務的 Proto 目錄下,把自己的 Proto 文件管理起來,然后自動同步進去,就相當于要寫一個插件,可以自動復制到 API 倉庫里面去。做完這件事情之后,我們又分了 api.go,api.JAVA,git submodule,就是把這些代碼使用 Google protobuf,protoc 這個編譯工具生成客戶端的調用代碼,然后推到另一個倉庫,也就是把所有客戶端調用代碼推到一個源碼倉庫里面去

Proto 獨立同步方式

API 工程化分享

 

移動端采用自定義工具方式,在同步代碼階段,自動更新最新的 proto 倉庫到 worksapce 中,之后依賴 bazel 進行構建整個倉庫

  • 業務代碼中不依賴 target 產物,比如 objective-c 的 .h/.a 文件,或者 Go 的 .go 文件(鉆石依賴、proto 未更新問題)

源碼依賴會引入很多問題

  • 依賴信息丟失
  • proto 未更新
  • 鉆石依賴

依賴信息丟失

在你的工程里面依賴了其他服務,依賴信息變成了源碼依賴,你根本不知道依賴了哪個服務,以前是 protobuf 的依賴關系,現在變成了源碼依賴,服務依賴信息丟失了。未來我要去做一些全局層面的代碼盤點,比方說我要看這個服務被誰依賴了,你已經搞不清楚了,因為它變成了源碼依賴

proto 未更新

如果我的 proto 文件更新了,你如何保證這個人重新生成了 .h/.a 文件,因為對它來說這個依賴信息已經丟失,為什么每次都要去做這個動作呢?它不會去生成 .h/.a 文件

鉆石依賴

當我的 A 服務依賴 B 服務的時候,通過源碼依賴,但是我的 A 服務還依賴 C 服務,C 服務是通過集中倉庫 bapis 去依賴的,同時 B 和 C 之間又有一個依賴關系,那么這個時候就可能出現對于 C 代碼來說可能會注冊兩次,protobuf 有一個約束就是說重名文件加上包名是不允許重復的,否則啟動的時候就會 panic,有可能會出現鉆石依賴

  • A 依賴 B
  • A 依賴 C
  • A 和 B 是源碼依賴
  • A 和 C 是 proto 依賴
  • B 和 C 之間又有依賴

那么它的版本有可能是對不齊的,就是有風險的,這就是為什么 google basic 構建工具把 proto 依賴的名字管理起來,它并沒有生成 .go 文件再 checkin 到倉庫里面,它不是源碼依賴,它每一次都要編譯,每次都要生成 .go 文件的原因,就是為了版本對齊

Proto git submodules 方式

經過多次討論,有幾個核心認知:

  • proto one source of truth,不使用鏡像方式同步,使用 git submodules 方式以倉庫中目錄形式來承載;
  • 本地構建工具 protoc 依賴 go module 下的相對路徑即可;
  • 基于分支創建新的 proto,submodules 切換分支生成 stub 代碼,同理 client 使用聯調切換同一個分支;
  • 維護 Makefile,使用 protoc + go build 統一處理;
  • 聲明式依賴方式,指定 protoc 版本和 proto 文件依賴(基于 BAZEL.BUILD 或者 Yaml 文件)

proto one source of truth

如果只在一個倉庫里面,如果只有一個副本,那么這個副本就是唯一的真相并且是高度可信任的,那如果你是把這個 proto 文件拷來拷去,最終就會變得源頭更新,拷貝的文件沒辦法保證一定會更新

鏡像方式同步

實際上維護了本地微服務的目錄里面有一個 protobuf 的定義,鏡像同步到集中的倉庫里面,實際上是有兩個副本的

使用 git submodules 方式以倉庫中目錄形式來承載

git submodules 介紹

子模塊允許您將 Git 存儲庫保留為另一個 Git 存儲庫的子目錄。這使您可以將另一個存儲庫克隆到您的項目中并保持您的提交分開。

API 工程化分享

 

圖中 gateway 這個目錄就是以本地目錄的形式,但是它是通過 git submodules 方式給承載進來的

如果公司內代碼都在一起,api 的定義都在一起,那么大倉絕對是最優解,其次才是 git submodules,這也是 Google 的建議

我們傾向于最終 proto 的管理是集中在一個倉庫里面,并且只有一份,不會做任何的 copy,通過 submodules 引入到自己的微服務里面,也就是說你的微服務里面都會通過 submodules 把集中 API 的 git 拷貝到本地項目里面,但是它是通過 submodeles 的方式來承載的,然后你再通過一系列 shell 的工具讓你的整個編譯過程變得更簡單

IDL Project Layout

Proto Project Layout

在統一倉庫中管理 proto,以倉庫為名

根目錄:

  • 目錄結構和 package 對齊;
  • 復雜業務的功能目錄區分;
  • 公共業務功能:api、rpc、type;
API 工程化分享

 

目錄結構和 package 對齊

我們看一下 googleapis 大量的 api 是如何管理的?

第一個就是在 googleapis 這個項目的 github 里面,它的第一級目錄叫 google,就是公司名稱,第二個目錄是它的業務域,業務的名稱

目錄結構和 protobuf 的包名是完全對齊的,方便檢索

復雜業務的功能目錄區分

v9 目錄下分為公共、枚舉、錯誤、資源、服務等等

公共業務功能:api、rpc、type

在 googleapis 的根目錄下還有類似 api、rpc、type 等公共業務功能

IDL Errors

  • Proto Errors
  • Proto Errors:Server
  • Proto Errors:Client

Proto Errors

  • 使用一小組標準錯誤配合大量資源
  • 錯誤傳播

用簡單的協議無關錯誤模型,這使我們能夠在不同的 API,API 協議(如 gRPC 或 HTTP)以及錯誤上下文(例如,異步,批處理或工作流錯誤)中獲得一致的體驗。

API 工程化分享

 


API 工程化分享

 

使用一小組標準錯誤配合大量資源

服務器沒有定義不同類型的“找不到”錯誤,而是使用一個標準 google.rpc.Code.NOT_FOUND 錯誤代碼并告訴客戶端找不到哪個特定資源。狀態空間變小降低了文檔的復雜性,在客戶端庫中提供了更好的慣用映射,并降低了客戶端的邏輯復雜性,同時不限制是否包含可操作信息。

我們以前自己的業務代碼關于404,關于某種資源找不到的錯誤碼,定義了上百上千個,請問為什么大家在設計 HTTP restful 或者 grpc 接口的時候不用人家標準的狀態碼呢?人家有標準的404,或者 not found 的狀態碼,用狀態碼去映射一下通用的錯誤信息不好嗎?你不可能調用一個接口,返回幾十種具體的錯誤碼,你根本對于調用者來說是無法使用的。當我的接口返回超過3個自定義的錯誤碼,你就是面向錯誤編程了,你不斷根據錯誤碼做不同的處理,非常難搞,而且你每一個接口都要去定義

這里的核心思路就是使用標準的 HTTP 狀態碼,比方說500是內部錯誤,503是網關錯誤,504是超時,404是找不到,401是參數錯誤,這些都是通用的,非常標準的一些狀態碼,或者叫錯誤碼,先用它們,因為不是所有的錯誤都需要我們叫業務上 hint,進一步處理,也就是說我調你的服務報錯了,我大概率是啥都不做的,因為我無法糾正服務端產生的一個錯誤,除非它是帶一些業務邏輯需要我做一些跳轉或者做一些特殊的邏輯,這種不應該特別多,我覺得兩個三個已經非常多了

所以說你會發現大部分去調用別人接口的時候,你只需要用一個通用的標準的狀態碼去映射,它會大大降低客戶端的邏輯復雜性,同時也不限制說你包含一些可操作的 hint 的一些信息,也就是說你可以包含一些指示你接下來要去怎么做的一些信息,就是它不沖突

錯誤傳播

如果您的 API 服務依賴于其他服務,則不應盲目地將這些服務的錯誤傳播到您的客戶端。

舉個例子,你現在要跟移動端說我有一個接口,那么這個接口會返回哪些錯誤碼,你始終講不清楚,你為什么講不清楚呢?因為我們整個微服務的調用鏈是 A 調 B,B 調 C,C 調 D,D 的錯誤碼會一層層透傳到 A,那么 A 的錯誤碼可能會是 ABCD 錯誤碼的并集,你覺得你能描述出來它返回了哪些錯誤碼嗎?根本描述不出來

所以對于一個服務之間的依賴關系不應該盲目地將下游服務產生的這些錯誤碼無腦透傳到客戶端,并且曾經跟海外很多公司,像 Uber,Twitter,Netflix,跟他們很多的華人的朋友交流,他們都不建議大家用這種全局的錯誤碼,比方 A 部門用 01 開頭,B 部門用 02 開頭,類似這樣的方式去搞所謂的君子契約,或者叫松散的沒有約束的脆弱的這種約定

在翻譯錯誤時,我們建議執行以下操作:

  • 隱藏實現詳細信息和機密信息
  • 調整負責該錯誤的一方。例如,從另一個服務接收 INVALID_ARGUMENT 錯誤的服務器應該將 INTERNAL 傳播給它自己的調用者。

比如你返回的錯誤碼是4,代表商品已下架,我對這個錯誤很感興趣,但是錯誤碼4 在我的項目里面已經被用了,我就把它翻譯為我還沒使用的錯誤碼6,這樣每次翻譯的時候就可以對上一層你的調用者,你就可以交代清楚你會返回錯誤碼,因為都是你定義的,而且是你翻譯的,你感興趣的才翻譯,你不感興趣的通通返回 500 錯誤,就是內部錯誤,或者說 unknown,就是未知錯誤,這樣你每個 API 都能講清楚自己會返回哪些錯誤碼

API 工程化分享

 

在 grpc 傳輸過程中,它會要求你要實現一個 grpc states 的一個接口的方法,所以在 Kraots 的 v2 這個工程里面,我們先用前面定義的 message Error 這個錯誤模型,在傳輸到 grpc 的過程中會轉換成 grpc 的 error_details.proto 文件里面的 ErrorInfo,那么在傳輸到 client 的時候,就是調用者請求服務,service 再返回給 client 的時候再把它轉換回來

也就是說兩個服務使用一個框架就能夠對齊,因為你是基于 message Error 這樣的錯誤模型,這樣在跨語言的時候同理,經過 ErrorInfo 使用同樣的模型,這樣就解決了跨語言的問題,通過模型的一致性

API 工程化分享

 

Proto Errors:Server

API 工程化分享

 

errors.proto 定義了 Business Domain Error 原型,使用最基礎的 Protobuf Enum,將生成的源碼放在 biz 大目錄下,例如 biz/errors

  • biz 目錄中核心維護 Domain,可以直接依賴 errors enum 類型定義;
  • data 依賴并實現了 biz 的 Reporisty/ACL,也可以直接使用 errors enum 類型定義;
  • TODO:Kratos errors 需要支持 cause 保存,支持 Unwrap();

在某一個微服務工程里面,errors.proto 文件實際上是放在 API 的目錄定義,之前講的 API 目錄定義實際上是你的服務里面的 API 目錄,剛剛講了一個 submodules,現在你可以理解為這個 API 目錄是另外一個倉庫的 submodules,最終你是把這些信息提交到那個 submodules,然后通過 reference 這個 submodules 獲取到最新的版本,其實你可以把它打成一個本地目錄,就是說我的定義聲明是在這個地方

這個 errors.proto 文件其實就列舉了各種錯誤碼,或者叫錯誤的字符串,我們其實更建議大家用字符串,更靈活,因為一個數字沒有寫文檔前你根本不知道它是干啥的,如果我用字符串的話,我可以 user_not_found 告訴你是用戶找不到,但是我告訴你它是3548,你根本不知道它是什么含義,如果我沒寫文檔的話

所以我們建議使用 Protobuf Enum 來定義錯誤的內容信息,定義是在這個地方,但是生成的代碼,按照 DDD 的戰術設計,屬于 Domain,因為業務設計是屬于領域的一個東西,Domain 里面 exception 它最終的源碼會在哪?會在 biz 的大目錄下,biz 是 business 的縮寫,就是在業務的目錄下,舉個例子,你可以放在 biz 的 errors 目錄下

有了這個認知之后我們會做三個事情

首先你的 biz 目錄維護的是領域邏輯,你的領域邏輯可以直接依賴 biz.errors 這個目錄,因為你會拋一些業務錯誤出去

第二,我們的 data 有點像 DDD 的 infrastructure,就是所謂的基礎設施,它依賴并實現了 biz 的 repository 和 acl,repository 就是我們所謂的倉庫,acl 是防腐層

因為我們之前講過它的整個依賴倒置的玩法,就是讓我們的 data 去依賴 biz,最終讓我們的 biz 零依賴,它不依賴任何人,也不依賴基礎設施,它把 repository 和 acl 的接口定義放在 biz 自己目錄下,然后讓 data 依賴并實現它

也就是說最終我這個 data 目錄也可以依賴 biz 的 errors,我可能通過查 MySQL,結果這個東西查不到,會返回一個 sql no rows,但肯定不會返回這個錯誤,那我就可以用依賴 biz 的這個 errors number,比如說 user_not_found,我把它包一個 error 拋出去,所以它可以依賴 biz 的 errors

目前 Kratos 還不支持根因保存,根因保存是什么呢?剛剛說了你可能是 mysql 報了一個內部的錯誤,這個內部錯誤你實際上在最上層的傳輸框架,就是 HTTP 和 grpc 的 middleware 里面,你可能會把日志打出來,就要把堆棧信息打出來,那么根因保存就是告訴你最底層發生的錯誤是什么

不支持 Unwrap 就是不支持遞歸找根因,如果支持根因以后呢,就可以讓 Kratos errors 這個 package 可以把根因傳進去,這樣子既能搞定我們 go 的 wrap errors,同時又支持我們的狀態碼和 reason,大類錯誤和小類錯誤,大類錯誤就是狀態碼,小類錯誤就是我剛剛說的用 enum 定義的具體信息,比方說這個商品被下架,這種就不太好去映射一個具體的錯誤碼,你可能是返回一個500,再帶上一個 reason,可能是這樣的一個做法

Proto Errors:Client

從 Client 消費端只能看到 api.proto 和 error.proto 文件,相應的生成的代碼,就是調用測的 api 以及 errors enum 定義

  • 使用 Kratos errors.As() 拿到具體類型,然后通過 Reason 字段進行判定;
  • 使用 Kratos errors.Reason() helper 方法(內部依賴 errors.As)快速判定;

拿到這兩個文件之后你可以生成相應代碼,然后調用 api

API 工程化分享

 

舉個例子,圖中的代碼是調用服務端 grpc 的某一個方法,那么我可能返回一個錯誤,我們可以用 Kratos 提供的一個 Reason 的 short car,一個快捷的方法,然后把 error 傳進去,實際上在內部他會調用標準庫的 error.As,把它強制轉換成 Kratos 的 errors 類型,然后拿到里面的 Reason 的字段,然后再跟這個枚舉值判定,這樣你就可以判定它是不是具體的一個業務錯誤

第二種寫法你可以拿到原始的我們 Kratos 的 Error 模型,就是以下這個模型

API 工程化分享

 

new 出來之后用標準庫的 errors.As 轉換出來,轉換出來之后再用 switch 獲取它里面的 reason 字段,然后可以寫一些業務邏輯

這樣你的 client 代碼跨語言,跨傳輸,跨協議,無論是 grpc,http,同樣是用一樣的方式去解決

IDL Docs

  • Proto Docs

Proto Docs

基于 openapi 插件 + IDL Protobuf 注釋(IDL 即定義,IDL 即代碼,IDL 即文檔),最終可以在 Makefile 中使用 make api 生成 openapi.yaml,可以在 gitlab/vscode 插件直接查看

  • API Metadata 元信息用于微服務治理、調試、測試等;

因為我們可以在 IDL 文件上面寫上大量的注釋,那么當講到這個地方,你就明白了 IDL 有什么樣的好處?

IDL 文件它既定義,同時又是代碼,也就是說你既做了聲明,然后使用 protoc 可以去生成代碼,并且是跨語言的代碼,同時 IDL 本身既文檔,也就是說它才真正滿足了 one source of truth,就是唯一的事實標準

最終你可以在 Makefile 中定義一個 api 指令,然后生成一個 openapi.yaml,以前是 swagger json,現在叫 openapi,用 yaml 聲明

API 工程化分享

 

生成 yaml 文件以后,現在 gitlab 直接支持 openapi.yaml 文件,所以你可以直接打開 gitlab 去點開它,就能看到這樣炫酷的 UI,然后 VSCode 也有一個插件,你可以直接去查看

還有一個很關鍵的點,我們現在的 IDL 既是定義,又是代碼,又是文檔,其實 IDL 還有一個核心作用,這個定義表示它是一個元信息,是一個元數據,最終這個 API 的 mate data 元信息它可以用于大量的微服務治理

因為你要治理的時候你比方說對每個服務的某個接口進行路由,進行熔斷進行限流,這些元信息是哪來的?我們知道以前 dubbo 2.x,3.x 之前都是把這些元信息注冊到注冊中心的,導致整個數據中心的存儲爆炸,那么元信息在哪?

我們想一想為什么 protobuf 是定義一個文件,然后序列化之后它比 json 要???因為它不是自描述的,它的定義和序列化是分開的,就是原始的 payload 是沒有任何的定義信息的,所以它可以高度的compressed,可被壓縮,或者說叫更緊湊

所以說同樣的道理,IDL 的定義和它的元信息,和生成代碼是分開的話,意味著你只要有 one source of truth 這份唯一的 pb 文件,基于這個 pb 文件,你就有辦法把它做成一個 api 的 metadata 的服務,你就可以用于做微服務的治理

你可以選一個服務,然后看它有些什么接口,然后你可以通過一個管控面去做熔斷、限流等功能,然后你還可以基于這個元信息去調試,你做個炫酷的 UI 可以讓它有一些參數,甚至你可以寫一些擴展,比方說這個字段叫 etc,建議它是什么樣的值,那么你在渲染 UI 的時候可以把默認值填進去,那你就很方便做一些調試,甚至包含測試,你基于這個 api 去生成大量的 test case

參考

API 工程化分享

https://www.bilibili.com/video/BV17m4y1f7qc/

接口定義語言

https://docs.microsoft.com/zh-cn/dotnet/architecture/grpc-for-wcf-developers/interface-definition-language

真是頭疼,Proto 代碼到底放哪里?

https://mp.weixin.qq.com/s/cBXZjg_R8MLFDJyFtpjVVQ

git submodules
https://git-scm.com/book/en/v2/Git-Tools-Submodules

kratos
https://github.com/go-kratos/kratos

error_details.proto
https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto#L112

pkg/errors
https://github.com/pkg/errors

Modifying gRPC Services over Time

分享到:
標簽:API
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定