EnvoygRPC和速率限制

Posted ServiceMesher

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了EnvoygRPC和速率限制相关的知识,希望对你有一定的参考价值。

Envoy是专为Cloud Native应用设计的轻量级服务代理,也是为数不多的支持gRPC的代理之一。gRPC是一个基于HTTP/2的高性能RPC(远程过程调用)框架,支持多种语言。

Envoy、gRPC和速率限制

在这篇文章中,我们将使用gRPC和Protocol Buffers构建C++语言版本的Greeter应用,使用Go语言构建另一个gRPC应用,实现Envoy的RateLimitService接口。最后,将Envoy部署为Greeter应用的代理,使用我们的速率限制服务实现反压机制(backpressure)。

gRPC Greeter应用

我们首先安装gRPC和Protobuf,然后构建C++语言版本的Greeter应用。您还可以通过选择文档中列出的其他语言来构建此应用程序; 但是,我将在本文中使用C++。

以下是Greeter应用的示意图。

Envoy、gRPC和速率限制

运行Greeter应用时,终端中会有以下输出:

 
   
   
 
  1. $ ./greeter_server

  2. Server listening on 0.0.0.0:50051

 
   
   
 
  1. $ ./greeter_client

  2. Greeter received: Hello world

升级gRPC Greeter应用

