首页 > 其他分享 >Golang微服务(一)

Golang微服务(一)

时间:2023-02-20 13:12:10浏览次数:54  
标签:服务 string err proto grpc ctx Golang message

Golang微服务(一)

目录

一、protobuf常规使用及踩坑记录

1.类型映射关系及零值

关于protobuf的文档见:Protocol Buffers Documentation (protobuf.dev),文档中可见protobuf与go语言的类型映射关系,以及各种类型的零值。当message中的没有提供指定字段,接收端并不会抛出异常,而是会使用该字段的零值。见以下测试:

syntax = "proto3";

option go_package = ".;proto";

service Greeter {
  rpc SayHello(HelloRequest) returns (HelloResponse);
}

message HelloRequest {
  string name = 1; // 此处定义请求字段name
}

message HelloResponse {
  string greeting = 1;
}
// server端

package main

import (
	"context"
	"google.golang.org/grpc"
	"learn/grpc_things/proto"
	"net"
)

type Server struct {
	proto.UnimplementedGreeterServer
}

func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloResponse, error) {
	return &proto.HelloResponse{
		Greeting: "Hello, " + request.Name,
	}, nil
}

func main() {
	s := grpc.NewServer()
	proto.RegisterGreeterServer(s, &Server{})
	lis, err := net.Listen("tcp", ":6666")
	if err != nil {
		panic(err)
	}
	err = s.Serve(lis)
	if err != nil {
		panic(err)
	}
}
// 客户端
package main

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"learn/grpc_things/proto"
)

func main() {
	conn, err := grpc.Dial("127.0.0.1:6666", grpc.WithInsecure())
	defer conn.Close()
	if err != nil {
		panic(err)
	}
	c := proto.NewGreeterClient(conn)
	name := proto.HelloRequest{} // 不提供proto文件中定义的Name字段
	resp, errResp := c.SayHello(context.Background(), &name)
	if errResp != nil {
		panic(errResp)
	}
	fmt.Println(resp.Greeting) // 打印服务端的响应:Hello,
}

2.go_package设置

option go_package设置,格式是"生成结果文件存放路径;go项目中的package"。

“生成结果文件存放路径”可以使用".","..","../"之类的标识,当指定路径不存在时则会创建。go_package或者proto中的package用于分别指定当前文件在其同语言中的(类似)作用域,目的应该是确保标识符的唯一性。

3.protobuf的字段编号

在开发中需要保持服务端和客户端proto文件的完全一致,否则可能出现不容易发现的BUG。例如在proto文件中“string name = 1;”中的1指此字段在某个结构中的编号,protobuf通过自动生成其他语言对应的代码完成了一种在具体结构中字段--编号的对应,这样编码时可以通过“编号+值长度+值”代替“键+值”,可以减少用于实际传输的数据量,在另一端解析时则可以通过编号和“字段--编号”的映射顺序取出值并赋给对应的字段。见以下示例说明:

syntax = "proto3";

option go_package = ".;proto";

service Greeter {
  rpc SayHello(HelloRequest) returns (HelloResponse);
}

message HelloRequest {
  string name = 1;
  string url = 2;
  // 如果另一端的proto文件设置为
  // string url = 1;
  // string name = 2;
  // 此时代码可能可以继续运行并干扰调试
}

message HelloResponse {
  string greeting = 1;
}

4.proto文件的import

可以自定义proto文件以供引用或者引用公共proto(google/protobuf/something),在go中引用公共支持的proto结构时引用proto对应的公共包路径即可,例如(import "github.com/golang/protobuf/ptypes/empty")

// base.proto
// 注意,如果想要在go代码中引用base.proto中的结构,该proto文件也需要通过protoc生成go文件
syntax = "proto3";

option go_package = ".;proto";

message Empty {
}

message Pong {
  string res = 1;
}
// hello.proto
syntax = "proto3";

import "base.proto"
// 公共支持:import "google/protobuf/empty.proto";

option go_package = ".;proto";

