微服务实战Go Micro v3 系列(二)- HelloWorld

1,721 阅读7分钟

首先从我们最最熟悉的 helloworld 例子在入手,对 go-micro 有一个初步的了解

源码地址

ProtoBuf

简介

protocol buffers (ProtoBuf)是一种语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于(数据)通信协议、数据存储等。

Protocol Buffers 是一种灵活,高效,自动化机制的结构数据序列化方法-可类比 XML,但是比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单。

json\xml都是基于文本格式,protobuf是二进制格式。

你可以通过 ProtoBuf 定义数据结构,然后通过 ProtoBuf 工具生成各种语言版本的数据结构类库,用于操作 ProtoBuf 协议数据

本教程介绍的是最新的protobuf proto3版本的语法。

使用ProtoBuf的例子

创建 .proto 文件,定义数据结构

使用 ProtoBuf ,首先需要通过 ProtoBuf 语法定义数据结构(消息),这些定义好的数据结构保存在.proto为后缀的文件中。

例子:

文件名: response.proto

// 指定protobuf的版本,proto3是最新的语法版本
syntax = "proto3";

// 定义数据结构,message 你可以想象成java的class,c语言中的struct
message Response {
  string data = 1;   // 定义一个string类型的字段,字段名字为data, 序号为1
  int32 status = 2;   // 定义一个int32类型的字段,字段名字为status, 序号为2
}

说明:proto文件中,字段后面的序号,不能重复,定义了就不能修改,可以理解成字段的唯一ID。

安装ProtoBuf编译器

protobuf的github发布地址: github.com/protocolbuf…

protobuf的编译器叫protoc,在上面的网址中找到最新版本的安装包,下载安装。

这里下载的是:protoc-3.9.1-win64.zip , windows 64位系统版本的编译器,下载后,解压到你想要的安装目录即可。

提示:安装完成后,将 [protoc安装目录]/bin 路径添加到PATH环境变量中

打开cmd,命令窗口执行protoc命令,没有报错的话,就已经安装成功。

更多protoc的教程请点击这里

grpc

gRPC 是一个高性能、跨平台、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计。目前提供 C/C++、Java、Python、Ruby、C#、PHP、Node.js、Go 语言等版本,几乎你想到的语言都支持了.

gRPC 基于 HTTP/2 标准设计,带来诸如双向流、流控、头部压缩、单 TCP 连接上的多复用请求等特。这些特性使得其在移动设备上表现更好,更省电和节省空间占用。

下面先介绍grpc相关概念

grpc是什么?

在 gRPC 里客户端应用可以像调用本地方法一样直接调用另一台机器上服务端应用的方法,这样我们就很容易创建分布式应用和服务。跟其他 RPC 系统类似,gRPC 也是基于以下理念:首先定义一个服务,定义能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个方法,并运行一个 gRPC 服务器来处理客户端调用。在客户端拥有一个存根,这个存根就是长得像服务端一样的方法(但是没有具体实现),客户端通过这个存根调用服务端的方法。

grpc工作原理,如下图:

grpc使用的协议

gRPC 默认使用 protocol buffers,这是 Google 开源的一套成熟的结构数据的序列化机制,当然也可以使用其他数据格式如 JSON,不过通常都使用protocol buffers这种灵活、高效的数据格式,如果不了解protobuf语法,点击这里学习 protocol buffers入门教程

服务定义

使用gprc,首先需要定义服务, 指定其可以被远程调用的方法及其参数和返回类型。

服务,你可以理解成服务端api接口的集合,对外提供一些功能。

通过protobuf定义服务的例子:

// 定义一个叫HelloService的服务
service HelloService {
  // 定义一个叫SayHello的方法,这个方法接受HelloRequest消息作为参数,返回HelloResponse消息
  rpc SayHello (HelloRequest) returns (HelloResponse);
}

// 定义HelloRequest消息
message HelloRequest {
  string greeting = 1;
}

// 定义HelloResponse消息
message HelloResponse {
  string reply = 1;
}

如果你把service和message关键词当成class,是不是跟类定义很像!

