从零开始构建gRPC的Go服务

介绍

在这里插入图片描述

Protocol Buffers and gRPC是用于定义通过网络有效通信的微服务的流行技术。许多公司在Go中构建gRPC微服务,发布了他们开发的框架,本文将从gRPC入门开始,一步一步构建一个gRPC服务。

本文的源代码已经上传至Github

背景

之前在B站看过一个gRPC教学视频,尝试跟着视频做但踩了不少的坑,因此决定自己动手从官方教程开始,完成一个gRPC项目。

开始

环境配置

首先要配置gRPC所需要的一些环境,由于本人使用Go语言进行开发,操作系统为Ubuntu20.04,因此配置gRPC-go的环境步骤很简单。

安装Go

Ubuntu下安装Go需要先下载Go的源码,本人采用的Go版本为1.18.3,源码下载地址为Go语言中文网:go1.18.3.linux-amd64.tar.gz

下载完毕后,首先检查机器是否存在旧版本的Go,如果存在则删除,然后解压源码到/usr/local。

rm -rf /usr/local/go && tar -C /usr/local -xzf go1.18.3.linux-amd64.tar.gz

添加/usr/local/go/bin到环境变量中,可以在命令行中直接执行:

export PATH=$PATH:/usr/local/go/bin

注意:在命令行中执行上述语句,只会在当前命令行环境下生效,如果关闭命令行后再执行go命令会报错,要解决这个问题,需要将这个语句添加到$HOME/.profile或/etc/profile中,并使用source命令生效

上述步骤完成后,检查Go环境是否安装成功:

go version

输出相应版本号则代表环境配置成功。

go version go1.18.3 linux/amd64

在这里,配置Go的proxy为国内代理,方便之后下载安装package时网速问题,由于安装的Go是1.13及以上的版本,因此直接执行以下命令。

go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct

安装Protocol buffer compiler

Ubuntu上使用apt或者apt-get安装Protocol buffer compiler,命令如下:

sudo apt install -y protobuf-compiler

检查是否安装成功:

protoc --version # Ensure compiler version is 3+

输出相应版本号则代表环境配置成功。

libprotoc 3.6.1

配置Go plugins

在配置Go plugins时,遇到了很多错误。

--go_out: protoc-gen-go: plugins are not supported; use 'protoc --go-grpc_out=...' to generate gRPC

protoc-gen-go-grpc: program not found or is not executable

网上的解决方法也不一定奏效,最后还是选择按照官网上的步骤安装对应版本的protoc-gen-go和protoc-gen-go-grpc。

go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2

注意这里都是从goole.golang.org下载的package

更新环境变量,将下面的命令添加到$HOME/.profile或/etc/profile中,source使之生效。

export PATH="$PATH:$(go env GOPATH)/bin"

到此为之,gRPC-go的环境就算配置完成了。

gRPC接口定义

.proto文件

第一步首先定义gRPC服务以及方法请求和响应类型。要定义服务,请在.proto文件中指定命名服务:

service NewService {
  rpc GetHotTopNews(Request) returns (News) {}
}

然后在服务定义中定义RPC方法,指定它们的请求和响应类型。gRPC允许您定义四种服务方法:

  • 一个简单的RPC,其中客户端使用存根向服务端发送请求并等待响应返回,就像正常的函数调用一样。
// Obtains the feature at a given position.
rpc GetFeature(Point) returns (Feature) {}
  • 服务端流式RPC,客户端向服务端发送请求并获取流以读回一系列消息。客户端从返回的流中读取,直到没有更多消息为止。
// Obtains the Features available within the given Rectangle.  Results are
// streamed rather than returned at once (e.g. in a response message with a
// repeated field), as the rectangle may cover a large area and contain a
// huge number of features.
rpc ListFeatures(Rectangle) returns (stream Feature) {}
  • 客户端流式RPC,其中客户端写入一系列消息并将它们发送到服务端,再次使用提供的流。一旦客户端完成了消息的写入,它会等待服务端读取所有消息并返回其响应。可以通过将stream关键字放在请求类型之前来指定客户端流式处理方法。
// Accepts a stream of Points on a route being traversed, returning a
// RouteSummary when traversal is completed.
rpc RecordRoute(stream Point) returns (RouteSummary) {}
  • 双向流式RPC,双方使用读写流发送一系列消息。这两个流独立运行,因此客户端和服务端可以按照他们喜欢的任何顺序读取和写入:例如,服务端可以在写入响应之前等待接收所有客户端消息,或者它可以交替读取消息然后写入消息,或其他一些读取和写入的组合,保留每个流中消息的顺序。可以通过在请求和响应之前放置stream关键字来指定这种类型的方法。
// Accepts a stream of RouteNotes sent while a route is being traversed,
// while receiving other RouteNotes (e.g. from other users).
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}

我们将要实现一个获取热点新闻的gRPC接口,.proto文件包含服务方法中使用的所有请求和响应类型的协议缓冲区消息类型定义。例如,这里是Request消息类型:

message Request {
  string type = 1;
  int64 page = 2;
  int64 size = 3;
  int64 is_filter = 4;
}

以及Response定义:

message Response { repeated New news = 1; }

其中New的结构定义为:

message New {
  string uniquekey = 1;
  string title = 2;
  string date = 3;
  string category = 4;
  string author_name = 5;
  string url = 6;
  string thumbnail_pic_s = 7;
  int64 is_content = 8;
}

最后定义RPC接口:

syntax = "proto3";