service Greeter {
  rpc SayHello(HelloRequest) returns (HelloResponse);
  rpc Ping(Empty) returns (Pong);
  // 调用公共支持:rpc Ping(google.protobuf.Empty) returns (Pong);
}

message HelloRequest {
  string name = 1;
}

message HelloResponse {
  string greeting = 1;
}

5.protobuf的message嵌套

当一个message复杂到需要由其他message构成,而不需要将所有message提取至公共作用域时,可以直接在内部定义。

message HelloResponse {
  string greeting = 1;
  message Detail {
    string info = 1;
    string id = 2;
  }
  repeated Detail data = 2;
}

6.protobuf的枚举、map、timestamp

syntax = "proto3";
option go_package = ".;proto";

service Greeter {
  rpc SayHello(HelloRequest) returns (HelloResponse);
}

// 枚举
enum Gender {
  FEMALE = 0;
  MALE = 1;
}

message HelloRequest {
  string name = 1;
  Gender g = 2;
  google.protobuf.Timestamp ts = 3; 
  // timestamp go代码中可以用timestamppb.New()生成时间戳数据
}

message HelloResponse {
  string greeting = 1;
  map<string, int32> rank = 2; // map
}

二、gRPC常规使用及踩坑记录

1.metadata

类似http中的header,为当次调用提供信息。

type MD map[string][]string

使用

// 实例化metadata
md := metadata.New(map[string]string{"key": "value", })
md1 := metadata.Pairs("key1", "value1", "key2", "value2", )
// 发送metadata
ctx := metadata.NewOutgoingContext(context.Background(), md)
response, err := client.RPCThings(ctx, requestThings)
// 接收metadata
md, ok := metadata.FromIncomingContext(ctx)

2.gRPC拦截器

一元拦截器

server端

	myInterceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
		fmt.Println("新请求")
        res, err := handler(ctx, req)
        fmt.Println("新响应")
        return res, err
	} // 实现拦截器逻辑函数

	itc := grpc.UnaryInterceptor(myInterceptor) // 实例化拦截器
	s := grpc.NewServer(itc) // 初始化server时传入拦截器

client端

	ci := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
		start := time.Now()
		err := invoker(ctx, method, req, reply, cc)
		fmt.Println("处理用时:", time.Since(start))
		return err
	} // 实现拦截器逻辑函数

	conn, err := grpc.Dial("127.0.0.1:6666", grpc.WithInsecure(), grpc.WithUnaryInterceptor(ci)) // 实例化拦截器并传入拨号函数

3.gRPC Auth认证

  • 手动实现

// 利用metadata和拦截器实现认证
// client端,在拦截器中修改ctx,为每次请求增加验证信息
	ci := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
		start := time.Now()
		md := metadata.New(map[string]string{"token": "right"})
		ctx = metadata.NewOutgoingContext(context.Background(), md)
		err := invoker(ctx, method, req, reply, cc)
		fmt.Println("处理用时:", time.Since(start))
		return err
	}

	conn, err := grpc.Dial("127.0.0.1:6666", grpc.WithInsecure(), grpc.WithUnaryInterceptor(ci))

// server端,在拦截器中获取metadata并校验
	myInterceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
		fmt.Println("新请求")
		md, ok := metadata.FromIncomingContext(ctx)
		if !ok {
			return resp, status.Errorf(codes.Unauthenticated, "认证失败")
		}
		if authInfo, got := md["token"]; got && strings.Join(authInfo, "") == "right" {
			res, err := handler(ctx, req)
			return res, err
		} else {
			return resp, status.Errorf(codes.Unauthenticated, "认证失败")
		}

	}

	itc := grpc.UnaryInterceptor(myInterceptor)
	s := grpc.NewServer(itc)
  • 使用PerRPCCredentials接口实现

// client端
// 实现PerRPCCredentials接口
type myCdt struct {
}

func (mc myCdt) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
	return map[string]string{"token": "right"}, nil
}

func (mc myCdt) RequireTransportSecurity() bool {
	return false
}

