騰訊 tRPC-Go 教學——(4)tRPC 元件生態和使用

發表於2024-02-11

之前我花了三篇文章來介紹 tRPC 怎麼用。而 tRPC 給開發者帶來的便利, 在整整三篇文章中,我也只是介紹了它可以方便服務在 HTTP、trpc、grpc 三種協議之間靈活切換。誠然, tRPC 作為能夠統一騰訊內開發框架的一個生態級產品,它的能力顯然不止這些。這一篇文章,我們們來一起初窺 tRPC 的周邊生態有哪些, 以及其中的第三方元件使用方法。


系列文章


tRPC-ecosystem 介紹

tRPC 的主倉庫是 trpc-group,在這之外,tRPC 的周邊生態系統則放在 trpc-echosystem。這個組主要包含的事 tRPC 服務之外的周邊生態程式碼, 從分類上,主要包括以下這些方面:

  • 名字服務(naming)和定址器(selector)
  • 配置(config)
  • 日誌(log)
  • 指標上報(metrics)
  • 攔截器(filter)
  • 第三方軟體元件

本文所說的 “第三方軟體元件”,指的是諸如 MySQL、Redis、ElasticSearch 等軟體元件客戶端 Go 實現。在 tRPC 生態系統組下的 go-database 倉庫,我們開啟倉庫,就可以看到這些熟悉的名稱:

資料庫開源庫封裝
bigcachebigcache
clickhouseclickhouse-go
cos騰訊雲物件儲存
goredisgo-redis
gormgorm
hbasegohbase
kafkasarama
mysqlsqlx
timer本地/分散式定時器

tRPC 把各種元件整合到生態中來,主要目的是為了在維持這些開源庫的使用習慣的基礎上,同時 複用 tRPC 的各種能力,比如路由定址、監控上報等等功能。

本文我們就從 上一篇文章 定義的 user 服務來看, 如何引入 trpc-database 的 MySQL。


邏輯設計

在前文中,我們給 user 服務只設計了一個介面 GetAccountByUserName,這個介面的功能,簡單而言就是根據入參,從資料庫中撈取指定的使用者資訊。

實體和介面設計

我們採用自頂向下的設計模式,從上層所需的介面往下設計。tRPC 業務程式碼 GetAccountByUserName 所在的層,我們稱為 service 層(有些框架稱為 handler 層、介面層、服務層等)。

這個介面的邏輯很簡單,由於太簡單了,因此我們不需要常規的多一個 logic 層封裝, 而是直接提供一個 repo 層實現就可以,從這個實現中直接根據 username 獲取帳戶資訊(repo 層有些框架稱為 infrastructure 層)。我們簡單設計一下需要傳輸的實體定義:

// Account 表示一個帳戶資訊
type Account struct {
    Username     string
    PasswordHash string
}

至於這個 repo 層依賴, 我們以一個 Dependency 型別定義出來:

// Dependency 表示使用者服務初始化依賴
type Dependency interface {
    // QueryAccountByUsername 透過使用者名稱查詢帳戶資訊, 如果帳戶不存在則返回 (nil, nil)
    QueryAccountByUsername(ctx context.Context, username string) (*entity.Account, error)
}

type userImpl struct {
    dep Dependency
}

可以看到,我把 Dependency 定義為一個 interface, 當然也可以定義為其他的型別,總之是任意方便用於注入測試的一個型別。在以後的文章中,我會說明注入模式的好處。

然後,我們使用這個 Dependency 作為引數, 用於初始化 user 主服務:

// RegisterUserService 註冊使用者服務
func RegisterUserService(s server.Service, d Dependency) error {
    impl := &userImpl{dep: d}
    user.RegisterUserService(s, impl)
    return nil
}

至於 impl 的 GetAccountByUserName 方法實現, 太顯而易見了,就不列在本文裡了,讀者可以自行查閱程式碼細節。

MySQL 表結構

接下來我們實現在 service 層中依賴的 repo 介面。我們先設計一下 MySQL 的表結構:

