gRpc框架

RPC 框架原理

RPC 框架的目标就是让远程服务调用更加简单、透明,RPC 框架负责屏蔽底层的传输方式(TCP 或者 UDP)、序列化方式(XML/Json/ 二进制)和通信细节。服务调用者可以像调用本地接口一样调用远程的服务提供者,而不需要关心底层通信细节和调用过程。

image

业界主流的 RPC 框架整体上分为三类:

  • 支持多语言的 RPC 框架,比较成熟的有 Google 的 gRPC、facebook的Apache、Thrift;
  • 只支持特定语言的 RPC 框架,例如新浪微博的 Motan;
  • 支持服务治理等服务化特性的分布式服务框架,其底层内核仍然是 RPC 框架, 例如阿里的 Dubbo。

gRPC是什么

gRPC 是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计。目前提供 C、Java 和 Go 语言版本,分别是:grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支持.

gRPC 基于 HTTP/2 标准设计,带来诸如双向流、流控、头部压缩、单 TCP 连接上的多复用请求等特。这些特性使得其在移动设备上表现更好,更省电和节省空间占用。

image

安全

HTTP2 规范当使用 TLS 时强制使用 TLS 1.2 及以上的版本,并且在部署上对允许的密码施加一些额外的限制以避免已知的比如需要 SNI 支持的问题。并且期待 HTTP2 与专有的传输安全机制相结合,这些传输机制的规格说明不能提供有意义的建议。

gRPC使用

使用gRPC, 我们可以一次性的在一个.proto文件中定义服务并使用任何支持它的语言去实现客户端和服务端,反过来,它们可以应用在各种场景中,从Google的服务器到你自己的平板电脑—— gRPC帮你解决了不同语言及环境间通信的复杂性。使用protocol buffers还能获得其他好处,包括高效的序列号,简单的IDL以及容易进行接口更新。总之一句话,使用gRPC能让我们更容易编写跨语言的分布式代码。

  • 通过一个 protocol buffers 模式,定义一个简单的带有 Hello World 方法的 RPC 服务。
  • 用你最喜欢的语言(如果可用的话)来创建一个实现了这个接口的服务端。
  • 用你最喜欢的(或者其他你愿意的)语言来访问你的服务端。

关于protocol buffers: https://www.cnblogs.com/zhaohaiyu/p/11826162.html

protubuf文件编写

syntax = "proto3"; 

package hello; 

// 定义一个服务
service Hello {
    // SayHello 方法
    rpc SayHello (HelloRequest) returns (HelloRenponse) {}
}

// 请求输入值
message HelloRequest {
    string name = 1;
}

// 请求返回值
message HelloResponse {
    string message = 1;
}

golang创建grpc server

下载包:

  1. go get -u google.golang.org/grpc
  2. https://github.com/google/protobuf/releases在线protoc放到GOPATH/bin下
  3. go get -u github.com/golang/protobuf/protoc-gen-go

执行:

protoc  --go_out=plugins=grpc:. .\hello.proto

go server代码

package main

import (
    "fmt"
    "net"
     pb "test/demo10/server/hello"

    "golang.org/x/net/context"
    "google.golang.org/grpc"
    "google.golang.org/grpc/reflection"
)

type server struct{}

func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
    fmt.Println("被调用!!!")
    return &pb.HelloResponse{Message: "hello ---> " + in.Name}, nil
}

func main() {
    // 监听本地的8848端口
    lis, err := net.Listen("tcp", "localhost:8848")
    if err != nil {
        fmt.Printf("listen failed: %v", err)
        return
    }
    s := grpc.NewServer() // 创建gRPC服务器
    pb.RegisterHelloServer(s, &server{}) // 在gRPC服务端注册服务

    reflection.Register(s) //在给定的gRPC服务器上注册服务器反射服务
    // Serve方法在lis上接受传入连接,为每个连接创建一个ServerTransport和server的goroutine。
    // 该goroutine读取gRPC请求,然后调用已注册的处理程序来响应它们。
    err = s.Serve(lis)
    if err != nil {
        fmt.Printf("failed to serve: %v", err)
        return
    }
}

golang创建grpc client

执行:

protoc  --go_out=plugins=grpc:. .\hello.proto

go client代码

package main

import (
    "context"
    "fmt"

    pb "test/demo10/client/hello"
    "google.golang.org/grpc"
)

