每个节点构建grpc服务_构建Go gRPC微服务

每个节点构建grpc服务

Protocol Buffers and gRPC are popular technologies for defining microservices that communicate efficiently over the network. Many companies build gRPC microservices in Go. Some have even published the frameworks they have developed, such as the New York Times’ Gizmo, which uses go-kit under the hood.

协议缓冲区和gRPC是用于定义可通过网络有效通信的微服务的流行技术。 许多公司在Go中构建gRPC微服务。 有些甚至发布了他们开发的框架,例如《纽约时报》的Gizmo ,它在后台使用go-kit

There are many great open source libraries out there, but as with many things in Go, rolling your own implementation is easy, fun, and surprisingly simple. In this tutorial, I will outline how we structure gRPC microservices at my company, HER. All the code discussed below is available on Github.

有很多很棒的开源库,但是和Go中的许多事情一样,滚动自己的实现很容易,有趣并且非常简单。 在本教程中,我将概述如何在我的公司HER中构造gRPC微服务。 下面讨论的所有代码都可以在Github上找到

动机 (Motiviation)

There are surprisingly few examples of how to structure gRPC services in Go. I found one tutorial by Aleksandr Sokolovskii that is very good and quite thorough. However, I didn’t find all of the design decisions to be practical for my purposes, so I developed my own template for laying things out. Hopefully between the two guides, you’ll find something that works well for you!

令人惊讶的是,很少有关于如何在Go中构造gRPC服务的示例。 我发现Aleksandr Sokolovskii撰写的一篇教程非常好,非常详尽。 但是,我发现并不是所有的设计决策都可以满足我的实际需求,因此我开发了自己的模板来进行布局。 希望在这两个指南之间,您会找到适合自己的东西!

先决条件 (Prerequisites)

It is assumed you are familiar with Go and can create a new project, compile, etc. I will be including a lot of code examples, but I won’t be going over things like compiling, using go mod, etc.

假定您熟悉Go并可以创建一个新项目,进行编译等。我将提供许多代码示例,但我不会涉及诸如编译,使用go mod等之类的事情。

定义我们的服务 (Defining Our Service)

I’m going to create a service for loading users for my awesome (hypothetical) app. I’m not going to think about any gRPC stuff yet. I will write the main service interface in a file called service.go in the root directory. It will consist of two methods, one to load a single user by their ID and another that accepts multiple user IDs and returns a map of Users keyed by their IDs. The single user method returns an error if no user is found while the multiple user method will just return an empty map in that case.

我将为真棒(假设的)应用程序创建用于加载用户的服务。 我不会考虑任何gRPC的东西。 我将主服务接口写入根目录中名为service.go的文件中。 它由两种方法组成,一种方法是通过其ID加载单个用户,另一种方法则是接受多个用户ID并返回以其ID为键的Users映射。 如果找不到用户,则单用户方法将返回错误,而在这种情况下,多用户方法将仅返回一个空映射。

package mysvc// ErrNotFound signifies that a single requested object was not found.
var ErrNotFound = errors.New("not found")// User is a user business object.
type User struct {
ID int64
Name string
}// Service defines the interface exposed by this package.
type Service interface {
GetUser(id int64) (User, error)
GetUsers(ids []int64) (map[int64]User, error)
}

I want to write the service definition first because I want to think about the business logic first and foremost. I place it in the root directory because I want the package structure to have an intuitive hierarchy (you’ll see what I mean in a second). I also want to have a simple import path and naming when I’m referring to business objects such as user.Service and user.User (this stutters a bit, but it’s clear).

我想首先编写服务定义,因为我想首先考虑业务逻辑。 我将其放在根目录中是因为我希望程序包结构具有直观的层次结构(您很快就会明白我的意思)。 当我引用诸如user.Serviceuser.User类的业务对象时,我还希望有一个简单的导入路径和命名user.User (这有点user.User ,但是很明显)。

实施服务 (Implementing the Service)

The next thing I am going to do is implement the core of the service. This means writing all the code that will accomplish what I want my microservice to do. I will create a sub-package and file called core/service.go with the following code:

我接下来要做的就是实现服务的核心。 这意味着编写所有代码,这些代码将完成我希望微服务执行的操作。 我将使用以下代码创建一个名为core/service.go的子包和文件:

