動態

詳情 返回 返回

騰訊 tRPC-Go 教學——(3)微服務間調用 - 動態 詳情

前兩篇文章(1、2),我構建了一個簡單的 HTTP 服務。 HTTP 服務是前後端分離架構中,後端最靠近前端的業務服務。不過純後台 RPC 之間,出於效率、性能、韻味等等考慮,HTTP 不是我們的首選。本文我們就來看看騰訊是怎麼使用 tRPG-Go 構建後台微服務集羣的。

本文我們將開始涉及 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 服務

制訂協議

與 HTTP 一樣,我們還是先制訂協議。我們先簡單設計一下我們要做的一個服務吧:

  1. 一個前端 HTTP 服務,對接前端

    • 提供一個登錄接口, 用於用户名密碼(哈希)登錄,如果登錄成功,給前端返回一個 JWT token,作為身份驗證票據
    • JWT token 的生成邏輯在該服務中實現
  2. 一個後端服務, 內部調用,提供用户及認證功能

    • 本文中這個服務實際實現用户名密碼驗證的功能。

HTTP 服務協議

HTTP 協議比較簡單,參照之前的文章格式,我們這麼定義:

import "common/metadata.proto";

message LoginRequest {
    common.Metadata metadata = 1;
    string username      = 2;
    string password_hash = 3;
}

message LoginResponse {
    int32  err_code = 1;
    string err_msg  = 2;
    Data   data     = 3;

    message Data {
        string id_ticket = 1;
    }
}

// Auth 提供 HTTP 認證接口
service Auth {
    rpc Login(LoginRequest) returns (LoginResponse); // @alias=/demo/auth/Login
}

這裏我使用到了 protoc 的跨目錄 import 特性。這就需要在 trpc create 命令中追加參數指定 import 的搜索路徑。各位可以看一下我的 Makefile 中的 pb 規則:

.PHONY: $(PB_DIR_TGTS)
$(PB_DIR_TGTS):
    @for dir in $(subst _PB,, $@); do \
        echo Now Build proto in directory: $$dir; \
        cd $$dir; rm -rf mock; \
        export PATH=$(PATH); \
        rm -f *.pb.go; rm -f *.trpc.go; \
        find . -name '*.proto' | xargs -I DD \
            trpc create -f --protofile=DD --protocol=trpc --rpconly --nogomod --alias --mock=false --protodir=$(WORK_DIR)/proto; \
        ls *.trpc.go | xargs -I DD mockgen -source=DD -destination=mock/DD -package=mock ; \
        find `pwd` -name '*.pb.go'; \
    done

注意其中最長的那一句

        find . -name '*.proto' | xargs -I DD \
            trpc create -f --protofile=DD --protocol=trpc --rpconly --nogomod --alias --mock=false --protodir=$(WORK_DIR)/proto; \

這裏通過 --protodir 指定了在 protoc 時的 import 搜索目錄。

後端服務協議

後端的服務協議,目前我們先針對這個簡單的登錄功能,設計一個獲取用户帳户數據的功能吧:

import "common/metadata.proto";

message GetAccountByUserNameRequest {
    common.Metadata metadata = 1;
    string username = 2;
}

message GetAccountByUserNameResponse {
    int32  err_code = 1;
    string err_msg  = 2;

    string user_id  = 3;
    string username = 4;
    string password_hash = 5;
    int64  create_ts_sec = 6;
}

// User 提供用户信息服務
service User {
    rpc GetAccountByUserName(GetAccountByUserNameRequest) returns (GetAccountByUserNameResponse);
}

邏輯很簡單,就是根據用户名稱,獲取一個用户信息。我們也可以約定一下,如果沒有用户信息,那麼就在 err_msg 中返回一個錯誤信息。

邏輯開發

tRPC 服務間調用

還記得前面説到的兩個關鍵點嗎?我們先來講第一個:tRPC 服務間調用

前面我們規劃了兩個服務,一個主要對外提供 HTTP 接口,直接對接前端;另外一個服務不對前端開放,這種情況下我們可以使用 trpc 協議。這個協議其實與 grpc 非常相似,也使用了 HTTP/2 的各種機制。

這兩個服務互相調用的場景下,HTTP(httpauth 服務)是上游主調方,另一個微服務(user 服務)則是下游被調方。作為被調方,服務的撰寫方式與我們最早介紹的 tRPC 服務創建沒什麼差異,因為在 tRPC 框架下,我們撰寫服務邏輯的時候可以無需關注編碼格式。

