使用Envoy 作Sidecar Proxy的微服务模式-5.rate limiter

43次阅读

共计 3931 个字符,预计需要花费 10 分钟才能阅读完成。

本博客是深入研究 Envoy Proxy 和 Istio.io 以及它如何实现更优雅的方式来连接和管理微服务系列文章的一部分。
这是接下来几个部分的想法(将在发布时更新链接):

断路器(第一部分)
重试 / 超时(第二部分)
分布式跟踪(第三部分)
Prometheus 的指标收集(第四部分)
rate limiter(第五部分)

第五部分 – rate limiter
Envoy ratelimit filters
Envoy 通过两个过滤器与 Ratelimit 服务集成:

Network Level Filter:envoy 为安装过滤器的侦听器上的每个新连接调用 Ratelimit 服务。这样,您可以对通过侦听器的每秒连接进行速率限制。
HTTP Level Filter:Envoy 为安装过滤器的侦听器上的每个新请求调用 Ratelimit 服务,路由表指定应调用 Ratelimit 服务。许多工作都在扩展 HTTP 过滤器的功能。

envoy 配置 启用 http rate limiter
http rate limiter 当请求的路由或虚拟主机具有与过滤器阶段设置匹配的一个或多个速率限制配置时,HTTP 速率限制过滤器将调用速率限制服务。该路由可以选择包括虚拟主机速率限制配置。多个配置可以应用于请求。每个配置都会导致将描述符发送到速率限制服务。
如果调用速率限制服务,并且任何描述符的响应超出限制,则返回 429 响应。速率限制过滤器还设置 x -envoy-ratelimited 标头。
果在呼叫速率限制服务中出现错误或速率限制服务返回错误并且 failure_mode_deny 设置为 true,则返回 500 响应。
全部的配置如下:
envoy.yaml: |-
static_resources:
listeners:
– address:
socket_address:
address: 0.0.0.0
port_value: 8000
filter_chains:
– filters:
– name: envoy.http_connection_manager
config:
codec_type: auto
stat_prefix: ingress_http
access_log:
– name: envoy.file_access_log
config:
path: “/dev/stdout”
format: “[ACCESS_LOG][%START_TIME%] \”%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%\” %RESPONSE_CODE% %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% \”%REQ(X-FORWARDED-FOR)%\” \”%REQ(USER-AGENT)%\” \”%REQ(X-REQUEST-ID)%\” \”%REQ(:AUTHORITY)%\” \”%UPSTREAM_HOST%\” \”%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%\”\n”
route_config:
name: local_route
virtual_hosts:
– name: gateway
domains:
– “*”
routes:
– match:
prefix: “/cost”
route:
cluster: cost
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
config: {}
clusters:
– name: cost
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
hosts:
– socket_address:
address: cost.sgt
port_value: 80
– name: rate_limit_cluster
type: strict_dns
connect_timeout: 0.25s
lb_policy: round_robin
http2_protocol_options: {}
hosts:
– socket_address:
address: limiter.sgt
port_value: 80
rate_limit_service:
grpc_service:
envoy_grpc:
cluster_name: rate_limit_cluster
timeout: 0.25s
admin:
access_log_path: “/dev/null”
address:
socket_address:
address: 0.0.0.0
port_value: 9000

通过配置文件可以看出,本 demo 设置的是一个全局的 http filter rate limiter。
尽管分布式熔断通常在控制分布式系统中的吞吐量方面非常有效,但有时它不是非常有效并且需要全局速率限制。最常见的情况是当大量主机转发到少量主机并且平均请求延迟较低时(例如,对数据库服务器的连接 / 请求)。如果目标主机已备份,则下游主机将淹没上游群集。在这种情况下,在每个下游主机上配置足够严格的断路限制是非常困难的,这样系统在典型的请求模式期间将正常运行,但在系统开始出现故障时仍能防止级联故障。全局速率限制是这种情况的一个很好的解决方案。
编写 rate limiter 服务
Envoy 直接通过 gRPC 与速率限制服务集成。Envoy 要求速率限制服务支持 rls.proto 中指定的 gRPC IDL。有关 API 如何工作的更多信息,请参阅 IDL 文档。
本身 envoy 只是提供了限流的接口,没有具体的实现,所以必须自己实现一个限流器。下面只是简单实现一下,给大家一个思路。
具体的代码如下:
package main

import (
“log”
“net”
“time”

rls “github.com/envoyproxy/go-control-plane/envoy/service/ratelimit/v2”
“github.com/juju/ratelimit”
“golang.org/x/net/context”
“google.golang.org/grpc”
“google.golang.org/grpc/reflection”
)

// server is used to implement rls.RateLimitService
type server struct {
bucket *ratelimit.Bucket
}

func (s *server) ShouldRateLimit(ctx context.Context,
request *rls.RateLimitRequest) (*rls.RateLimitResponse, error) {
// logic to rate limit every second request
var overallCode rls.RateLimitResponse_Code
if s.bucket.TakeAvailable(1) == 0 {
overallCode = rls.RateLimitResponse_OVER_LIMIT
} else {
overallCode = rls.RateLimitResponse_OK
}

response := &rls.RateLimitResponse{OverallCode: overallCode}
return response, nil
}

func main() {
// create a TCP listener on port 8089
lis, err := net.Listen(“tcp”, “:8089”)
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{
bucket: ratelimit.NewBucket(100*time.Microsecond, 100),
})
reflection.Register(s)
if err := s.Serve(lis); err != nil {
log.Fatalf(“failed to serve: %v”, err)
}
}
具体项目,查阅 github。
PS:
使用了令牌桶算法来限流。令牌桶算法 (Token Bucket) 和 Leaky Bucket 效果一样但方向相反的算法, 更加容易理解. 随着时间流逝, 系统会按恒定 1 /QPS 时间间隔 (如果 QPS=100, 则间隔是 10ms) 往桶里加入 Token(想象和漏洞漏水相反, 有个水龙头在不断的加水), 如果桶已经满了就不再加了. 新请求来临时, 会各自拿走一个 Token, 如果没有 Token 可拿了就阻塞或者拒绝服务.

该实现存在单点风险。
Dockerfile 均在代码仓库中,大家可以构建镜像自己测试。

结论
本文简单讲了 envoy 的 rate limit 功能,提供了全局限流的配置文件,并简单实现了一个基于令牌桶的限流器。希望能帮助你理解 Envoy 的限速过滤器如何跟 gRPC 协议协同工作。

正文完
 0