Envoy、gRPC和速率限制

做者 Venil Noronha | 譯者 王全根 | 審校者 楊傳勝 王凱 | 2500字 | 閱讀大約須要5分鐘

Envoy是專爲Cloud Native應用設計的輕量級服務代理,也是爲數很少的支持gRPC的代理之一。gRPC是一個基於HTTP/2的高性能RPC(遠程過程調用)框架,支持多種語言。html

在這篇文章中,咱們將使用gRPC和Protocol Buffers構建C++語言版本的Greeter應用,使用Go語言構建另外一個gRPC應用,實現Envoy的RateLimitService接口。最後,將Envoy部署爲Greeter應用的代理,使用咱們的速率限制服務實現反壓機制(backpressure)。git

gRPC Greeter應用

咱們首先安裝gRPCProtobuf,而後構建C++語言版本的Greeter應用。您還能夠經過選擇文檔中列出的其餘語言來構建此應用程序; 可是,我將在本文中使用C++。github

如下是Greeter應用的示意圖。golang

運行Greeter應用時,終端中會有如下輸出:docker

$ ./greeter_serverServer listening on 0.0.0.0:50051複製代碼
$ ./greeter_clientGreeter received: Hello world複製代碼

升級gRPC Greeter應用

如今,咱們經過使用帶有請求計數前綴的返回值替代靜態的「Hello」前綴,來加強Greeter應用。只需更新greeter_server.cc文件,以下所示。api

// Logic and data behind the server's behavior. class GreeterServiceImpl final : public Greeter::Service {+ int counter = 0; Status SayHello(ServerContext* context, const HelloRequest* request, HelloReply* reply) override {- std::string prefix("Hello ");+ std::string prefix(std::to_string(++counter) + " "); reply->set_message(prefix + request->name()); return Status::OK; }複製代碼

而後從新構建和運行greeter_server,經過greeter_client發送請求時你就能看到以下輸出。bash

$ for i in {1..3}; do ./greeter_client; sleep 1; doneGreeter received: 1 worldGreeter received: 2 worldGreeter received: 3 world複製代碼

簡單速率限制服務

接下來,咱們經過擴展Envoy的RateLimitService原型接口,用Go語言實現一個簡單的速率限制服務。爲此,咱們建立一個名爲rate-limit-service的Go項目,並引入Envoy的go-control-plane和其它相關依賴。go-control-plane項目爲Envoy原型提供了Go語言綁定。爲了後續實現速率限制服務,咱們還需建立cmd/server/main.gocmd/client/main.go兩個文件。框架

$ mkdir -p $GOPATH/src/github.com/venilnoronha/rate-limit-service/$ cd $GOPATH/src/github.com/venilnoronha/rate-limit-service/$ mkdir -p cmd/server/ && touch cmd/server/main.go$ mkdir cmd/client/ && touch cmd/client/main.go複製代碼

引入了全部依賴以後,你將得到一個以下所示的項目結構。注意我只突出列出了這個實驗相關的包。dom

── rate-limit-service   ├── cmd   │   ├── client   │   │   └── main.go   │   └── server   │       └── main.go   └── vendor       ├── github.com       │   ├── envoyproxy       │   │   ├── data-plane-api       │   │   └── go-control-plane       │   ├── gogo       │   │   ├── googleapis       │   │   └── protobuf       │   └── lyft       │       └── protoc-gen-validate       └── google.golang.org           ├── genproto           └── grpc複製代碼

速率限制服務端

如今,咱們建立一個簡單的gRPC速率限制服務,來限制每秒的請求數(譯者注:例子實現是交替限制請求)。socket