// 拨号
	conn, err := grpc.Dial("127.0.0.1:6666", grpc.WithInsecure(), grpc.WithPerRPCCredentials(myCdt{}))
	

4.gRPC err处理

状态码:grpc/statuscodes.md at master · grpc/grpc · GitHub

// server端返回status err
func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloResponse, error) {
	fmt.Println(request.G)
	return nil, status.Errorf(codes.InvalidArgument, "参数错误:%s", request.Name)
}
// status.Errorf同样基于status.New()方法,只不过在New()的结果上调用了.Err()方法

// client端解析status err
resp, errResp := c.SayHello(ctx, &name)
	if errResp != nil {
		statusErr, ok := status.FromError(errResp)
		if !ok {
			fmt.Println("解析status err失败")
		} else {
			fmt.Println(statusErr.Message(), statusErr.Code())
		}
		return
	}

5.gRPC超时

重在设置超时的原因:防止服务链中的节点单次任务占用资源过多以及负面作用叠加

常规简单处理:

ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)

标签:服务,string,err,proto,grpc,ctx,Golang,message
From: https://www.cnblogs.com/missfxy/p/17136973.html

相关文章

  • 如何基于安防手段进行高速公路服务区的智能化管理?​
    ​高速公路服务区作为高速公路的重要组成部分,能够为出行人员提供加油、如厕、餐饮、休息等刚需服务,一直以来被认为是高速公路重要的辅助产业。而服务区的信息化建设也在经营......
  • 服务器托管与cdn有哪些联系
    1、内容交换它根据内容的可用性、服务器的可用性以及用户的背景,在POP的缓存服务器上,利用应用层交换、流量分类、重定向(ICP、WCCP)等技术,智能地平衡负载流量。2、......
  • 使用 Angular Universal 进行服务器端渲染的防御性编程思路
    如果无法从Angular平台注入所需的正确全局值,则可以避免调用浏览器代码,只要不需要在服务器上访问该代码即可。例如,全局窗口元素的调用通常是为了获取窗口大小或其他一些视......
  • golang 入门(十) 异常处理
    1、recovery捕获异常代码在运行的时候,总会遇到错误。有的时候我们会希望程序遇到错误以后继续运行后面的流程,而不是直接异常退出。在Python中,使用tryexcept组合实现这种需......
  • javaEE005.01 Tomcat简介&启动关闭服务器(WEB服务器的搭建)
    Tomcat简介文章目录​​Tomcat简介​​​​前言​​​​一、Tomcat简介​​​​web服务器有哪些?​​​​Tomcat简介​​​​Tomcat安装、配置、启动​​前言WEB服务器的搭......
  • Golang基础-Runes
    rune与stringTherunetypeinGoisanaliasforint32.Giventhisunderlyingint32type,therunetypeholdsasigned32-bitintegervalue.However,unlikean......
  • golang 面向对象
    1.张老太养了两只猫:一只名字叫小白,今年3岁,白色。还有一只叫小花,今年100岁,花色。请编写一个程序,当用户输入小猫的名字时,就显示该猫的名字,年龄,颜色。如果用户输入的小猫名字......
  • 【FAQ】集成分析服务的常见问题及解决方案
    常见问题一:如何验证Analytics是否上报/接入成功?以及关键日志含义是什么?在初始化AnalyticsSDK前添加SDK日志开关如下:HiAnalyticsTools.enableLog();2.初始化SDK代码......
  • 关于《小公司需要使用微服务架构吗?》的读后感
    最近阅读了一篇文章《小公司需要使用微服务架构吗?》,这篇文章讨论了微服务架构的优缺点,以及微服务架构是否适合小公司。为了蹭一下热度,本文将结合两年半的练习经验,谈谈我对......
  • Python脚本:把本地文件实时更新到服务器上
    #如果没有安装paramiko,用pipinstallparamiko安装importparamiko,os,timedefupdate(addr,usr,pasw,fn,target_path):trans=paramiko.Transport((addr,......