天天看點

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
}
           

繼續閱讀