作為主調方的服務,如何獲取入參、輸出出參,在之前的文章中我們已經知道該怎麼做了。接下來我們要關注的是如何調用下游。

我們先看看 httpauth 服務的 Login 實現代碼 吧。在代碼中,我列出了一個最簡單的方法:

func (authServiceImpl) Login(
    ctx context.Context, req *httpauth.LoginRequest,
) (rsp *httpauth.LoginResponse, err error) {
    rsp = &httpauth.LoginResponse{}
    uReq := &user.GetAccountByUserNameRequest{
        Metadata: req.GetMetadata(),
        Username: req.GetUsername(),
    }
    uRsp, err := user.NewUserClientProxy().GetAccountByUserName(ctx, uReq)
    if err != nil {
        log.ErrorContextf(ctx, "調用 user 服務失敗: %v", err)
        return nil, err
    }
    // 用户存在與否
    if uRsp.GetErrCode() != 0 {
        rsp.ErrCode, rsp.ErrMsg = uRsp.GetErrCode(), uRsp.GetErrMsg()
        return
    }

    // 密碼檢查
    if uRsp.GetPasswordHash() != req.PasswordHash {
        rsp.ErrCode, rsp.ErrMsg = 404, "密碼錯誤"
        return
    }
    return
}

要説明問題的核心代碼,就只有一行:

    uRsp, err := user.NewUserClientProxy().GetAccountByUserName(ctx, uReq)

什麼 client 初始化,通通不需要。如果下游是一個 tRPC 服務,那麼我們只需要在使用的時候再 new 就可以了,這個開銷非常低。

服務部署

讀者讀到上一小節肯定會非常疑惑:啊?代碼怎麼尋址下游服務的?這一小節我就先嚐試着初步解答你的問題。

我們還是像最開始我們的 hello world 服務一樣,看看這個 httpauth 服務啓動時所需的 trpc_go.yaml 文件 吧:

可以看到,除了之前 hello world 服務給出的例子之外,yaml 文件中多了這一項:

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

這一部份規定了在服務中的各種 tRPC 下游依賴的尋址方式。跟服務側一樣,我我這裏也建議讀者參照 pb 中定義的服務名來給 name 字段賦值(demo.account.User)。

protocol 字段的值是 trpc,這表示我們使用 trpc 協議來調用下游。這一點我們需要與下游協商好,因為即便同是 tRPC 服務,如果 server 和 client 側沒有指定好相同的 protocol 字段,那麼雙方的通信將會失敗。

相比起 server 的配置有 portnicport 等字段,client 並沒有這些,取而代之的是一個 target 字段。目前的例子中,配置的值為:ip://127.0.0.1:8002。這個配置包含兩部份,也就是 ip://127.0.0.1:8002

其中前面的 ip 表示告訴 tRPC 框架,client 將使用一個被註冊為叫做 ip 的尋址器(在 tRPC 中稱作 “selector”),尋址器的參數是 127.0.0.1:8002ip 是 tRPC 內置的尋址器,邏輯也很簡單,根據後面的 IP + 端口進行尋址。此外,tRPC 還支持 dns 尋址,在這個尋址器下,如果 port 部份是 443,並且 protocol 為 http,那麼tRPC 會自動使用 https 調用。

當然,在正式生產環境下,我們的服務間很少直接使用 ip 尋址器進行服務發現。在後文我會介紹一下我們實際使用的 “北極星” 名字服務系統。此處讀者先知道尋址器功能即可,咱們先把服務打通,然後再來講更進階的事情。

下一步

本文我們説明了從一個 tRPC 服務,如何調用另一個 tRPC 服務。下一篇文章我們從那個被調用的 tRPC 服務來介紹,如何把諸如 MySQL、Redis、Kafka 等組件也接入 tRPC 框架中。


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

原作者: amc,原文發佈於騰訊雲開發者社區,也是本人的博客。歡迎轉載,但請註明出處。

原文標題:《手把手 tRPC-Go 教學——(3)微服務間調用》

發佈日期:2024-01-29

原文鏈接:https://cloud.tencent.com/developer/article/2384591。

CC BY-NC-SA 4.0 DEED.png

user avatar ting_61d6d9790dee8 頭像 shumile_5f6954c414184 頭像 eolink 頭像 yuhuashi_584a46acea21f 頭像 cbuc 頭像 zbooksea 頭像 baqideyaling 頭像 emanjusaka 頭像 god23bin 頭像 mangrandechangjinglu 頭像 best_6455a509a2177 頭像 hushuosha 頭像
點贊 24 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.