栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Go语言

go中使用grpc之心得 - go微服务进阶之路

Go语言 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

go中使用grpc之心得 - go微服务进阶之路

文章目录
  • 前言
  • 编写proto
  • 编写业务代码
  • 编写服务端代码
  • 编写客户端
  • 调用
  • 添加拦截器
  • 踩坑环节
    • command-line-arguments: ***: undefined: ***
    • go mod初始化模块管理
    • cannot take the address & cannot call pointer method
    • protoc-gen-go failed :: The import path must contain at least one forward slash ('/') character."
  • 写在最后

前言

上次发文章是什么时候的事儿了,笔者懒癌又发作了。最近这段时间公司进行项目重构,主要是从python往go方面迁移。理由是python项目在目前的web服务中遭遇了一些瓶颈,包括:

  1. 代码规范混乱(写python的都知道,一个人就是一个风格)
  2. 系统运行性能低下(这倒不一定就是python 的锅)
  3. 语言陈旧(原项目采用的是python2)

由于项目是本身是一个微服务的架构,各个服务件本身是通过rpc去调用。原项目本身使用的rpc框架是grpc,在新项目中,仍然继续选择grpc。

grpc本身是支持http2标准化协议的,添加了头信息,可以方便在框架层面对调用做拦截和控制(比如说限流,调用链分析,安全认证等),其protobuffer也被证明是一种十分高效的序列化技术。

当然历史包袱也是有一定原因(没有足够的理由去更换掉grpc)

本文的调用方式仅限于一元调用。

编写proto

这里不详细介绍protobuf的语法了,贴一个简单的data.proto做演示

syntax = "proto3";
package GrpcDemo;
option go_package = "./pb";


service HelloService {
  
  rpc HelloInfo(HelloInfoRequest) returns (HelloInfoReply) {}  // 请求主体
}




// 请求主体结构
message HelloInfoRequest {
  string name = 1;
}
message HelloInfoReply {
  string name = 1;
}

在使用grpc之前,我们必须先通过编写的proto文件生成对应的服务端和客户端代码。对应到go中,自然就是生成go的代码喽。

在这之前,我们需要下载一个编译依赖包:

go get github.com/golang/protobuf/protoc-gen-go

下载完成之后protoc --version 检查一下是否安装成功,如果没有找到protoc命令,检查一下对应的执行文件是否正确下载到GOBIN路径当中,以及执行文件所在路径是否正确添加到环境变量PATH当中。

OK,我们进入到上述编写好的proto文件我在路径,执行
protoc --go_out=plugins=grpc:. data.proto
就可以生成go的grpc客户端和服务端代码,文件名称格式为xxx.pb.go

编写业务代码
package service

import (
	"GrpcDemo/pb"
	"context"
)

type Hello struct {}

func (cls *Hello) HelloInfo(ctx context.Context, pkg *pb.HelloInfoRequest) (*pb.HelloInfoReply, error) {
	return &pb.HelloInfoReply{
		Name: pkg.Name,
	}, nil
}

ctx中包含了此次请求的元信息,类似于python web框架的request实例,这段代码中的函数名HelloInfo和proto文件中的rpc方法名需要一一对应,方法里什么都没有做,只是拿到请求后直接返回了请求体中的Name数据。

编写服务端代码

下一步就是编写服务端代码,直接上代码

package main

import (
	"GrpcDemo/pb"
	"GrpcDemo/service"
	"google.golang.org/grpc"
	"log"
	"net"
)

func main() {
	addres := "localhost:8889"  // 服务地址
	listener, err := net.Listen("tcp", addres)  // 监听地址
	if err != nil {
		log.Println("net listen err ", err)
		return
	}

	s := grpc.NewServer()  // 初始化grpc服务
	pb.RegisterHelloServiceServer(s, &service.Hello{}) // 注册服务
	log.Println("start gRPC listen on addres " + addres)
	if err := s.Serve(listener); err != nil {  // 监听服务,如启动失败则抛出异常
		log.Println("failed to serve...", err)
		return
	}
}