package main​import (    "log"    "net"    "golang.org/x/net/context"    "google.golang.org/grpc"    "google.golang.org/grpc/reflection"    rls "github.com/envoyproxy/go-control-plane/envoy/service/ratelimit/v2")​// server is used to implement rls.RateLimitServicetype server struct{    // limit specifies if the next request is to be rate limited    limit bool}​func (s *server) ShouldRateLimit(ctx context.Context,        request *rls.RateLimitRequest) (*rls.RateLimitResponse, error) {    log.Printf("request: %v\n", request)​    // logic to rate limit every second request    var overallCode rls.RateLimitResponse_Code    if s.limit {        overallCode = rls.RateLimitResponse_OVER_LIMIT        s.limit = false    } else {        overallCode = rls.RateLimitResponse_OK        s.limit = true    }​    response := &rls.RateLimitResponse{OverallCode: overallCode}    log.Printf("response: %v\n", response)        return response, nil}​func main() {    // create a TCP listener on port 50052        lis, err := net.Listen("tcp", ":50052")        if err != nil {                log.Fatalf("failed to listen: %v", err)        }    log.Printf("listening on %s", lis.Addr())​    // create a gRPC server and register the RateLimitService server        s := grpc.NewServer()    rls.RegisterRateLimitServiceServer(s, &server{limit: false})        reflection.Register(s)        if err := s.Serve(lis); err != nil {                log.Fatalf("failed to serve: %v", err)        }}複製代碼

啓動RateLimitService服務以後,終端輸出以下。

$ go run cmd/server/main.go2018/10/27 00:35:28 listening on [::]:50052複製代碼

速率限制客戶端

咱們一樣建立一個RateLimitService的客戶端來驗證服務端的行爲。

