go语言gRPC框架实战解析

1 grpc中的要点:

1.1 grpc 是什么?

grpc是一个服务器,用于定义服务,指定方法,客户端可以通过他直接调用不同服务器上的方法。轻松创建分布式服务。

1.2 在代码中有什么用?

实现客户端跨语言调用不同服务器服务端的方法

1.3 proto buffers 是什么?

proto buffer 称为协议缓冲区,用于定义结构化数据,并使用编译器,将数据生成指定语言的接口方法,其中包括客户端和服务器代码,还有其他序列化代码;

1.4 proto buffers 如何定义和使用?

结构化定义(定义方法和参数),参考:Protocol Buffers官方文档
由定义生成go代码,使用命令
protoc --go_out=. snowflake.proto

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

protoc -I proto --go_out=plugins=grpc:proto proto/snowflake.proto
其他语言转化
protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR path/to/file.proto

2 grpc实战代码:

使用grpc完成GetName接口,从数据持久化到测试。

2.1 数据库持久化层

用于持久化数据库;实现最基础的底层GetName方法。

package service

import (
	"errors"
	"github.com/gitstliu/go-id-worker"
	"imcs/common/config"
	"strconv"
)

var worker *idworker.IdWorker

//初始化
func init() {
	workerId := config.GetInt64("snowflake.worker-id")
	dataCenterId := config.GetInt64("snowflake.data-center-id")
	worker = &idworker.IdWorker{}
	if err := worker.InitIdWorker(workerId, dataCenterId); err != nil {
		panic(err)
	}
}
func GetName(mess string) (string, error) {
	replee := "worker test +++" + mess
	return replee, nil
}

2.2 业务层

组合底层方法,实现业务功能。
注意:
server中的方法必须同下面proto buffer中定义的GetName方法一致。

package server

import (
	"context"
	"errors"
	"imcs/proto"
	"imcs/snowflake/service"
)

//grpc server
type SnowflakeGRPCServer struct {
}
func (s *SnowflakeGRPCServer) GetName(ctx context.Context, request *proto.SnowflakeRequest) (*proto.SnowflakeResponse, error) {
	response := new(proto.SnowflakeResponse)
	if request.Messs != "" {
		if replee, err := service.GetName(request.Messs); err == nil {
			response.Replee = replee
		} else {
			return nil, err
		}
	} else {
		return nil, errors.New("The count should greater than 0!")
	}
	return response, nil
}

2.3 proto buffer 转化层

定义客户端接口、服务端接口和代码语言,并完成调用;

syntax = "proto3"; // 最新版,支持多种语言转化
option java_package = "cc.iooc.common.rpc.snowflake.proto";
option java_multiple_files = true;

package proto;

service Snowflake { // 定义服务方法
    rpc GetName (SnowflakeRequest) returns (SnowflakeResponse) {
    }
}

//定义参数
message SnowflakeRequest {
    int32 count = 1;
    string messs = 2;
}

message SnowflakeResponse {
    repeated string ids = 1;
    string replee = 2;
}

然后,使用不同的proto buffer 实现接口的语言转化和代码的生成;
操作:在proto文件同级目录下,使用命令
protoc -I proto --go_out=plugins=grpc:proto proto/snowflake.proto
自动生成代码snowflake.pb.go

2.4 客户接口层

动态获取客户,调用转化层的GetName接口;

package client

import (
	"context"
	"google.golang.org/grpc"
	"imcs/common/config"
	"imcs/common/log"
	"imcs/common/rpc"
	"imcs/proto"
)

type SnowflakeGRPCClient struct {
	Address string
}
func NewSnowflakeGRPCClient() *SnowflakeGRPCClient {
	return &SnowflakeGRPCClient{Address: config.GetString("grpc.client.snowflake.address")}
}

func (s *SnowflakeGRPCClient) GetName(mess string) string {
	// 到grpc连接池获取当前客户的grpc连接
	conn, err := rpc.GetConn(s.Address)
	if err != nil {
		log.Errorf("snowflake client: %v", err)
		return "nil"
	}
	// 方法最后,关闭连接
	defer rpc.Close(s.Address, conn)
	response, err := func(conn *grpc.ClientConn) (interface{}, error) {
		// 调用grpc生成的客户端
		client := proto.NewSnowflakeClient(conn)
		// 调用grpc生成的接口及其实现方法
		// 给proto生成的请求对象(SnowflakeRequest)的属性(Mess)设置值
		response, err := client.GetName(context.Background(), &proto.SnowflakeRequest{Messs: mess})
		return response, err
	}(conn)
	if err != nil {
		log.Error(err)
		return "nil"
	}
	// 从生成的相应对象(SnowflakeResponse)中获取属性值(Replee)作为返回值
	return response.(*proto.SnowflakeResponse).Replee
}

2.5 测试层

使用go语言自带的测试类,对客户端获取到的接口进行测试,包括:功能测试、压力测试。
注意:
测试文件的名称和测试方法的名称按规范来,请网上查询。

package client

import (
	"fmt"
	"testing"
)
func TestGetName(t *testing.T) {
	model := NewSnowflakeGRPCClient()
	fmt.Println(model.GetName("name test ====>"))
}

2.6.网关层

用于微服务之间的调用和拦截。
这层可以选配

package server

import (
	"context"
	"encoding/json"
	"errors"
	"imcs/common/response"
	"imcs/proto"
	"imcs/snowflake/service"
)

//api server
type SnowflakeApiServer struct {
}

func (s *SnowflakeApiServer) Request(ctx context.Context, request *proto.GatewayRequest) (*proto.GatewayResponse, error) {
	resp := new(proto.GatewayResponse)
	method := request.Method
	params := map[string]interface{}{}
	err := json.Unmarshal(request.Param, &params)
	if err != nil {
		return nil, err
	}
	var baseResp *response.BaseResponse
	switch method {
	case "snowflake.id":
		baseResp, err = id()
	case "snowflake.ids":
		baseResp, err = ids(params)
	case "snowflake.GetName":
		baseResp, err = GetName(params)
	default:
		return nil, errors.New("snowflake method not found")
	}
	if err == nil {
		if respBytes, err := json.Marshal(*baseResp); err == nil {
			resp.Result = respBytes
			return resp, nil
		} else {
			return nil, err
		}
	}
	return nil, err
}

func GetName(params map[string]interface{}) (r *response.BaseResponse, err error) {
	countInter := params["mess"]
	if mess, ok := countInter.(string); ok {
		if replee, e := service.GetName(mess); e != nil {
			err = e
		} else {
			r = response.NewBaseResponseOkWithData(replee)
		}
	} else {
		err = errors.New("snowflake replee params error")
	}
	return
}
发布了93 篇原创文章 · 获赞 20 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/leinminna/article/details/104364612