```go
在这里插入代码片

option go_package = “./;protobuf”;

package protobuf;

service NewService {
rpc GetHotTopNews(Request) returns (Response) {}
}

注意这里加上了option go_package = "./;protobuf";,说明生成的pb.go的package名称。
### protoc命令
接下来,我们需要从.proto服务定义中生成gRPC客户端和服务端接口。我们使用带有特殊gRPC Go插件的protobuf compiler来执行此操作。
```bash
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative protobuf/*.proto

会在.proto文件同级目录下生成以下go文件:

  • news.pb.go,其中包含用于填充、序列化和检索请求和响应消息类型的所有协议缓冲区代码
  • news_grpc.pb.go,包含:1)客户端使用服务中定义的方法调用的接口类型(或存根);2)服务端要实现的接口类型,也使用服务中定义的方法。

这里我使用VS code进行开发,在编写.proto文件时推荐使用两个插件:

  • vscode-proto3:用于识别.proto文件的一些语法
  • clang-format:用于格式化.proto文件,需要使用sudo apt install clang-format,并且按照插件说明进行相应配置

Go服务构建

server

服务端需要实现gRPC的接口,首先定义一个结构体:

type Server struct {
   
	protobuf.UnimplementedNewServiceServer
}

继承了生成的pb.go文件中的UnimplementedNewServiceServer,接着实现接口内的方法:

func (s *Server) GetHotTopNews(ctx context.Context, req *protobuf.Request) (*protobuf.Response, error) {
   
	ret := srv.RequestPublishAPI()
	return &protobuf.Response{
   
		News: ret,
	}, nil
}

这样,最基本的gRPC服务就能启动了。

func main() {
   
	// register grpc service
	s := grpc.NewServer()
	protobuf.RegisterNewServiceServer(s, &Server{
   })

	// listen tcp connection
	flag.Parse()
	lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
	if err != nil {
   
		log.Fatalf("failed to listen: %v", err)
	}

	// start grpc server
	log.Printf("server listening at %v", lis.Addr())
	if err := s.Serve(lis); err != nil {
   
		log.Fatalf("failed to serve: %v", err)
	}
}

client

同样的,我们用go编写一个客户端来请求测试gRPC服务是否能工作。

var (
	addr = flag.String("addr", "localhost:50051", "the address to connect to")
)

func main() {
   
	flag.Parse()
	// Set up a connection to the server.
	conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
   
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
	c := protobuf.NewNewServiceClient(conn)

	// Contact the server and print out its response.
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	r, err := c.GetHotTopNews(ctx, &protobuf.Request{
   })

	if err != nil {
   
		log.Fatalf("could not greet: %v", err)
	}
	for _, v := range r.GetNews() {
   
		fmt.Println(v)
	}
}

至此,一个简单的gRPC服务就已经全部完成了,但我们获取热点新闻的接口是伪造的,因此我们加入免费的获取热点新闻API到项目中,让客户端有实际返回,API主要逻辑如下:

// NewService contains services that fetch new and convert to grpc protobuf
type NewService struct {
   
	apiUri   string
	apiKey   string
	reqType  string
	page     int
	size     int
	isFilter int
}

func (s *NewService) RequestPublishAPI() []*protobuf.New {
   
	reqUrl := fmt.Sprintf("%s?type=%s&page=%d&page_size=%d&is_filter=%d&key=%s", s.apiUri, s.reqType, s.page, s.size, s.isFilter, s.apiKey)
	log.Printf("request url: %s", reqUrl)

	method := "GET"

	client := &http.Client{
   }
	req, err := http.NewRequest(method, reqUrl, nil)

	if err != nil {
   
		panic(err)
	}

	res, err := client.Do(req)
	if err != nil {
   
		panic(err)
	}
	defer res.Body.Close()

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
   
		panic(err)
	}

	var resp ApiResponse
	err = json.Unmarshal(body, &resp)
	if err != nil {
   
		panic(err)
	}

	var ret []*protobuf.New
	for _, n := range resp.Result.Data {
   
		isContent, _ := strconv.Atoi(n.</
  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
DBeaver是一个功能强大的开源数据库管理工具,支持多种数据库,包括DB2。DB2是由IBM开发的关系型数据库管理系统,广泛用于企业级应用。 DBeaver提供了与DB2数据库的连接和操作功能,但是它并不内置DB2的驱动包。为了连接和管理DB2数据库,我们需要手动安装DB2的驱动包。 首先,我们需要从IBM官方网站上下载适用于我们所使用的DB2版本的驱动程序。这通常是一个JAR文件(Java Archive),包含了连接和操作DB2数据库所需的类和库。 然后,我们需要在DBeaver中配置DB2的驱动。启动DBeaver并打开“数据库连接管理器”。选择我们想要连接的数据库类型为DB2,然后点击“编辑驱动”。 在驱动配置窗口中,点击“添加文件”按钮,选择我们刚刚下载的DB2驱动JAR文件,并点击“确定”。 然后,我们可以在DBeaver中创建一个新的DB2连接。在“数据库连接管理器”中,点击“新建连接”,输入连接的基本信息,如主机名、端口号、数据库名称、用户名和密码等。 在连接配置中,我们需要选择刚刚添加的DB2驱动。点击“驱动”,选择我们之前配置的DB2驱动,并点击“确定”。 最后,点击“测试连接”按钮,以确保我们的DB2连接配置正确。如果一切正常,我们可以保存连接并开始连接和管理DB2数据库。 总结来说,DBeaver是一个支持多种数据库的数据库管理工具,包括DB2。要在DBeaver中连接和操作DB2数据库,我们需要手动安装DB2的驱动包,并在DBeaver中配置该驱动。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值