现在,我们通过使用带有请求计数前缀的返回值替代静态的“Hello”前缀,来增强Greeter应用。只需更新 greeter_server.cc文件,如下所示。

 
   
   
 
  1. // Logic and data behind the server's behavior.

  2. class GreeterServiceImpl final : public Greeter::Service {

  3. +  int counter = 0;

  4.   Status SayHello(ServerContext* context, const HelloRequest* request,

  5.                   HelloReply* reply) override {

  6. -    std::string prefix("Hello ");

  7. +    std::string prefix(std::to_string(++counter) + " ");

  8.     reply->set_message(prefix + request->name());

  9.     return Status::OK;

  10.   }

然后重新构建和运行 greeter_server,通过 greeter_client发送请求时你就能看到如下输出。

 
   
   
 
  1. $ for i in {1..3}; do ./greeter_client; sleep 1; done

  2. Greeter received: 1 world

  3. Greeter received: 2 world

  4. Greeter 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两个文件。

 
   
   
 
  1. $ mkdir -p $GOPATH/src/github.com/venilnoronha/rate-limit-service/

  2. $ cd $GOPATH/src/github.com/venilnoronha/rate-limit-service/

  3. $ mkdir -p cmd/server/ && touch cmd/server/main.go

  4. $ mkdir cmd/client/ && touch cmd/client/main.go

引入了所有依赖之后,你将获得一个如下所示的项目结构。注意我只突出列出了这个实验相关的包。

 
   
   
 
  1. ── rate-limit-service

  2.   ├── cmd

  3.     ├── client

  4.       └── main.go

  5.     └── server

  6.         └── main.go

  7.   └── vendor

  8.       ├── github.com

  9.         ├── envoyproxy

  10.           ├── data-plane-api

  11.           └── go-control-plane

  12.         ├── gogo

  13.           ├── googleapis

  14.           └── protobuf

  15.         └── lyft

  16.             └── protoc-gen-validate

  17.       └── google.golang.org

  18.           ├── genproto

  19.           └── grpc

速率限制服务端

现在,我们创建一个简单的gRPC速率限制服务,来限制每秒的请求数(译者注:例子实现是交替限制请求)。

 
   
   
 
  1. package main

  2. import (

  3.    "log"

  4.    "net"

  5.    "golang.org/x/net/context"

  6.    "google.golang.org/grpc"

  7.    "google.golang.org/grpc/reflection"

  8.    rls "github.com/envoyproxy/go-control-plane/envoy/service/ratelimit/v2"

  9. )

  10. // server is used to implement rls.RateLimitService

  11. type server struct{

  12.    // limit specifies if the next request is to be rate limited

  13.    limit bool

  14. }

  15. func (s *server) ShouldRateLimit(ctx context.Context,

  16.        request *rls.RateLimitRequest) (*rls.RateLimitResponse, error) {

  17.    log.Printf("request: %v\n", request)

  18.    // logic to rate limit every second request

  19.    var overallCode rls.RateLimitResponse_Code

  20.    if s.limit {

  21.        overallCode = rls.RateLimitResponse_OVER_LIMIT

  22.        s.limit = false

  23.    } else {

  24.        overallCode = rls.RateLimitResponse_OK

  25.        s.limit = true

  26.    }

  27.    response := &rls.RateLimitResponse{OverallCode: overallCode}

  28.    log.Printf("response: %v\n", response)

  29.        return response, nil

  30. }

  31. func main() {

  32.    // create a TCP listener on port 50052

  33.        lis, err := net.Listen("tcp", ":50052")

  34.        if err != nil {

  35.                log.Fatalf("failed to listen: %v", err)

  36.        }

  37.    log.Printf("listening on %s", lis.Addr())

  38.    // create a gRPC server and register the RateLimitService server

  39.        s := grpc.NewServer()

  40.    rls.RegisterRateLimitServiceServer(s, &server{limit: false})

  41.        reflection.Register(s)

  42.        if err := s.Serve(lis); err != nil {

  43.                log.Fatalf("failed to serve: %v", err)

  44.        }

  45. }

启动 RateLimitService服务之后,终端输出如下。

 
   
   
 
  1. $ go run cmd/server/main.go

  2. 2018/10/27 00:35:28 listening on [::]:50052

速率限制客户端

我们同样创建一个 RateLimitService的客户端来验证服务端的行为。

 
   
   
 
  1. package main

  2. import (

  3.        "log"

  4.    "time"

  5.        "golang.org/x/net/context"

  6.        "google.golang.org/grpc"

  7.    rls "github.com/envoyproxy/go-control-plane/envoy/service/ratelimit/v2"

  8. )

  9. func main() {

  10.        // Set up a connection to the server

  11.        conn, err := grpc.Dial("localhost:50052", grpc.WithInsecure())

  12.        if err != nil {

  13.                log.Fatalf("could not connect: %v", err)

  14.        }

  15.        defer conn.Close()

  16.        c := rls.NewRateLimitServiceClient(conn)

  17.        // Send a request to the server

  18.        ctx, cancel := context.WithTimeout(context.Background(), time.Second)

  19.        defer cancel()

  20.    r, err := c.ShouldRateLimit(ctx, &rls.RateLimitRequest{Domain: "envoy"})

  21.        if err != nil {

  22.                log.Fatalf("could not call service: %v", err)

  23.        }

  24.        log.Printf("response: %v", r)

  25. }

现在让我们通过启动客户端来测试服务端/客户端的交互。

 
   
   
 
  1. $ for i in {1..4}; do go run cmd/client/main.go; sleep 1; done

  2. 2018/10/27 17:32:23 response: overall_code:OK

  3. 2018/10/27 17:32:25 response: overall_code:OVER_LIMIT

  4. 2018/10/27 17:32:26 response: overall_code:OK

  5. 2018/10/27 17:32:28 response: overall_code:OVER_LIMIT

服务端的相关日志。

 
   
   
 
  1. 2018/10/27 17:32:23 request: domain:"envoy"

  2. 2018/10/27 17:32:23 response: overall_code:OK

  3. 2018/10/27 17:32:25 request: domain:"envoy"

  4. 2018/10/27 17:32:25 response: overall_code:OVER_LIMIT

  5. 2018/10/27 17:32:26 request: domain:"envoy"

  6. 2018/10/27 17:32:26 response: overall_code:OK

  7. 2018/10/27 17:32:28 request: domain:"envoy"

  8. 2018/10/27 17:32:28 response: overall_code:OVER_LIMIT

Envoy代理

现在我们引入Envoy代理,它将来自Greeter客户端的请求路由到Greeter服务端,同时使用我们的速率限制服务检查速率。下图描述了我们最终的部署结构。

Envoy、gRPC和速率限制

代理配置

 
   
   
 
  1. static_resources:

  2.  listeners:

  3.  - address:

  4.      socket_address:

  5.        address: 0.0.0.0

  6.        port_value: 9211 # expose proxy on port 9211

  7.    filter_chains:

  8.    - filters:

  9.      - name: envoy.http_connection_manager

  10.        config:

  11.          codec_type: auto

  12.          stat_prefix: ingress_http

  13.          access_log: # configure logging

  14.            name: envoy.file_access_log

  15.            config:

  16.              path: /dev/stdout

  17.          route_config:

  18.            name: greeter_route # configure the greeter service routes

  19.            virtual_hosts:

  20.            - name: service

  21.              domains:

  22.              - "*"

  23.              routes:

  24.              - match:

  25.                  prefix: "/"

  26.                  grpc: {}

  27.                route:

  28.                  cluster: greeter_service

  29.              rate_limits: # enable rate limit checks for the greeter service

  30.                actions:

  31.                - destination_cluster: {}

  32.          http_filters:

  33.          - name: envoy.rate_limit # enable the Rate Limit filter

  34.            config:

  35.              domain: envoy

  36.          - name: envoy.router # enable the Router filter

  37.            config: {}

  38.  clusters:

  39.  - name: greeter_service # register the Greeter server

  40.    connect_timeout: 1s

  41.    type: strict_dns

  42.    lb_policy: round_robin

  43.    http2_protocol_options: {} # enable H2 protocol

  44.    hosts:

  45.    - socket_address:

  46.        address: docker.for.mac.localhost

  47.        port_value: 50051

  48.  - name: rate_limit_service # register the RateLimitService server

  49.    connect_timeout: 1s

  50.    type: strict_dns

  51.    lb_policy: round_robin

  52.    http2_protocol_options: {} # enable H2 protocol

  53.    hosts:

  54.    - socket_address:

  55.        address: docker.for.mac.localhost

  56.        port_value: 50052

  57. rate_limit_service: # define the global rate limit service

  58.  use_data_plane_proto: true

  59.  grpc_service:

  60.    envoy_grpc:

  61.      cluster_name: rate_limit_service

部署Envoy代理

为了部署Envoy代理,我们将上述配置拷贝到 envoy.yaml文件。然后我们使用如下的 Dockerfile构建Docker镜像。

 
   
   
 
  1. FROM envoyproxy/envoy:latest

  2. COPY envoy.yaml /etc/envoy/envoy.yaml

使用如下命令构建镜像:

 
   
   
 
  1. $ docker build -t envoy:grpc .

  2. Sending build context to Docker daemon  74.75kB

  3. Step 1/2 : FROM envoyproxy/envoy:latest

  4. ---> 51fc619e4dc5

  5. Step 2/2 : COPY envoy.yaml /etc/envoy/envoy.yaml

  6. ---> c766ba3d7d09

  7. Successfully built c766ba3d7d09

  8. Successfully tagged envoy:grpc

然后运行代理:

 
   
   
 
  1. $ docker run -p 9211:9211 envoy:grpc

  2. ...

  3. [2018-10-28 02:59:20.469][000008][info][main] [source/server/server.cc:456] starting main dispatch loop

  4. [2018-10-28 02:59:20.553][000008][info][upstream] [source/common/upstream/cluster_manager_impl.cc:135] cm init: all clusters initialized

  5. [2018-10-28 02:59:20.554][000008][info][main] [source/server/server.cc:425] all clusters initialized. initializing init manager

  6. [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。

 
   
   
 
  1.   GreeterClient greeter(grpc::CreateChannel(

  2. -      "localhost:50051", grpc::InsecureChannelCredentials()));

  3. +      "localhost:9211", grpc::InsecureChannelCredentials()));

  4.   std::string user("world");

  5.   std::string reply = greeter.SayHello(user);

