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, ¶ms)
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
}