動態

詳情 返回 返回

騰訊 tRPC-Go 教學——(4)tRPC 組件生態和使用 - 動態 詳情

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


系列文章

  • 騰訊 tRPC-Go 教學——(1)搭建服務
  • 騰訊 tRPC-Go 教學——(2)trpc HTTP 能力
  • 騰訊 tRPC-Go 教學——(3)微服務間調用
  • 騰訊 tRPC-Go 教學——(4)tRPC 組件生態和使用
  • 騰訊 tRPC-Go 教學——(5)filter、context 和日誌組件
  • 騰訊 tRPC-Go 教學——(6)服務發現
  • 騰訊 tRPC-Go 教學——(7)服務配置和指標上報
  • 騰訊 tRPC-Go 教學——(8)通過泛 HTTP 能力實現和觀測 MCP 服務

tRPC-ecosystem 介紹

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

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

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

數據庫 開源庫封裝
bigcache bigcache
clickhouse clickhouse-go
cos 騰訊雲對象存儲
goredis go-redis
gorm gorm
hbase gohbase
kafka sarama
mysql sqlx
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

user avatar tongbo 頭像 tssc 頭像 aitibao_shichangyingxiao 頭像 java_study 頭像 deephub 頭像 kubesphere 頭像 kuaidi100api 頭像 yaochujiadetiebanshao 頭像 wuliaodeqie 頭像 birenxuemou 頭像 liberhome 頭像 huobaodejianpan 頭像
點贊 14 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.