[从RPC到Go-Micro 叁]Go语言使用gRPC

使用官方的RPC库,需要自己编码服务的注册等操作,从而增加了许多重复性的操作。所以,gRPC便出现在视野中。

什么是gRPC

gRPC介绍

gRPC是由Google公司开源的一款高性能的远程过程调用(RPC)框架,可以在任何环境下运行。该框架提供了负载均衡,跟踪,智能监控,身份验证等功能,可以实现系统间的高效连接。另外,在分布式系统中,gRPC框架也有有广泛应用,实现移动社会,浏览器等和服务器的连接。其支持Go、Java、Python等众多语言

gRPC官网与仓库

gRPC官方网站:https://grpc.io/

gRPC源码库:https://github.com/grpc/grpc

gRPC开源库支持诸如:C++,C#,Dart,Go,Java,Node,Objective-C,PHP,Python,Ruby,WebJS等多种语言,开发者可以自行在gRPC的github主页库选择查看对应语言的实现。

gRPC调用执行过程

因为gRPC支持多种语言的实现,因此gRPC支持客户端与服务器在多种语言环境中部署运行和互相调用。多语言环境交互示例如下图所示:

gRPC调用过程

gRPC中默认采用的数据格式化方式是protocol buffers

gRPC-Go

介绍

grpc-go库是gRPC库的Golang语言实现版本。可以通过github主页访问grpc-go库的源码并下载。grpc-go主页的Github地址如下:https://github.com/grpc/grpc-go

安装

go get -u google.golang.org/grpc

使用gRPC

定义服务

我们想要实现的是通过gRPC框架进行远程服务调用,首先第一步应该是要有服务。利用之前所掌握的内容,gRPC框架支持对服务的定义和生成。
gRPC框架默认使用protocol buffers作为接口定义语言,用于描述网络传输消息结构。除此之外,还可以使用protobuf定义服务接口。

syntax = "proto3";
package message;

//订单请求参数
message OrderRequest {
    string orderId = 1;
    int64 timeStamp = 2;
}

//订单信息
message OrderInfo {
    string OrderId = 1;
    string OrderName = 2;
    string OrderStatus = 3;
}

//订单服务service定义
service OrderService{
    rpc GetOrderInfo(OrderRequest) returns (OrderInfo);
}

我们通过proto文件定义了数据结构的同时,还定义了要实现的服务接口,GetOrderInfo即是具体服务接口的定义,在GetOrderInfo接口定义中,OrderRequest表示是请求传递的参数,OrderInfo表示处理结果返回数据参数。

编译.proto文件

下载go语言的编译插件
go get -a github.com/golang/protobuf/protoc-gen-go

-a 参数标示下载好后直接做 go install

编译.proto文件

原用法

protoc --go_out=. *.proto

增加插件的用法
如果定义的.proto文件,如本案例中所示,定义中包含了服务接口的定义,而我们想要使用gRPC框架实现RPC调用。开发者可以采用protocol-gen-go库提供的插件编译功能,生成兼容gRPC框架的golang语言代码。只需要在基本编译命令的基础上,指定插件的参数,告知protoc编译器即可。具体的编译生成兼容gRPC框架的服务代码的命令如下:

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

使用gRPC实现RPC编程

实现服务接口
在.proto定义好服务接口并生成对应的go语言文件后,需要对服务接口做具体的实现。定义服务接口具体由OrderServiceImpl进行实现,并实现GetOrderInfo详细内容,服务实现逻辑与前文所述内容相同。不同点是服务接口参数的变化。详细代码实现如下:

type OrderServiceImpl struct {
}

//具体的方法实现
func (os *OrderServiceImpl) GetOrderInfo(ctx context.Context, request *message.OrderRequest) (*message.OrderInfo, error) {
	orderMap := map[string]message.OrderInfo{
		"201907300001": message.OrderInfo{OrderId: "201907300001", OrderName: "衣服", OrderStatus: "已付款"},
		"201907310001": message.OrderInfo{OrderId: "201907310001", OrderName: "零食", OrderStatus: "已付款"},
		"201907310002": message.OrderInfo{OrderId: "201907310002", OrderName: "食品", OrderStatus: "未付款"},
	}

	var response *message.OrderInfo
	current := time.Now().Unix()
	if (request.TimeStamp > current) {
		*response = message.OrderInfo{OrderId: "0", OrderName: "", OrderStatus: "订单信息异常"}
	} else {
		result := orderMap[request.OrderId]
		if result.OrderId != "" {
			fmt.Println(result)
			return &result, nil
		} else {
			return nil, errors.New("server error")
		}
	}
	return response, nil
}

服务端实现
使用gRPC框架,首先实现服务端的程序。既然使用gRPC框架来实现,就需要调用gRPC进行服务方法的注册以及监听的处理。服务注册和监听处理实现如下:

func main() {

	server := grpc.NewServer()
	// 此处的RegisterOrderServiceServer是根据proto文件中自动生成的
	message.RegisterOrderServiceServer(server, new(OrderServiceImpl))

	lis, err := net.Listen("tcp", ":8090")
	if err != nil {
		panic(err.Error())
	}
	server.Serve(lis)
}

