tRPC-GO 框架Helloworld实践初体验

目录

引言

记得是2023年RPC框架tRPC正式开源。笔者就对tRPC-GO框架来简单实践体验一下这个框架的使用,相当于入门敲门砖,迈进大门的第一步。

一、tRPC-Go架构

tRPC-Go,是 tRPC 的 Go 语言实现,它是一个可插拔的高性能 RPC 框架。

tRPC-Go的整理架构说明可以在开源仓库这里找到。

事实上关于RPC的原理都大同小异,接口规范、序列化、通信协议,就这么大点事。框架细节原理可以查看源码,从桩文件开始回溯代码,不是本文重点不赘述。

二、tRPC-GO 实践Helloworld

通常情况下也没有一个固定的顺序,先生成协议和生成桩代码或者先写服务逻辑实现再生成桩代码都没关系。

1、trpc工具链安装/trpc命令行工具

Go, 版本应该大于等于 go1.18。

tRPC 命令行工具, 用于从 protobuf 生成 Go 桩代码。

安装trpc-cmdline

trpc-cmdline 是 trpc-cpp 和 trpc-go 的命令行工具。

首先将以下内容添加到你的 ~/.gitconfig 中:

bash 复制代码
[url "ssh://git@github.com/"]
    insteadOf = https://github.com/

然后执行以下命令以安装 trpc-cmdline:

bash 复制代码
go install trpc.group/trpc-go/trpc-cmdline/trpc@latest

安装依赖

参考官方这里的教程。

bash 复制代码
# 一键安装
trpc setup

bash 复制代码
#手动安装各种依赖的列表(具体执行命令参考官方文档)
Install protoc # trpc 工具是基于 protoc 实现的
Install flatc
Install protoc-gen-go
Install goimports
Install mockgen
Install protoc-gen-validate and protoc-gen-validate-go

当我们由于各种环境原因一键安装报错时,手动安装可以作为参考。

我们注意到protoc 和protoc-gen-go,是比较重要的。

2、接口规范/制定协议

准备工作做好了之后我们来先编辑一下接口规范,或者也可以叫做制定协议。

以官方为例做些修改。

go 复制代码
syntax = "proto3";
package helloworld;

option go_package = "github.com/some-repo/examples/helloworld";

// HelloRequest is hello request.
message HelloRequest {
  string greeting = 1;;//参数greeting
}

// HelloResponse is hello response.
message HelloResponse {
  int32  err_code  = 1;
  string err_msg   = 2;
  string response  = 3;
  double timestamp = 4;//参数timestamp 
  
}

// HelloWorldService handles hello request and echo message.
service HelloWorldService {//服务
  // Hello says hello.
  rpc Hello(HelloRequest) returns(HelloResponse);// 方法Hello,别名使用 @alias=/demo/Hello
}

我们定义了一个简单的服务。接下来,我们需要把这个服务转换成 trpc 桩代码。为此,我们需要借助 trpc 的一个工具来实现。

3、生成代码桩

桩代码主要分为 client 和 server 两部分。

但协议是只要写一份哈。

这里主要是 trpc-cmdline中trpc create 命令的使用,可以运行 trpc -h 以及 trpc create -h 来进行查看。

bash 复制代码
-f: 用于强制覆盖输出目录中的内容
-d some-dir: 添加 proto 文件的查找路径(包括依赖的 proto 文件),可以指定多次
--mock=false: 禁止生成 mock 代码
--nogomod=true: 在生成桩代码时不生成 go.mod 文件,只在 --rpconly=true 的时候生效, 默认为 false
-l cpp:生成 cpp 桩代码
--validate=true: 开启数据校验

比如使用 trpc-cmdline 来生成完整项目:

bash 复制代码
trpc create -p helloworld.proto -o out
# 注意: -p 用于指定 proto 文件, -o 用于指定输出目录, 更多 flag 信息可以运行 trpc -h 以及 trpc create -h 来进行查看。

目录详情:

bash 复制代码
$ tree
.
|-- cmd
|   `-- client
|       `-- main.go  # Generated client code.
|-- go.mod
|-- go.sum
|-- hello_world_service.go  # Generated server service implementation.
|-- hello_world_service_test.go
|-- main.go  # Server entrypoint.
|-- stub  # Stub code.
|   `-- github.com
|       `-- some-repo
|           `-- examples
|               `-- helloworld
|                   |-- go.mod
|                   |-- helloworld.pb.go #命令工具生成
|                   |-- helloworld.proto #手动编写接口规范
|                   |-- helloworld.trpc.go #命令工具生成
|                   `-- helloworld_mock.go #命令工具生成
`-- trpc_go.yaml  # Configuration file for trpc-go.

或者仅生成桩代码:

bash 复制代码
trpc create -p helloworld.proto -o out --rpconly

目录详情:

bash 复制代码
tree out
out
|-- go.mod
|-- go.sum
|-- helloworld.pb.go
|-- helloworld.trpc.go
`-- helloworld_mock.go

通常情况下,如生成helloworld.pb.go 和 helloworld.trpc.go 两个文件是主要核心。命令如下:

bash 复制代码
trpc create -f --protofile=helloworld.proto --protocol=trpc --rpconly --nogomod --mock=false

此时其实接口规范已经有了(虽然业务逻辑还没写),前端同学拿到接口规范就可以MOCK了。

若接口协议有变更,必须重新生成桩代码文件。

4、编写业务逻辑

go 复制代码
package main

import (
    "context"
    "fmt"
    "time"

    // proto package 的路径请读者自行调整
    "github.com/some-repo/examples/helloworld"
    "trpc.group/trpc-go/trpc-go"
)

func main() {
    s := trpc.NewServer()
    simplest.RegisterHelloWorldService(s, helloWorldImpl{})
    _ = s.Serve()
}

type helloWorldImpl struct{}

func (helloWorldImpl) Hello(ctx context.Context, req *simplest.HelloRequest) (*simplest.HelloResponse, error) {
    rsp := &simplest.HelloResponse{}
    rsp.Response = fmt.Sprintf("%s to you, too", req.Greeting)
    rsp.TimestampMsec = time.Now().UnixMilli()
    return rsp, nil
}

查看helloworld.trpc.go 文件部分代码:

go 复制代码
type HelloWorldService interface {
    Hello(ctx context.Context, req *HelloRequest) (*HelloResponse, error) // @alias=/demo/Hello
}

// ......

func RegisterHelloWorldService(s server.Service, svr HelloWorldService) {
    if err := s.Register(&HelloWorldServer_ServiceDesc, svr); err != nil {
        panic(fmt.Sprintf("HelloWorld register error:%v", err))
    }
}

//......
var HelloWorldServer_ServiceDesc = server.ServiceDesc{
    ServiceName: "helloworld.HelloWorldService",
    HandlerType: ((*HelloWorldService)(nil)),
    Methods: []server.Method{
        {
            Name: "/demo/Hello",
            Func: HelloWorldService_Hello_Handler,
        },
        {
            Name: "/helloworld.HelloWorldService/Hello",
            Func: HelloWorldService_Hello_Handler,
        },
    },
}
//trpc 默认是使用 package/method 的格式定义一个接口方法的路径;而 alias 的作用则是在这基础上再额外注册了一个路径。上面的这两个路径,都可以直接通过 http 访问到。

RegisterHelloWorldService 函数将 trpc 服务和我们的具体业务实现绑定在了一起,启动服务就对接上了业务逻辑。

5、启动服务

单纯编译上面的代码之后,还不足以实现一个完整的服务。trpc 服务还需要搭配一个配置文件,这个文件我们通常命名为 trpc_go.yaml,内容为:

bash 复制代码
server:
  service:
    - name: helloworld.HelloWorldService #服务名,一般为proto 中定义的 package 名 + 服务名
      nic: eth0
      # ip: 127.0.0.1
      port: 8000 # 监听端口 8000
      network: tcp # 服务工作在 tcp 协议上,tcp/udp 
      protocol: http #应用层采用 http 协议,trpc/http/grpc
      timeout: 1800 #超时时间是 1800 毫秒

启动服务:

bash 复制代码
go run . -conf conf/trpc_go.yaml

可以看到 trpc 在标准输出输出类似以下文字:

bash 复制代码
2025-11-16 16:15:03.886 INFO    client/client_linux.go:35       client  is empowered with tnet! 🤩 
2025-11-16 16:15:03.887 DEBUG   maxprocs/maxprocs.go:47 maxprocs: Leaving GOMAXPROCS=4: CPU quota undefined
2025-11-16 16:15:03.888 INFO    server/service.go:167   process:19556, http service:demo.simplest.HelloWorld launch success, tcp:172.17.0.4:8000, serving ...

访问响应:

bash 复制代码
curl http://172.17.0.4:8000/demo/Hello?greeting=Morning
{"err_code":0, "err_msg":"", "response":"Morning to you, too", "timestamp":1705243778.669}
bash 复制代码
curl http://172.17.0.4:8000/demo/Hello --header 'Content-Type:application/json'\
-d '{"greeting":"Good afternoon"}'
{"err_code":0, "err_msg":"", "response":"Good afternoon to you, too", "timestamp":1705243811.625}

如果启动的是 trpc服务可以使用客户端工具(不建议)或者自己编写一个trpc客户端代码来访问验证。

6、关于客户端

这个其实就是服务间调用部分的知识了。可以参考官方这里教程。

参考一份桩代码中客户端部分的代码示例:

go 复制代码
//......
// START ======================================= Client Service Definition ======================================= START
// GreeterClientProxy defines service client/ proxy
type GreeterClientProxy interface {
	Hello(ctx context.Context, req *HelloRequest, opts ...client.Option) (rsp *HelloReply, err error)

	Hi(ctx context.Context, req *HelloRequest, opts ...client.Option) (rsp *HelloReply, err error)
}

type GreeterClientProxyImpl struct {
	client client.Client
	opts   []client.Option
}

var NewGreeterClientProxy = func(opts ...client.Option) GreeterClientProxy {
	return &GreeterClientProxyImpl{client: client.DefaultClient, opts: opts}
}

func (c *GreeterClientProxyImpl) Hello(ctx context.Context, req *HelloRequest, opts ...client.Option) (*HelloReply, error) {
	ctx, msg := codec.WithCloneMessage(ctx)
	defer codec.PutBackMessage(msg)
	msg.WithClientRPCName("/trpc.examples.helloworld.Greeter/Hello")
	msg.WithCalleeServiceName(GreeterServer_ServiceDesc.ServiceName)
	msg.WithCalleeApp("examples")
	msg.WithCalleeServer("helloworld")
	msg.WithCalleeService("Greeter")
	msg.WithCalleeMethod("Hello")
	msg.WithSerializationType(codec.SerializationTypePB)
	callopts := make([]client.Option, 0, len(c.opts)+len(opts))
	callopts = append(callopts, c.opts...)
	callopts = append(callopts, opts...)
	rsp := &HelloReply{}
	if err := c.client.Invoke(ctx, req, rsp, callopts...); err != nil {
		return nil, err
	}
	return rsp, nil
}

func (c *GreeterClientProxyImpl) Hi(ctx context.Context, req *HelloRequest, opts ...client.Option) (*HelloReply, error) {
	ctx, msg := codec.WithCloneMessage(ctx)
	defer codec.PutBackMessage(msg)
	msg.WithClientRPCName("/trpc.examples.helloworld.Greeter/Hi")
	msg.WithCalleeServiceName(GreeterServer_ServiceDesc.ServiceName)
	msg.WithCalleeApp("examples")
	msg.WithCalleeServer("helloworld")
	msg.WithCalleeService("Greeter")
	msg.WithCalleeMethod("Hi")
	msg.WithSerializationType(codec.SerializationTypePB)
	callopts := make([]client.Option, 0, len(c.opts)+len(opts))
	callopts = append(callopts, c.opts...)
	callopts = append(callopts, opts...)
	rsp := &HelloReply{}
	if err := c.client.Invoke(ctx, req, rsp, callopts...); err != nil {
		return nil, err
	}
	return rsp, nil
}

// END ======================================= Client Service Definition ======================================= END

编写客户端逻辑代码和yaml文件并启动run,访问客户端服务就可以通过客户端trpc调用到trpc服务方了。

客户端部分逻辑代码:

go 复制代码
//......
import (
	//......
	trpc "git.mycode.com/trpc-go/trpc-go"
	pb "git.mycode.com/trpc-go/trpc-go/examples/helloworld/proto"
	//......
)

func main() {
	// load config from trpc_go.yaml
	_ = trpc.NewServer()

	// send trpc request
	c := pb.NewGreeterClientProxy() //trpc调用new client
	rsp, err := c.Hello(trpc.BackgroundContext(), &pb.HelloRequest{Msg: "tRPG-Go-Client"})//trpc调用服务端接口方法
	
	if err != nil {
		log.Error(err)
	} else {
		log.Info(rsp.Msg)
	}
	//......

客户端yaml文件(也可能同时存在于服务端的yaml文件中,即服务端yaml可能同时有客户端服务端配置,客户端yaml仅有客户端配置。比如服务端同时也需要承担客户端去调用其他服务端的时候,就需要客户端yaml配置用于与桩代码配合对下游被调用方的服务寻址):

yaml 复制代码
client:
  service:
    - name: trpc.example.helloworld.trpc_greeter # naming service 名字服务名称(被调用方),用于路由寻址,同上遵循规范
      callee: trpc.examples.helloworld.Greeter  # proto service,用于关联代码和配置
      # 注册到名字服务的话,下面 target 不用配置
      target: ip://127.0.0.1:8000           # 寻址器,后端服务地址,例如:unix://temp.sock
      network: tcp                          # 后端服务的网络类型 tcp udp unix
      protocol: trpc                        # 应用层协议 trpc http,这里trpc表示使用trpc方式来调用下游(需要与下游协商好service协议一致)
      timeout: 800     

client定义了服务中的各种 tRPC 下游依赖的寻址方式。

相比起 server 的配置有 port、nic、port 等字段,client 并没有这些,取而代之的是一个 target 字段。目前的例子中,配置的值为:ip://127.0.0.1:8000。这个配置包含两部份,也就是 ip:// 和 127.0.0.1:8000。

其中前面的 ip 表示告诉 tRPC 框架,client 将使用一个被注册为叫做 ip 的寻址器(在 tRPC 中称作 "selector"),寻址器的参数是 127.0.0.1:8000。ip 是 tRPC 内置的寻址器,逻辑也很简单,根据后面的 IP + 端口进行寻址。

此外,tRPC 还支持 dns 寻址,在这个寻址器下,如果 port 部份是 443,并且 protocol 为 http,那么tRPC 会自动使用 https 调用。

当然,在正式生产环境下,我们的服务间很少直接使用 ip 寻址器进行服务发现,而是使用服务发现组件。

总结

关于tRPC-Go框架Helloworld实践初体验就到此搁笔,旨在对tRPC有个整体的感知。关于架构中提到的可观测性,服务发现等微服务进一步的知识后续再来补充。


  • 彩蛋的位置~
相关推荐
GoodStudyAndDayDayUp42 分钟前
WIN11安装配置验证java\maven
java·开发语言·maven
fish_xk42 分钟前
用c++写控制台贪吃蛇
开发语言·c++
小小工匠43 分钟前
大规模数据处理:12_Kappa架构剖析与Kafka在大规模流式数据处理中的应用实践
架构·kafka·kappa
一 乐43 分钟前
游戏账号交易|基于Springboot+vue的游戏账号交易系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·后端·游戏
u***13744 分钟前
springboot 修复 Spring Framework 特定条件下目录遍历漏洞(CVE-2024-38819)
spring boot·后端·spring
王燕龙(大卫)44 分钟前
rust:trait
开发语言·后端·rust
合作小小程序员小小店44 分钟前
桌面开发,物业管理系统开发,基于C#,winform,mysql数据库
开发语言·数据库·sql·mysql·microsoft·c#
小程故事多_801 小时前
Kthena 引爆云原生推理革命:K8s 分布式架构破解 LLM 编排困局,吞吐狂飙 273%
人工智能·分布式·云原生·kubernetes·aigc
程序员-周李斌1 小时前
LinkedList 源码深度分析(基于 JDK 8)
java·开发语言·数据结构·list