golang使用grpc开发

作者:zarte    发布时间: 2021-02-12

golanggrpc微服务

gRPC

A high-performance, open-source universal RPC framework

在 gRPC 里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使得您能够更容易地创建分布式应用和服务。与许多 RPC 系统类似,gRPC 也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法。

什么是protobuf

Protobuf(Protocol Buffer)是google 的一种数据交换的格式,它独立于语言,独立于平台。google 提供了多种语言的实现:java、c#、c++、go 和 python,每一种实现都包含了相应语言的编译器以及库文件。由于它是一种二进制的格式,比使用 xml 进行数据交换快许多。可以把它用于分布式应用之间的数据通信或者异构环境下的数据交换。作为一种效率和兼容性都很优秀的二进制数据传输格式,可以用于诸如网络传输、配置文件、数据存储等诸多领域。

什么是protoc

protoc是protobuf文件(.proto)的编译器(参考链接),可以借助这个工具把 .proto 文件转译成各种编程语言对应的源码,包含数据类型定义、调用接口等。

什么是protoc-gen-go

protoc-gen-go是protobuf编译插件系列中的Go版本,根据写好的proto文件生成go语言的接口库

环境搭建

https://github.com/protocolbuffers/protobuf 下载编译器并添加到系统环境变量中
goloang安装protoc编译器插件即可 go get -u github.com/golang/protobuf/protoc-gen-go

export PATH=$PATH:$GOPATH/bin

安装grpc-go 库

go get google.golang.org/grpc

开始编写

定义proto文件

首先第一步是使用protocol buffer定义gRPC服务还有方法的请求和响应类型,你可以在下载的示例代码examples/route_guide/routeguide/route_guide.proto中看到完整的.proto文件。
下面是一个简单示例


syntax = "proto3";

// user 包
package user;

// 指定 go 的包路径及包名
// option go_package="app/services;services";
// 指定 php 的命名空间
// option php_namespace="App\\Services";

// User 服务及服务接口的定义
service User {
    rpc UserIndex(UserIndexRequest) returns (UserIndexResponse) {}
    rpc UserView(UserViewRequest) returns (UserViewResponse) {}
    rpc UserPost(UserPostRequest) returns (UserPostResponse) {}
    rpc UserDelete(UserDeleteRequest) returns (UserDeleteResponse) {}
}

// 枚举类型
emun EnumUserSex {
    SEX_INIT = 0; // 枚举类型必须以 0 起始
    SEX_MALE = 1;
    SEX_FEMALE = 2;
}

// 用户实体模型
message UserEntity {
    string name = 1;
    int32 age = 2;
}

// User 服务的各个接口的请求/响应结构
message UserIndexRequest {
    int32 page = 1;
    int32 page_size = 2;
}
message UserIndexResponse {    
    int32 err = 1;    
    string msg = 2;    
    // 返回一个 UserEntity 对象的列表数据    
    repeated UserEntity data = 3;
}


详细说明

要定义服务,你需要在.proto文件中指定一个具名的service

service RouteGuide {
   ...
}

然后在服务定义中再来定义rpc方法,指定他们的请求和响应类型。gRPC允许定义四种类型的服务方法,这四种服务方法都会应用到我们的RouteGuide服务中。

  • 一个简单的RPC,客户端使用存根将请求发送到服务器,然后等待响应返回,就像普通的函数调用一样。
    // 获得给定位置的特征rpc 
    rpc GetFeature(Point) returns (Feature) {}
    
  • 服务器端流式RPC,客户端向服务器发送请求,并获取流以读取回一系列消息。客户端从返回的流中读取,直到没有更多消息为止。如示例所示,可以通过将stream关键字放在响应类型之前来指定服务器端流方法。
    //获得给定Rectangle中可用的特征。结果是
    //流式传输而不是立即返回
    //因为矩形可能会覆盖较大的区域并包含大量特征。
    rpc ListFeatures(Rectangle) returns (stream Feature) {}
    
  • 客户端流式RPC,其中客户端使用gRPC提供的流写入一系列消息并将其发送到服务器。客户端写完消息后,它将等待服务器读取所有消息并返回其响应。通过将stream关键字放在请求类型之前,可以指定客户端流方法。
    // 接收路线上被穿过的一系列点位, 当行程结束时
    // 服务端会返回一个RouteSummary类型的消息.
    rpc RecordRoute(stream Point) returns (RouteSummary) {}
    
  • 双向流式RPC,双方都使用读写流发送一系列消息。这两个流是独立运行的,因此客户端和服务器可以按照自己喜欢的顺序进行读写:例如,服务器可以在写响应之前等待接收所有客户端消息,或者可以先读取消息再写入消息,或其他一些读写组合。每个流中的消息顺序都会保留。您可以通过在请求和响应之前都放置stream关键字来指定这种类型的方法。
    //接收路线行进中发送过来的一系列RouteNotes类型的消息,同时也接收其他RouteNotes(例如:来自其他用户)
    rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
    

文件中也需要所有请求和响应类型的protocol buffer消息类型定义。比如说下面的Point消息类型:

// Points被表示为E7表示形式中的经度-纬度对。
//(度数乘以10 ** 7并四舍五入为最接近的整数)。
// 纬度应在+/- 90度范围内,而经度应在// 范围+/- 180度(含)
message Point {
  int32 latitude = 1;
  int32 longitude = 2;
}

生成接口库