客户端实现
实现完服务端以后,实现客户端程序。和服务端程序关系对应,调用gRPC框架的方法获取相应的客户端程序,并实现服务的调用,具体编程实现如下:

func main() {

	//1、Dail连接
	conn, err := grpc.Dial("localhost:8090", grpc.WithInsecure())
	if err != nil {
		panic(err.Error())
	}
	defer conn.Close()

	orderServiceClient := message.NewOrderServiceClient(conn)

	orderRequest := &message.OrderRequest{OrderId: "201907300001", TimeStamp: time.Now().Unix()}
	orderInfo, err := orderServiceClient.GetOrderInfo(context.Background(), orderRequest)
	if orderInfo != nil {
		fmt.Println(orderInfo.GetOrderId())
		fmt.Println(orderInfo.GetOrderName())
		fmt.Println(orderInfo.GetOrderStatus())
	}
}

此处GetOrderInfo可以传入上下文
上下文类型有:

// 背景返回non-nil(非零),空的 Context。它从未被取消,没有值,也没有最后期限。它通常由主函数,初始化和测试使用,并作为传入请求的top-level Context (顶级上下文)。
func Background() Context

//TODO 返回非零空的上下文。代码应该使用context.TODO,当它不清楚使用哪个 Context或它尚不可用时(因为周围的函数尚未扩展为接受Context参数)。TODO 被静态分析工具识别,以确定上下文是否在程序中正确传播。
func TODO() Context

// WithCancel 返回一个新的完成通道的父级的副本。返回的上下文的 Done 通道在返回的取消函数被调用时或父上下文的 Done 通道关闭时关闭,无论哪个先发生。取消这个上下文会释放与它相关的资源,所以只要完成在这个Context 中运行的操作,代码就应该调用 cancel。 
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)

// WithDeadline 返回父上下文的副本,并将截止日期调整为不晚于d。如果父母的截止日期早于d,WithDeadline(parent,d)在语义上等同于父母。当截止日期到期,返回的取消功能被调用时,或者父上下文的完成通道关闭时,返回的上下文的完成通道将关闭,以先发生者为准。取消这个上下文会释放与它相关的资源,所以只要完成在这个Context 中运行的操作,代码就应该调用cancel。
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)

// WithTimeout 返回 WithDeadline(parent, time.Now().Add(timeout)) 。 取消这个上下文可以释放与它相关的资源,因此只要在这个Context 中运行的操作完成,代码就应该立即调用 cancel:
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

// WithValue 返回父键的副本,其中与键关联的值是val。 使用上下文值仅适用于传输进程和 API 的请求范围数据,而不用于将可选参数传递给函数。 
func WithValue(parent Context, key, val interface{}) Context

完整代码

client.go

package main

import (
	"GoCode/example/gRpcDemoOrder/message"
	"context"
	"fmt"
	"google.golang.org/grpc"
	"time"
)

func main() {
	//1、Dail连接
	conn, err := grpc.Dial("localhost:9000", grpc.WithInsecure())
	if err != nil {
		panic(err.Error())
	}
	defer conn.Close()

	orderServiceClient := message.NewOrderServiceClient(conn)

	orderRequest := &message.OrderRequest{OrderId: "202011060001", TimeStamp: time.Now().Unix()}
	orderInfo, err := orderServiceClient.GetOrderInfo(context.Background(), orderRequest)
	if orderInfo != nil {
		fmt.Println(orderInfo.GetOrderId())
		fmt.Println(orderInfo.GetOrderName())
		fmt.Println(orderInfo.GetOrderStatus())
	}
}

server.go

package main

import (
	"GoCode/example/gRpcDemoOrder/message"
	"context"
	"errors"
	"google.golang.org/grpc"
	"net"
	"time"
)

type OrderServiceImpl struct {
}

func (o *OrderServiceImpl) GetOrderInfo(ctx context.Context, request *message.OrderRequest) (*message.OrderInfo, error) {
	orderMap := map[string]message.OrderInfo{
		"202011060001": {OrderId: "202011060001", OrderName: "衣服", OrderStatus: "已付款"},
		"202011060002": {OrderId: "202011060002", OrderName: "零食", OrderStatus: "已付款"},
		"202011060003": {OrderId: "202011060003", OrderName: "食品", OrderStatus: "未付款"},
	}

	var response = &message.OrderInfo{}
	current := time.Now().Unix()

	if request.TimeStamp > current {
		*response = message.OrderInfo{OrderName: "", OrderId: "0", OrderStatus: "订单信息异常"}
	} else {
		result := orderMap[request.OrderId]
		if result.OrderId != "" {
			*response = result
		} else {
			return nil, errors.New("server error")
		}
	}
	return response, nil
}

func main() {
	server := grpc.NewServer()

	message.RegisterOrderServiceServer(server, new(OrderServiceImpl))

	lis, err := net.Listen("tcp", ":9000")

	if err != nil {
		panic(err)
	}
	server.Serve(lis)
}

参考链接

上下文|Context
Golang - 100天从新手到大师

源码地址

源码地址

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值