package main​import (        "log"    "time"        "golang.org/x/net/context"        "google.golang.org/grpc"    rls "github.com/envoyproxy/go-control-plane/envoy/service/ratelimit/v2")​func main() {        // Set up a connection to the server        conn, err := grpc.Dial("localhost:50052", grpc.WithInsecure())        if err != nil {                log.Fatalf("could not connect: %v", err)        }        defer conn.Close()        c := rls.NewRateLimitServiceClient(conn)​        // Send a request to the server        ctx, cancel := context.WithTimeout(context.Background(), time.Second)        defer cancel()    r, err := c.ShouldRateLimit(ctx, &rls.RateLimitRequest{Domain: "envoy"})        if err != nil {                log.Fatalf("could not call service: %v", err)        }        log.Printf("response: %v", r)}複製代碼

如今讓咱們經過啓動客戶端來測試服務端/客戶端的交互。

$ for i in {1..4}; do go run cmd/client/main.go; sleep 1; done2018/10/27 17:32:23 response: overall_code:OK2018/10/27 17:32:25 response: overall_code:OVER_LIMIT2018/10/27 17:32:26 response: overall_code:OK2018/10/27 17:32:28 response: overall_code:OVER_LIMIT複製代碼

服務端的相關日誌。

2018/10/27 17:32:23 request: domain:"envoy"2018/10/27 17:32:23 response: overall_code:OK2018/10/27 17:32:25 request: domain:"envoy"2018/10/27 17:32:25 response: overall_code:OVER_LIMIT2018/10/27 17:32:26 request: domain:"envoy"2018/10/27 17:32:26 response: overall_code:OK2018/10/27 17:32:28 request: domain:"envoy"2018/10/27 17:32:28 response: overall_code:OVER_LIMIT複製代碼

Envoy代理

如今咱們引入Envoy代理,它未來自Greeter客戶端的請求路由到Greeter服務端,同時使用咱們的速率限制服務檢查速率。下圖描述了咱們最終的部署結構。

代理配置

咱們使用以下Envoy配置來註冊Greeter和RateLimitService服務並啓用限速檢查。注意,因爲咱們是將Envoy部署在Docker for Mac上,本地部署的服務是經過docker.for.mac.localhost地址引用的。

static_resources:  listeners:  - address:      socket_address:        address: 0.0.0.0        port_value: 9211 # expose proxy on port 9211 filter_chains: - filters: - name: envoy.http_connection_manager config: codec_type: auto stat_prefix: ingress_http access_log: # configure logging name: envoy.file_access_log config: path: /dev/stdout route_config: name: greeter_route # configure the greeter service routes virtual_hosts: - name: service domains: - "*" routes: - match: prefix: "/" grpc: {} route: cluster: greeter_service rate_limits: # enable rate limit checks for the greeter service actions: - destination_cluster: {} http_filters: - name: envoy.rate_limit # enable the Rate Limit filter config: domain: envoy - name: envoy.router # enable the Router filter config: {} clusters: - name: greeter_service # register the Greeter server connect_timeout: 1s type: strict_dns lb_policy: round_robin http2_protocol_options: {} # enable H2 protocol hosts: - socket_address: address: docker.for.mac.localhost port_value: 50051 - name: rate_limit_service # register the RateLimitService server connect_timeout: 1s type: strict_dns lb_policy: round_robin http2_protocol_options: {} # enable H2 protocol hosts: - socket_address: address: docker.for.mac.localhost port_value: 50052rate_limit_service: # define the global rate limit service use_data_plane_proto: true grpc_service: envoy_grpc: cluster_name: rate_limit_service複製代碼

部署Envoy代理

爲了部署Envoy代理,咱們將上述配置拷貝到envoy.yaml文件。而後咱們使用以下的Dockerfile構建Docker鏡像。

FROM envoyproxy/envoy:latestCOPY envoy.yaml /etc/envoy/envoy.yaml複製代碼

使用以下命令構建鏡像:

$ docker build -t envoy:grpc .Sending build context to Docker daemon  74.75kBStep 1/2 : FROM envoyproxy/envoy:latest ---> 51fc619e4dc5Step 2/2 : COPY envoy.yaml /etc/envoy/envoy.yaml ---> c766ba3d7d09Successfully built c766ba3d7d09Successfully tagged envoy:grpc複製代碼

而後運行代理:

$ docker run -p 9211:9211 envoy:grpc...[2018-10-28 02:59:20.469][000008][info][main] [source/server/server.cc:456] starting main dispatch loop[2018-10-28 02:59:20.553][000008][info][upstream] [source/common/upstream/cluster_manager_impl.cc:135] cm init: all clusters initialized[2018-10-28 02:59:20.554][000008][info][main] [source/server/server.cc:425] all clusters initialized. initializing init manager[2018-10-28 02:59:20.554][000008][info][config] [source/server/listener_manager_impl.cc:908] all dependencies initialized. starting workers複製代碼

更新Greeter客戶端

因爲要使用Envoy路由Greeter客戶端的請求,咱們將客戶端代碼中的服務端端口從50051改成9211,並從新build。

GreeterClient greeter(grpc::CreateChannel(-      "localhost:50051", grpc::InsecureChannelCredentials()));+      "localhost:9211", grpc::InsecureChannelCredentials()));   std::string user("world");   std::string reply = greeter.SayHello(user);複製代碼

最終測試

此時,咱們已經有了Greeter服務端、RateLimitService服務和一個Envoy代理,是時候驗證整個部署了。爲此,咱們使用更新後的Greeter客戶端發送幾個以下所示的請求(譯者注:前面Greeter服務端沒有停,counter已經到了3)。

$ for i in {1..10}; do ./greeter_client; sleep 1; doneGreeter received: 4 world14:Greeter received: RPC failedGreeter received: 5 world14:Greeter received: RPC failedGreeter received: 6 world14:Greeter received: RPC failedGreeter received: 7 world14:Greeter received: RPC failedGreeter received: 8 world14:Greeter received: RPC failed複製代碼

如你所見,10個請求中的5個是成功的,交替出現gRPC狀態碼爲14RPC failed失敗請求。這代表速率限制服務按照設計限制了請求,Envoy正確地終止了以後的請求。

結論

這篇文章讓你對如何使用Envoy做爲應用代理有了一個高層次的認識,也能幫助你理解Envoy的限速過濾器如何跟gRPC協議協同工做。

相關文章
相關標籤/搜索