package coreimport (
"errors" "github.com/neocortical/mysvc"
)type service struct {
// a database dependency would go here but instead we're going to have a static map
m map[int64]mysvc.User
}// NewService instantiates a new Service.
func NewService( /* a database connection would be injected here */ ) mysvc.Service {
return &service{
m: map[int64]mysvc.User{
1: {ID: 1, Name: "Alice"},
2: {ID: 2, Name: "Bob"},
3: {ID: 3, Name: "Carol"},
},
}
}func (s *service) GetUser(id int64) (result mysvc.User, err error) {
// instead of querying a database, we just query our static map
if result, ok := s.m[id]; ok {
return result, nil
} return result, mysvc.ErrNotFound
}func (s *service) GetUsers(ids []int64) (result map[int64]mysvc.User, err error) {
// always a good idea to return non-nil maps to avoid nil pointer dereferences
result = map[int64]mysvc.User{} for _, id := range ids {
if u, ok := s.m[id]; ok {
result[id] = u
}
} return
}

At this point I have a working implementation of the code I care about. If I decide that I don’t want to create a separate microservice after all, I can just write userService := mysvc.NewService() in my app to get the functionality I need (again, in a real-world example you would be required to configure the service’s dependencies as well).

至此,我已经有了我关心的代码的有效实现。 如果我最终决定不想创建单独的微服务,则可以在应用中编写userService := mysvc.NewService()以获得所需的功能(再次,在真实示例中,您将也需要配置服务的依赖项)。

添加gRPC (Adding gRPC)

Now it’s time to create the gRPC service definition. This design pattern treats gRPC as a transport layer only, keeping it entirely separate from our business objects and logic. It may feel counterintuitive to create two different interfaces for the same thing, but this allows us to reason about our core service and correct gRPC behavior in isolation, producing a cleaner, easier to maintain end product.

现在是时候创建gRPC服务定义了。 这种设计模式仅将gRPC视为传输层,使其与我们的业务对象和逻辑完全分开。 为同一事物创建两个不同的接口可能感觉违反直觉,但这使我们能够推理核心服务并孤立地纠正gRPC行为,从而生产出更清洁,更易于维护的最终产品。

I’m going to create a sub-package called grpc and add a protobuf file called service.proto:

我将创建一个名为grpc的子软件包,并添加一个名为service.proto的protobuf文件:

syntax = "proto3";package grpc;service UserService {
rpc GetUsers (GetUsersRequest) returns (GetUsersResponse) {}
}message User {
int64 id = 1;
string name = 2;
}message GetUsersRequest {
repeated int64 ids = 1;
}

message GetUsersResponse {
repeated User users = 1;
}

Hey wait, I’ve only added half of the user service interface and it doesn’t even match, what gives? This is a matter of choice, but for this example I wanted to demonstrate that you can use the glue code between the gRPC layer and your core service layer to map and simplify the gRPC code you need to write.

嘿,等等,我只添加了一半的用户服务界面,甚至都不匹配,这有什么用呢? 这是一个选择问题,但是对于本示例,我想证明您可以使用gRPC层和核心服务层之间的粘合代码来映射和简化您需要编写的gRPC代码。

Next, I will generate the Go gRPC code using protoc. Getting protoc installed is beyond the scope of this tutorial, but once it’s installed you just need to run go install google.golang.org/protobuf/cmd/protoc-gen-go to add the Go plugin. Check out this Protocol Buffers guide on Google’s developer website for more info.

接下来,我将使用产生转到GRPC代码protoc 。 获得protoc安装已经超出了本文的范围,但一旦它的安装,你只需要运行go install google.golang.org/protobuf/cmd/protoc-gen-go添加围棋插件。 在Google开发者网站上查看此协议缓冲区指南,以了解更多信息。

To generate the Go code, I just need to cd into the grpc directory and run:

要生成Go代码,我只需要cd进入grpc目录并运行:

protoc --go_out=plugins=grpc:. *.proto

I always forget the exact command, so I highly recommend creating a small run script or README to document it!

我总是忘记确切的命令,因此我强烈建议创建一个小的运行脚本或README来对其进行记录!

Now I have a Go file in the same directory called service.pb.go that contains all the autogenerated protobufs code I will need to wire up my gRPC service. Let’s do that now.