其中pb.RegisterHelloServiceServer为通过proto文件生成的方法。用于注册我们刚刚写的业务代码。方法的命名格式为Register + xxx + Server,其中xxx的部分对应proto中的服务名称。

编写客户端

服务端有了,我们需要一个客户端去调用服务端,同样,直接上代码

package main

import (
	"GrpcDemo/pb"
	"context"
	"fmt"
	"google.golang.org/grpc"
	"log"
)

const (
	address = "localhost:8889"
)

func main() {
	conn, err := grpc.Dial(address, grpc.WithInsecure())  // 建立链接
	if err != nil {
		log.Println("did not connect.", err)
		return
	}
	defer conn.Close()

	client := pb.NewHelloServiceClient(conn)  // 初始化客户端
	ctx := context.Background()  // 初始化元数据

	helloName := &pb.HelloInfoRequest{Name: "chengjian.liu"}  // 构造请求体
	info, err := client.HelloInfo(ctx, helloName)  // 调用helloInfo rpc服务,就像调用普通函数一样
	if err != nil {
		return
	}
	fmt.Println("hello", info.Name)
}
调用

业务代码,服务端,客户端都写好了,我们直接尝试调用一下,首先,启动服务端

接着运行客户端进行调用

OK,调用完成,grpc的一个完整流程到此已经走通了。

添加拦截器

我们在使用rpc服务时,通常需要在进入业务主体之前进行一些前置操作,比如鉴权,加密解密等等,我们不希望这些通用逻辑嵌入到每个业务当中。幸运的是,grpc为我们提供了拦截器的功能,我们可以通过添加拦截器给服务加上上述功能,而不影响到业务本身。

下面就来看看拦截器的写法吧~我们直接在服务端添加一个拦截器

package main

import (
	"GrpcDemo/pb"
	"GrpcDemo/service"
	"context"
	"fmt"
	"google.golang.org/grpc"
	"log"
	"net"
)

// interceptor 拦截器
func interceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
	fmt.Println("拦截到服务: ", info.FullMethod)
	// 继续处理请求
	return handler(ctx, req)
}

func main() {
	...  // 服务端中的重复代码省略
	
	var opts []grpc.ServerOption
	// 注册interceptor
	opts = append(opts, grpc.UnaryInterceptor(interceptor))
	
	s := grpc.NewServer(opts...)

	... // 服务端中的重复代码省略
}

拦截器方法和业务代码本质上差不多,不过其触发时机在所有业务代码之前,并且拦截器可以添加多个。

按照之前的方法启动服务端,在用客户端调用,查看服务端的输出,结果如下

客户端的拦截器流成和服务端基本相似,区别在于注册方法和位置要换成客户端的

var opts []grpc.DialOption
opts = append(opts, grpc.WithUnaryInterceptor(interceptor))
conn, err := grpc.Dial(Address, opts...)
踩坑环节

在编写调试过程中遇到了一些问题,相应的解决方案总结在下面供参考。

command-line-arguments: ***: undefined: ***

同一个包下的两个文件,点击idea的运行按钮或者运行 go run main.go命令时,就会报错,该问题属于go的多文件加载问题,在go ide中以package模式运行代码可以解决,具体解决方案参见:Go常见问题:# command-line-arguments: ***: undefined: ***

go mod初始化模块管理

在初始化项目前,我们首先要了解go mod是如何进行模块管理的,否则就会出现连项目都无法运行的情况,go mod运用详情参见:go mod使用

cannot take the address & cannot call pointer method

详见:Go 挖坑指南: cannot take the address & cannot call pointer method

protoc-gen-go failed :: The import path must contain at least one forward slash (’/’) character."

笔者在用proto文件生成代码时出现的问题,笔者的原因是proto的package文件路径设置有问题,问题详情参见:github issues proto-gen-go failed

写在最后

通常我们在搭建微服务时,rpc经常被用来在服务间进行通信,通过了解grpc,我们迈出了通向微服务道路的坚实一步。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/903138.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号