前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >腾讯 tRPC-Go 教学——(3)微服务间调用

腾讯 tRPC-Go 教学——(3)微服务间调用

原创
作者头像
amc
修改2024-05-01 13:23:27
5680
修改2024-05-01 13:23:27
举报
文章被收录于专栏:后台全栈之路后台全栈之路

前两篇文章(12),我构建了一个简单的 HTTP 服务。 HTTP 服务是前后端分离架构中,后端最靠近前端的业务服务。不过纯后台 RPC 之间,出于效率、性能、韵味等等考虑,HTTP 不是我们的首选。本文我们就来看看腾讯是怎么使用 tRPG-Go 构建后台微服务集群的。

本文我们将开始涉及 tRPC 的核心关键点之一:

  • tRPC 服务之间如何互相调用

系列文章

制订协议

与 HTTP 一样,我们还是先制订协议。我们先简单设计一下我们要做的一个服务吧:

  1. 一个前端 HTTP 服务,对接前端
    • 提供一个登录接口, 用于用户名密码(哈希)登录,如果登录成功,给前端返回一个 JWT token,作为身份验证票据
    • JWT token 的生成逻辑在该服务中实现
  2. 一个后端服务, 内部调用,提供用户及认证功能
    • 本文中这个服务实际实现用户名密码验证的功能。

HTTP 服务协议

HTTP 协议比较简单,参照之前的文章格式,我们这么定义

代码语言:proto
复制
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 规则:

代码语言:makefile
复制
.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

注意其中最长的那一句

代码语言:makefile
复制
		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 搜索目录。

后端服务协议

后端的服务协议,目前我们先针对这个简单的登录功能,设计一个获取用户帐户数据的功能吧:

代码语言:proto
复制
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 实现代码 吧。在代码中,我列出了一个最简单的方法:

代码语言:go
复制
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
}

要说明问题的核心代码,就只有一行:

代码语言:go
复制
	uRsp, err := user.NewUserClientProxy().GetAccountByUserName(ctx, uReq)

什么 client 初始化,通通不需要。如果下游是一个 tRPC 服务,那么我们只需要在使用的时候再 new 就可以了,这个开销非常低。

服务部署

读者读到上一小节肯定会非常疑惑:啊?代码怎么寻址下游服务的?这一小节我就先尝试着初步解答你的问题。

我们还是像最开始我们的 hello world 服务一样,看看这个 httpauth 服务启动时所需的 trpc_go.yaml 文件 吧:

可以看到,除了之前 hello world 服务给出的例子之外,yaml 文件中多了这一项:

代码语言: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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 系列文章
  • 制订协议
    • HTTP 服务协议
      • 后端服务协议
      • 逻辑开发
        • tRPC 服务间调用
          • 服务部署
          • 下一步
          相关产品与服务
          云数据库 MySQL
          腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档