现在,我在一个名为service.pb.go目录中有一个Go文件,其中包含所有自动生成的protobufs代码,我需要将它们连接到gRPC服务。 现在开始吧。

gRPC服务:控制器 (gRPC Service: The Controller)

The first thing I’m going to write is the controller that will implement the gRPC service method I created. I’m going to create a new sub-package and file called grpc/service/controller.go with the following code:

我要写的第一件事是控制器,它将实现我创建的gRPC服务方法。 我将使用以下代码创建一个名为grpc/service/controller.go的新子软件包和文件:

package mainimport (
"context" "github.com/neocortical/mysvc"
mysvcgrpc "github.com/neocortical/mysvc/grpc"
)// userServiceController implements the gRPC UserServiceServer interface.
type userServiceController struct {
userService mysvc.Service
}// NewUserServiceController instantiates a new UserServiceServer.
func NewUserServiceController(userService mysvc.Service) mysvcgrpc.UserServiceServer {
return &userServiceController{
userService: userService,
}
}// GetUsers calls the core service's GetUsers method and maps the result to a grpc service response.
func (ctlr *userServiceController) GetUsers(ctx context.Context, req *mysvcgrpc.GetUsersRequest) (resp *mysvcgrpc.GetUsersResponse, err error) {
resultMap, err := ctlr.userService.GetUsers(req.GetIds())
if err != nil {
return
} resp := &mysvcgrpc.GetUsersResponse{}
for _, u := range resultMap {
resp.Users = append(resp.Users, marshalUser(&u))
} return
}// marshalUser marshals a business object User into a gRPC layer User.
func marshalUser(u *mysvc.User) *mysvcgrpc.User {
return &mysvcgrpc.User{Id: u.ID, Name: u.Name}
}

This code implements the service side of our gRPC contract. It is simply a gRPC-aware wrapper around our core user service whose job it is to marshal the core service’s responses for transport via gRPC. This may seem a little cumbersome now, but we’ll see why I do things this way in a minute.

此代码实现了我们的gRPC合同的服务端。 它只是我们核心用户服务周围的一个gRPC感知包装器,其任务是封送核心服务通过gRPC进行传输的响应。 现在这似乎有点麻烦,但是我们很快就会明白为什么我要这样做。

gRPC服务:服务器 (gRPC Service: The Server)

But first, let’s start our service. We need a gRPC server. I will add a simple main function in grpc/server/main.go. It’s job is to wire up the core service, inject it into the controller, and start a gRPC server.

但是首先,让我们开始我们的服务。 我们需要一个gRPC服务器。 我将在grpc/server/main.go添加一个简单的main函数。 它的工作是连接核心服务,将其注入控制器,然后启动gRPC服务器。

package mainimport (
"net"
"os" mysvccore "github.com/neocortical/mysvc/core"
mysvcgrpc "github.com/neocortical/mysvc/grpc"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)func main() { // configure our core service
userService := mysvccore.NewService() // configure our gRPC service controller
userServiceController := NewUserServiceController(userService) // start a gRPC server
server := grpc.NewServer()
mysvcgrpc.RegisterUserServiceServer(server, userServiceController)
reflection.Register(server) con, err := net.Listen("tcp", os.Getenv("GRPC_ADDR"))
if err != nil {
panic(err)
} err = server.Serve(con)
if err != nil {
panic(err)
}
}

Now, we can build and run our gRPC service with the following commands:

现在,我们可以使用以下命令构建并运行我们的gRPC服务:

cd grpc/server
go build -o ./mysvc .
GRPC_ADDR=":9000" ./mysvc

Voilà — we have a gRPC user service up and running! But how do we talk to it?

贴吧-我们已经启动并运行了gRPC用户服务! 但是我们该怎么谈呢?

gRPC服务:客户端 (gRPC Service: The Client)

The last thing we need to build is the gRPC client interface. This is also the core service interface that we will use to call our microservice. Its job is to mediate between calls to the service and the gRPC transport layer underneath. This is where we do all the cool stuff like mapping our two service methods down to one gRPC call and handling the details of gRPC communication. I’ll add a sub-package and file called grpc/client/client.go:

我们需要构建的最后一件事是gRPC客户端接口。 这也是我们用来调用微服务的核心服务接口。 它的工作是在对服务的调用与下面的gRPC传输层之间进行中介。 在这里,我们可以完成所有很酷的工作,例如将我们的两个服务方法映射到一个gRPC调用并处理gRPC通信的详细信息。 我将添加一个名为grpc/client/client.go的子软件包和文件:

package clientimport (
"context"
"time" "github.com/neocortical/mysvc"
mysvcgrpc "github.com/neocortical/mysvc/grpc"
"google.golang.org/grpc"
)var defaultRequestTimeout = time.Second * 10type grpcService struct {
grpcClient mysvcgrpc.UserServiceClient
}// NewGRPCService creates a new gRPC user service connection using the specified connection string.
func NewGRPCService(connString string) (mysvc.Service, error) {
conn, err := grpc.Dial(connString, grpc.WithInsecure())
if err != nil {
return nil, err
} return &grpcService{grpcClient: mysvcgrpc.NewUserServiceClient(conn)}, nil
}func (s *grpcService) GetUsers(ids []int64) (result map[int64]mysvc.User, err error) {
result = map[int64]mysvc.User{} req := &mysvcgrpc.GetUsersRequest{
Ids: ids,
} ctx, cancelFunc := context.WithTimeout(context.Background(), defaultRequestTimeout)
defer cancelFunc()
resp, err := s.grpcClient.GetUsers(ctx, req)
if err != nil {
return
} for _, grpcUser := range resp.GetUsers() {
u := unmarshalUser(grpcUser)
result[u.ID] = u
}
return
}func (s *grpcService) GetUser(id int64) (result mysvc.User, err error) {
req := &mysvcgrpc.GetUsersRequest{
Ids: []int64{id},
} ctx, cancelFunc := context.WithTimeout(context.Background(), defaultRequestTimeout)
defer cancelFunc()
resp, err := s.grpcClient.GetUsers(ctx, req)
if err != nil {
return
} for _, grpcUser := range resp.GetUsers() {
// sanity check: only the requested user should be present in results
if grpcUser.GetId() == id {
return unmarshalUser(grpcUser), nil
}
} return result, mysvc.ErrNotFound
}func unmarshalUser(grpcUser *mysvcgrpc.User) (result mysvc.User) {
result.ID = grpcUser.Id
result.Name = grpcUser.Name
return
}

There are a couple things worth noting here. I’m mapping the two service methods onto a single gRPC method while preserving the different behavior (GetUser() returns an error if no user is found whereas GetUsers() returns an empty map and no error). Also, I’m able to work around the lack of maps in protobufs in a way that is transparent to the user of the service.

这里有几件事值得注意。 我将两个服务方法映射到单个gRPC方法上,同时保留了不同的行为(如果未找到用户,则GetUser()返回错误,而GetUsers()返回空映射且没有错误)。 而且,我能够以一种对服务用户透明的方式解决protobuf中缺少地图的问题。

Most importantly, all gRPC-specific code is encapsulated here so that downstream developers don’t have to think about it. The package chooses a reasonable timeout deadline for the context and manages calling its cancel function opaquely. I could also expose the ability to set the timeout and/or other gRPC-related configuration here. The goal is that the only place your code needs to know it is using the gRPC version of the service and not the core service is where it is configured.

最重要的是,所有gRPC特定的代码都封装在这里,以便下游开发人员不必考虑它。 程序包为上下文选择一个合理的超时期限,并管理透明地调用其取消功能。 我还可以在此处公开设置超时和/或其他与gRPC相关的配置的功能。 目标是您的代码唯一需要知道的地方是使用服务的gRPC版本,而不是配置核心服务的地方。

结论 (Conclusion)

I hope you found this tutorial useful. All the code is available on Github. The README goes into a little more detail and has instructions for running the service as well as a command line tool to test out the gRPC client.

希望本教程对您有所帮助。 所有代码都可以在Github上找到 。 README进行了更详细的介绍,并提供了有关运行服务的说明以及用于测试gRPC客户端的命令行工具。

If you have any questions, comments, or suggestions please feel free to comment below or reach out on Twitter. Thanks for reading!

如果您有任何疑问,意见或建议,请随时在下面发表评论,或在Twitter上与我们联系 。 谢谢阅读!

翻译自: https://medium.com/@nate510/structuring-go-grpc-microservices-dd176fdf28d0

每个节点构建grpc服务

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值