CREATE TABLE IF NOT EXISTS `t_trpc_demo_user_account` (
    `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵 ID',
    `username` varchar(128) NOT NULL COMMENT '使用者名稱稱',
    `password_hash` varchar(64) NOT NULL COMMENT '使用者密碼雜湊值',
    `create_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',
    `update_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
    `delete_at_ms` bigint(11) NOT NULL DEFAULT 0 COMMENT '刪除時間戳, 毫秒',
    PRIMARY KEY (`id`),
    KEY `i_username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='使用者賬戶表';

在 Go 裡面,我們並不需要定義所有的欄位,而是我們所需要的欄位就可以了:

type userAccountItem struct {
    ID           int64  `db:"id"`
    Username     string `db:"username"`
    PasswordHash string `db:"password_hash"`
}

repo 層實現

在 repo/account 的程式碼中,可以寫得與 tRPC 一點關係也沒有,我們使用 sqlx 來實現對資料的存取, 然而,我使用注入的方式, 我們讓呼叫方傳入一個 sqlx DB 的 getter:

// UserAccountRepository 使用者賬戶倉庫
type UserAccountRepository struct {
    dep Dependency
}

// Dependency 表示使用者賬戶倉庫初始化依賴
type Dependency struct {
    DBGetter func(context.Context) (mysql.Client, error)
}

// InitializeUserAccountRepository 初始化使用者賬戶倉庫
func (r *UserAccountRepository) InitializeUserAccountRepository(d Dependency) error {
    r.dep = d
    return nil
}

這個倉庫的真正實現中,我們並沒有直接使用 sqlx 型別,而是使用了 trpc,主要的就是對 *sqlx.DB 追加了一層 context 封裝。


服務部署

好了,現在兩個服務的程式碼都寫好了。這個時候我們就要開始啟動這個由兩個服務的 “叢集” 了。

MySQL

咳咳,其實 MySQL 不屬於 trpc 框架裡的。生產環境中,我們的 MySQL 一般是雲上購買的資料庫。從學習的角度,我用的是 Mac 進行開發,Docker 用來開發除錯,我的 MySQL 也是部署在 Mac Docker 下,3306 埠,使用者名稱 root,密碼 123456,資料庫名稱 db_test

user 服務

user 服務對外提供一個 trpc 協議的介面;同時,它也依賴一個 MySQL。一如既往地,程式碼的邏輯其實很簡單,最主要的、我們來看看這個 MySQL 服務的 啟動配置:

server:
  service:
    - name: demo.account.User
      ip: 127.0.0.1
      # nic: eth0
      port: 8002
      network: tcp
      protocol: trpc
      timeout: 1800

client:
  service:
    - name: db.mysql.userAccount
      target: ip://root:123456@tcp(host.docker.internal:3306)/db_test?charset=utf8mb4&parseTime=true&loc=Local&timeout=1s
      timeout: 1000

先看 server 部份,為了便於除錯,我直接監聽環回地址 127.0.0.1,協議配置的也是 trpc,而不再是之前文章中慣用的 http。

然後我們們來看新的一個配置項—— client:client 配置和 server 有點像,也是一個 service 陣列。這個配置中定義了服務對下游各種依賴的定址方法和相關配置。上面的配置中,最顯眼的就是 target 引數了。這個引數規定了如何定址指定的下游服務,以及相關的引數。

可以看到 DB 的地址是: host.docker.internal,因為我的服務執行在 Docker 容器中,得使用 host.docker.internal 才可以訪問主機的埠。

在 tRPC 的 selector(定址器)邏輯中,我們之前提過,框架預設註冊了 ip 這個 selector,因此我們這裡複用了這個功能。此外,selector 配置中剩餘的引數,則會被傳遞到下游元件實現中。同樣的邏輯,我們無需修改業務程式碼的實現,就可以透過 client 配置修改下游的依賴。

http 服務

有了前面我們的描述,針對 http 服務,我們就可以輕車熟路了。http 服務(全名為 http-auth-server)的 啟動配置 如下:

server:
  service:
    - name: demo.httpauth.Auth
      nic: eth0
      port: 8001
      network: tcp
      protocol: http
      timeout: 1800

client:
  service:
    - name: demo.account.User
      target: ip://127.0.0.1:8002
      network: tcp
      protocol: trpc
      timeout: 1000

除錯

我們啟動兩個終端,分別進入兩個程式碼目錄中,分別啟動兩個服務:

cd app/user/; go run . -conf conf/trpc_go.yaml
cd app/http-auth-server/; go run . -conf conf/trpc_go.yaml

然後我們再開啟一個終端,使用命令除錯一下我們的介面:

curl '172.17.0.6:8001/demo/auth/Login?username=amc'

可以獲得返回:

{"err_code":404,"err_msg":"使用者不存在","data":null}

這就說明邏輯透過,這個 404 是我在 程式碼中 寫的當查詢不到使用者名稱的返回資訊。

OK,那我們往資料庫中插入一個條目吧(正常情況下應該是透過頁面建立的)

INSERT INTO t_trpc_demo_user_account (`username`, `password_hash`) VALUES ('amc', '75c498407830cb766fb20d619f3e08280ad7c5b9')

其中 75c498407830cb766fb20d619f3e08280ad7c5b9 就是 123456 的 sha256 值。這個時候我們再執行一下 curl 命令,則可以得到返回:

{"err_code":404,"err_msg":"密碼錯誤","data":null}

哎,使用者找到了,但是密碼錯誤。所以我們最後,再將程式碼帶上:

curl '172.17.0.6:8001/demo/auth/Login?username=amc&password_hash=8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92'

此時的返回就是:

{"err_code":0,"err_msg":"success","data":null}

成功了~邏輯也算是自測 OK 啦~~


小結

至此,我們使用四篇簡短的小文章,介紹瞭如何搭建一個最基本的 tRPC 微服務叢集,這個叢集包含了以下內容:

  1. 一個對前端的 HTTP API 服務
  2. 一個純後端服務
  3. 可配置化的服務配置和服務發現

讀者看完這四篇文章之後,其實就已經掌握了所有使用 tRPC 提供服務的最基本功能了。至少,筆者自己搭建的私人 web 服務的 API,也就只用到了這些知識點。

然而,要部署一個真正完整的、擁有良好可觀測性的服務叢集,我們還需要學習和使用更多 tRPC 的知識。下一步,我們來介紹一下 tRPC 日誌功能的實現吧。


本文章採用 知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議 進行許可。

原作者: amc,原文釋出於騰訊雲開發者社群,也是本人的部落格。歡迎轉載,但請註明出處。

原文標題:《手把手騰訊 tRPC-Go 教學——(4)tRPC 元件生態和使用》

釋出日期:2024-02-05

原文連結:https://cloud.tencent.com/developer/article/2387742

CC BY-NC-SA 4.0 DEED.png