最终测试

此时,我们已经有了Greeter服务端、RateLimitService服务和一个Envoy代理,是时候验证整个部署了。为此,我们使用更新后的Greeter客户端发送几个如下所示的请求(译者注:前面Greeter服务端没有停,counter已经到了3)。

 
   
   
 
  1. $ for i in {1..10}; do ./greeter_client; sleep 1; done

  2. Greeter received: 4 world

  3. 14:

  4. Greeter received: RPC failed

  5. Greeter received: 5 world

  6. 14:

  7. Greeter received: RPC failed

  8. Greeter received: 6 world

  9. 14:

  10. Greeter received: RPC failed

  11. Greeter received: 7 world

  12. 14:

  13. Greeter received: RPC failed

  14. Greeter received: 8 world

  15. 14:

  16. Greeter received: RPC failed

如你所见,10个请求中的5个是成功的,交替出现gRPC状态码为 14RPC failed失败请求。这表明速率限制服务按照设计限制了请求,Envoy正确地终止了之后的请求。

结论

这篇文章让你对如何使用Envoy作为应用代理有了一个高层次的认识,也能帮助你理解Envoy的限速过滤器如何跟gRPC协议协同工作。

相关阅读推荐






Istio

IBM Istio

  • 111 Istio

  • 118 Istio上手

  • 1115 Istio

  • 11月22日 Envoy

  • 1129 使Istio

  • 126 Istio mixer -

  • 1213 Istio

  • 1220 Istio使Serverless knative

点击【阅读原文】跳转到ServiceMesher网站上浏览可以查看文中的链接。

Envoy、gRPC和速率限制

  • SOFAMesh(https://github.com/alipay/sofa-mesh)基于Istio的大规模服务网格解决方案

  • SOFAMosn(https://github.com/alipay/sofa-mosn)使用Go语言开发的高性能Sidecar代理

合作社区

参与社区

以下是参与ServiceMesher社区的方式,最简单的方式是联系我!

  • 社区网址:http://www.servicemesher.com

  • Slack:https://servicemesher.slack.com (需要邀请才能加入)

  • GitHub:https://github.com/servicemesher

  • Istio中文文档进度追踪:https://github.com/servicemesher/istio-official-translation

  • Twitter: https://twitter.com/servicemesher

  • 提供文章线索与投稿:https://github.com/servicemesher/trans


以上是关于EnvoygRPC和速率限制的主要内容,如果未能解决你的问题,请参考以下文章

Nginx 限制访问速率

BitTorrent:发送请求的最佳速率?

速率限制系列part2—作用于API网关的速率限制

如何避免使用 tweepy Twitter API 的速率限制?

为啥我的 Ray March 片段着色器反射纹理查找会减慢我的帧速率?

几次请求后超出用户速率限制