protoc -I routeguide/ user.proto --go_out=plugins=grpc:routeguide
运行命令后会在routeguide目录下生成user.pb.go文件 -I 指定导入目录可以指定多次 命令不存在请先安装protoc参环境搭建操作。

gRPC服务端

实现服务

//引入上面生成的接口库文件
import "minirpc/rpc/interf"

//实现User服务的业务对象
type UserServer struct {
}
//实现User服务接口的所有方法
func (u *UserServer) UserIndex(ctx context.Context, in *rpc.UserIndexRequest) 
(*rpc.UserIndexResponse, error) { 
    log.Printf("receive user index request:page %d page_size %d", in.Page, 
in.PageSize)       
    return &rpc.UserIndexResponse{              
        Err: 0,              
        Msg: "success",              
        Data: []*rpc.UserEntity{                     
            {Name: "aaaa", Age: 28},                     
            {Name: "bbbb", Age: 1},             
        },      
    }, 
 nil}

//UserView 获取详情
func (u *UserServer) UserView(ctx context.Context, in *rpc.UserViewRequest) 
(*rpc.UserViewResponse, error) {
    log.Printf("receive user uid request:uid %d", in.Uid)
    return &rpc.UserViewResponse{       
        Err: 0,       
        Msg: "success",       
        Data: &rpc.UserEntity{              
            Name: "aaaa", Age: 28,       
        },
    }, nil
}
//UserPost 提交数据
func (u *UserServer) UserPost(ctx context.Context, in *rpc.UserPostRequest) 
(*rpc.UserPostResponse, error) {       
    log.Printf("receive user uid request:name %s password:%s,age:%d", in.Name, 
in.Password, in.Age)       
    return &rpc.UserPostResponse{              
        Err: 0,              
        Msg: "success",       
    }, nil
}
//UserDelete 删除数据
func (u *UserServer) UserDelete(ctx context.Context, in *rpc.UserDeleteRequest) 
(*rpc.UserDeleteResponse, error) {
    log.Printf("receive user uid request:uid %d", in.Uid)
    return &rpc.UserDeleteResponse{
        Err: 0,       
        Msg: "success",
    }, nil
}

//启动微服务
func tesrpc()  {       
    lis, err := net.Listen("tcp", ":1234")       
    if err != nil {              
        log.Fatal("failed to listen", err)       
    }      
    //创建rpc服务            
    grpcServer := grpc.NewServer()      
    //为User服务注册业务实现 将User服务绑定到RPC服务器上            
    rpc.RegisterUserServer(grpcServer, &UserServer{})      
    //注册反射服务, 这个服务是CLI使用的, 跟服务本身没有关系            
    reflection.Register(grpcServer)       
    if err := grpcServer.Serve(lis); err != nil {              
         log.Fatal("faild to server,", err)       
    }
}

grpc客户端

测试连接服务

func testrpc()  {       
    //建立链接            
    conn, err := grpc.Dial("127.0.0.1:1234", grpc.WithInsecure())       
    if err != nil {              
        log.Fatal("did not connect", err)      
    }       
    defer conn.Close()       
    userClient := rpc.NewUserClient(conn)       
    //设定请求超时时间 3s       
    ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)       
    defer cancel()       
    //UserIndex 请求            
    userIndexResponse, err := userClient.UserIndex(ctx, &rpc.UserIndexRequest{              
        Page:     1,              
        PageSize: 12,       
    })       
    if err != nil {              
        log.Printf("user index could not greet: %v", err)       
    }       
    if 0 == userIndexResponse.Err {              
        log.Printf("user index success: %s", userIndexResponse.Msg)              
        // 包含 UserEntity 的数组列表                        
        userEntityList := userIndexResponse.Data              
        for _, row := range userEntityList {                     
            fmt.Println(row.Name, row.Age)              
        }       
    } else {             
        log.Printf("user index error: %d", userIndexResponse.Err)      
    }       
    // UserView 请求            
    userViewResponse, err := userClient.UserView(ctx, &rpc.UserViewRequest{Uid: 1})       
    if err != nil {              
        log.Printf("user view could not greet: %v", err)       
    }      
    if 0 == userViewResponse.Err {              
        log.Printf("user view success: %s", userViewResponse.Msg)              
        userEntity := userViewResponse.Data              
        fmt.Println(userEntity.Name, userEntity.Age)      
    } else {              
        log.Printf("user view error: %d", userViewResponse.Err)       
    }      
    // UserPost 请求            
    userPostReponse, err := userClient.UserPost(ctx, &rpc.UserPostRequest{Name: "big_cat", Password: "123456", Age: 29})       
    if err != nil {              
        log.Printf("user post could not greet: %v", err)      
    }       
    if 0 == userPostReponse.Err {              
        log.Printf("user post success: %s", userPostReponse.Msg)       
    } else {              
        log.Printf("user post error: %d", userPostReponse.Err)       
    }      
    // UserDelete 请求            
    userDeleteReponse, err := userClient.UserDelete(ctx, &rpc.UserDeleteRequest{Uid: 1})       
    if err != nil {              
        log.Printf("user delete could not greet: %v", err)       
    }       
    if 0 == userDeleteReponse.Err {              
        log.Printf("user delete success: %s", userDeleteReponse.Msg)       
    } else {              
        log.Printf("user delete error: %d", userDeleteReponse.Err)       
    }
}

结束

服务端输出(不要在意时间-.-)
Img

客户端输出
Img

上一篇:  mysql InnoDB与MyISAM引擎插入性能测试

下一篇:  consul使用

加载更多