func main() {
    // 连接服务器
    conn, err := grpc.Dial("localhost:8848", grpc.WithInsecure())
    if err != nil {
        fmt.Printf("connect faild: %v", err)
    }
    defer conn.Close()

    c := pb.NewHelloClient(conn)
    // 调用SayHello
    r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: "zhaohaiyu"})
    if err != nil {
        fmt.Printf("sayHello failed: %v", err)
    }
    fmt.Printf("SayHello: %s \n", r.Message)
}

结果:

SayHello: hello ---> zhaohaiyu

python创建grpc client

使用python客户端调用golang服务端的方法

下载依赖:

pip install grpcio
pip install protobuf
pip install grpcio_tools

执行:

python -m grpc_tools.protoc -I ./ --python_out=./ --grpc_python_out=./ hello.proto

python client代码

import grpc
import hello_pb2
import hello_pb2_grpc


def run():
    with grpc.insecure_channel('localhost:8848') as channel:
        stub = hello_pb2_grpc.HelloStub(channel)
        res = stub.SayHello(hello_pb2.HelloRequest(name="赵海宇"))
    print(res.message)

if __name__ == '__main__':
    run()

结果:

python ./main.go
hello ---> 赵海宇

gprc的haeder

  • grpc是基于http2.0的rpc框架 -
  • grpc对于http头部传递数据进行了封装 metadata,单独抽象了一个包 google.golang.org/grpc/metadata -
  • type MD map[string][]string 其实就是一个map

客户端发送方式一:

// 创建md 并加入ctx
    md := metadata.Pairs("key1","value1","key2","value2")
    ctx := metadata.NewOutgoingContext(context.Background(),md)
    // 从ctx中拿出md
    md,_ = metadata.FromOutgoingContext(ctx)
    newMd := metadata.Pairs("key3","value3")
    ctx = metadata.NewOutgoingContext(ctx,metadata.Join(md,newMd))

客户端发送方式二:

    ctx := context.Background()
    ctx = metadata.AppendToOutgoingContext(ctx,"key1","value1","key2","value2")
    ctx = metadata.AppendToOutgoingContext(ctx,"key3","value3")

服务端接收:

md,ok := metadata.FromIncomingContext(ctx)

实例:

  1. server:
package main

import (
    "fmt"
    "net"
    pb "test/demo13/server/hello"

    "github.com/grpc-ecosystem/grpc-gateway/examples/clients/responsebody"
    "github.com/uber/jaeger-client-go/crossdock/client"
    "golang.org/x/net/context"
    "google.golang.org/grpc"
    "google.golang.org/grpc/metadata"
    "google.golang.org/grpc/reflection"
)

type server struct{}

func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
    
    md,ok := metadata.FromIncomingContext(ctx)
    if ok {
        fmt.Println(md)
    }
    return &pb.HelloResponse{Message: "hello ---> " + in.Name}, nil
}

func main() {
    // 监听本地的8848端口
    lis, err := net.Listen("tcp", "localhost:8848")
    if err != nil {
        fmt.Printf("listen failed: %v", err)
        return
    }
    s := grpc.NewServer() // 创建gRPC服务器
    pb.RegisterHelloServer(s, &server{}) // 在gRPC服务端注册服务

    reflection.Register(s) //在给定的gRPC服务器上注册服务器反射服务
    // Serve方法在lis上接受传入连接,为每个连接创建一个ServerTransport和server的goroutine。
    // 该goroutine读取gRPC请求,然后调用已注册的处理程序来响应它们。
    err = s.Serve(lis)
    if err != nil {
        fmt.Printf("failed to serve: %v", err)
        return
    }

}
  1. client
package main

import (
    "context"
    "fmt"

    pb "test/demo13/client/hello"

    "google.golang.org/grpc"
    "google.golang.org/grpc/metadata"
)

func main() {
    // 连接服务器
    conn, err := grpc.Dial("localhost:8848", grpc.WithInsecure())
    if err != nil {
        fmt.Printf("faild to connect: %v", err)
    }
    defer conn.Close()

    c := pb.NewHelloClient(conn)
    // 调用服务端的SayHello
    ctx := context.Background()
    ctx = metadata.AppendToOutgoingContext(ctx,"zhyyz","961119")
    r, err := c.SayHello(ctx, &pb.HelloRequest{Name: "zhaohaiyu"})
    if err != nil {
        fmt.Printf("sayHello failed: %v", err)
    }
    fmt.Printf("SayHello: %s \n", r.Message)
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352