gRPC 允许你定义四类服务方法,下面分别介绍如何定义,以及客户端和服务端的交互方式。

单向RPC

即客户端发送一个请求给服务端,从服务端获取一个应答,就像一次普通的函数调用。

rpc SayHello(HelloRequest) returns (HelloResponse){
}

服务端流式 RPC

即客户端发送一个请求给服务端,可获取一个数据流用来读取一系列消息。客户端从返回的数据流里一直读取直到没有更多消息为止。

通俗的讲就是客户端请求一次,服务端就可以源源不断的给客户端发送消息。

// 注意stream关键词在什么地方
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){
}

客户端流式 RPC

即客户端用提供的一个数据流写入并发送一系列消息给服务端。一旦客户端完成消息写入,就等待服务端读取这些消息并返回应答。

通俗的讲就是请求一次,客户端就可以源源不断的往服务端发送消息。

// 注意stream关键词在什么地方
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) {
}

双向流式 RPC

即两边都可以分别通过一个读写数据流来发送一系列消息。这两个数据流操作是相互独立的,所以客户端和服务端能按其希望的任意顺序读写,例如:服务端可以在写应答前等待所有的客户端消息,或者它可以先读一个消息再写一个消息,或者是读写相结合的其他方式。每个数据流里消息的顺序会被保持。

类似tcp通信,客户端和服务端可以互相发消息。

// 注意stream关键词在什么地方
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){
}

更多关于grpc教程请点击这里

编写 go-micro HTTP服务

安装 gofast插件

//gofast
go get -u -v github.com/gogo/protobuf/protoc-gen-gofast

开始编写 go-micro HTTP服务

创建 go-micro-examples 目录,然后在该目录下创建 helloworld目录

helloworld 目录下,创建 protohandler,并创建 main.go 和使用 go mod init 初始化项目,如下图所示

其它文件暂时忽略,后面会解释

在 proto 目录创建 helloworld.proto,并写入下面代码

syntax = "proto3";

package helloworld;

option go_package = "proto;helloworld";

service Helloworld {
	rpc Call(Request) returns (Response) {}
	rpc Stream(StreamingRequest) returns (stream StreamingResponse) {}
	rpc PingPong(stream Ping) returns (stream Pong) {}
}

message Message {
	string say = 1;
}

message Request {
	string name = 1;
}

message Response {
	string msg = 1;
}

message StreamingRequest {
	int64 count = 1;
}

message StreamingResponse {
	int64 count = 1;
}

message Ping {
	int64 stroke = 1;
}

message Pong {
	int64 stroke = 1;
}

进入该目录并输入以下命令

并生成 helloworld.pb.gohelloworld.pb.micro.go

main.go,创建服务并注册一下 handler,运行服务

package main

import (
	"github.com/asim/go-micro/v3"
	"github.com/asim/go-micro/v3/logger"
	"go-micro-examples/helloworld/handler"
	pb "go-micro-examples/helloworld/proto"
)

const (
	ServerName = "go.micro.srv.HelloWorld" // server name
)

func main() {
	// Create service
	service := micro.NewService(
		micro.Name(ServerName),
		micro.Version("latest"),
	)

	// Register handler
	if err := pb.RegisterHelloworldHandler(service.Server(), new(handler.Helloworld)); err != nil {
		logger.Fatal(err)
	}

	// Run service
	if err := service.Run(); err != nil {
		logger.Fatal(err)
	}
}

client 调用 service

创建 client 目录并创建client.go,写入一下代码

package main

import (
	"context"
	"fmt"
	"github.com/asim/go-micro/v3"
	helloworld "go-micro-examples/helloworld/proto"
)

func main() {
	// create a new service
	service := micro.NewService()

	// parse command line flags
	service.Init()

	// Use the generated client stub
	cl := helloworld.NewHelloworldService("go.micro.srv.HelloWorld", service.Client())

	// Make request
	rsp, err := cl.Call(context.Background(), &helloworld.Request{
		Name: "John",
	})
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(rsp.Msg)
}

效果

先启用 go.micro.srv.HelloWorld 服务,然后再启动 client 调用,效果如下

启动 client 之后,输出 Hello World!

参考链接