前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >自己动手实现一个envoy限流器

自己动手实现一个envoy限流器

作者头像
有点技术
发布2020-07-13 11:25:35
1.2K0
发布2020-07-13 11:25:35
举报
文章被收录于专栏:有点技术有点技术

envoy ratelimit

envoy 可以集成一个全局grpc ratelimit 服务,称之为为rate limit service

go-control-plane 是一个官方实现的envoy golang 库github.com/envoyproxy/go-control-plane

go-control-plane中关于rls的pb文件为envoy/service/ratelimit/v2/rls.pb.go

其包含了一个RegisterRateLimitServiceServer方法,将一个限流器实现注册到grpcserver

代码语言:javascript
复制
func RegisterRateLimitServiceServer(s *grpc.Server, srv RateLimitServiceServer) {
    s.RegisterService(&_RateLimitService_serviceDesc, srv)
}

而RateLimitServiceServer是一个接口

代码语言:javascript
复制
type RateLimitServiceServer interface {
    ShouldRateLimit(context.Context, *RateLimitRequest) (*RateLimitResponse, error)
}

由此看出我们重点需要实现一个ShouldRateLimit方法

对于ShouldRateLimit,接收RateLimitRequest,返回RateLimitResponse

对于RateLimitRequest结构体如下,

代码语言:javascript
复制
type RateLimitRequest struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields

    Domain      string                           `protobuf:"bytes,1,opt,name=domain,proto3" json:"domain,omitempty"`
    Descriptors []*ratelimit.RateLimitDescriptor `protobuf:"bytes,2,rep,name=descriptors,proto3" json:"descriptors,omitempty"`
    HitsAddend  uint32                           `protobuf:"varint,3,opt,name=hits_addend,json=hitsAddend,proto3" json:"hits_addend,omitempty"`
}

其包含了Descriptors,也就是限流信息描述,可以包含多个Descriptor,HitsAddend就是命中累加次数

代码语言:javascript
复制
type RateLimitDescriptor struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields

    Entries []*RateLimitDescriptor_Entry `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries,omitempty"`
}

每个Descriptor 可以包含多个Entry,Descriptor是限流的最小单元,对于Descriptor下所有的Entry,无论任何一个达到阈值,都应触发限流

代码语言:javascript
复制
type RateLimitDescriptor_Entry struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields

    Key   string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
    Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
}

Entry包含具体的key value

对于key,envoy包含五种类型:

•source_cluster(根据source_cluster限流)•destination_cluster (根据destination_cluster限流)•request_headers (根据request_headers限流)•remote_address (根据remote_address限流)•generic_key (根据generic_key限流)•header_value_match (根据header 正则匹配进行限流)

实现限流器

我们将通过redis实现一个基于固定窗口的限流实现

这里我们实现了一个不限流的ShouldRateLimit方法实现。

定义限流结构、方法

代码语言:javascript
复制
type ratelimitService struct{}
func (r ratelimitService) ShouldRateLimit(ctx context.Context, request *pb.RateLimitRequest) (*pb.RateLimitResponse, error) {
    return  &pb.RateLimitResponse{
        OverallCode: pb.RateLimitResponse_OK,
    }, nil
}

注册限流实现

在main函数中,将我们的限流器注册到grpcserver,调用reflection.Register(s)方便我们使用grpcurl进行调试。

代码语言:javascript
复制
func main() {
    listener, err := net.Listen("tcp", ":8080")
    if err != nil {
        log.Println(err)
    }
    s := grpc.NewServer()
    pb.RegisterRateLimitServiceServer(s, &ratelimitService{})
    reflection.Register(s)
    s.Serve(listener)
}

添加限流逻辑

这里将通过redis固定窗口实现限流器,限制每分钟不能超过2个请求,超过则处罚限流

代码语言:javascript
复制
func (r ratelimitService) ShouldRateLimit(ctx context.Context, request *pb.RateLimitRequest) (*pb.RateLimitResponse, error) {
    now := (time.Now().Unix()/60)*60
    conn,err:=redis.Dial("tcp","127.0.0.1:6379")
    if err!=nil {
        log.Println(err)
        return nil, err
    }
    defer conn.Close()
    var uq string
    if request.Descriptors[0].Entries[0].Value!=""{
        uq = request.Domain+"_"+request.Descriptors[0].Entries[0].Key +"_" +request.Descriptors[0].Entries[0].Value
    }else {
        uq = request.Domain+"_"+request.Descriptors[0].Entries[0].Key
    }
    uq+=fmt.Sprint(now)
    reply,err:=redis.String(conn.Do("GET", uq))
    if  err!= nil&& reply!="" {
        return &pb.RateLimitResponse{OverallCode: pb.RateLimitResponse_UNKNOWN,}, err
    }
    if count,_:=strconv.Atoi(fmt.Sprint(reply));count>2 {
        return  &pb.RateLimitResponse{OverallCode: pb.RateLimitResponse_OVER_LIMIT}, nil
    }
    if _, err := conn.Do("INCR", uq); err != nil {
        return &pb.RateLimitResponse{OverallCode: pb.RateLimitResponse_UNKNOWN}, err
    }
    return  &pb.RateLimitResponse{OverallCode: pb.RateLimitResponse_OK,}, nil
}

我们通过 timestamp除去60获取一个时间窗口,在时间窗口内将访问次数进行累加,当达到阈值返回overlimit,这里并没有进行ttl设置,生产级别实现需要对rediskey 设置ttl,自动删除过期的key,这个使用OverallCode进行统一返回,实际上我们针对每个Descriptor可以进行单独设置,并且可以设置limit_remaining,让客户端可以获取当前剩余的可访问次数。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-07-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 有点技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • envoy ratelimit
  • 实现限流器
    • 定义限流结构、方法
      • 注册限流实现
        • 添加限流逻辑
        相关产品与服务
        云数据库 Redis
        腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档