How It Works
Toggle navigation
How It Works
Home
About Me
Archives
Tags
janus是怎么做频率限制
2017-05-07 10:51:58
701
0
0
ochapman
#janus janus是一个轻量级的API网关和管理平台。实现对外提供openapi(如http接口)的鉴权认证(票据是否正确),权限控制(是否有权限访问cgi)和频率限制等功能。这避免了每一个对外提供的接口重复做这些公共事情。 #频率控制 简单地,保存一个kv,以限制的维度(例如IP)为key,以访问次数为值判断。如果指定时间内,达到指定的次数,则认为超过频率限制。 复杂地,如 1. 对不同业务(appid),要求不同的appid支持不一样的频率。重点业务频率可以很高。非重点业务设得相对低些。 2. 支持分布。给单机设定固定的限制频率不合适。扩容缩容需要动态调整,运维起来不方便。 下面看看janus是如何实现频率控制的 #怎么实现频率控制的 在janus中,频率限制功能作为一个插件(plugin)实现的。插件在loader包进行管理。插件向loader暴露http.Handler接口 依赖github.com/ulule/limiter实现 以IP地址为key 后端存储抽象,可以指定使用具体的存储方案 #存储后端接口 考虑到不同的场景的需要,提供Store接口,业务可以根据自己的需要提供后端存储接口 ``` // Store is the common interface for limiter stores. type Store interface { Get(key string, rate Rate) (Context, error) Peek(key string, rate Rate) (Context, error) } ``` 具体实现由redis和内存两种方案 以内存方案为例,底层cache使用[go-cache](github.com/patrickmn/go-cache) ``` func (s *MemoryStore) Get(key string, rate Rate) (Context, error) { ctx := Context{} key = fmt.Sprintf("%s:%s", s.Prefix, key) item, found := s.Cache.Items()[key] ms := int64(time.Millisecond) now := time.Now() if !found || item.Expired() { s.Cache.Set(key, int64(1), rate.Period) return Context{ Limit: rate.Limit, Remaining: rate.Limit - 1, Reset: (now.UnixNano()/ms + int64(rate.Period)/ms) / 1000, Reached: false, }, nil } count, err := s.Cache.IncrementInt64(key, 1) if err != nil { return ctx, err } return s.getContextFromState(now, rate, item.Expiration, count), nil } ``` 逻辑比较简单,如果找不到key,则创建值为1的key。如果找到,则增加1;构建Context,返回。 #http 频率控制方法 通常来讲,频率限制的结果放在包体返回即可,但是http支持在Header里面暴露更多的信息 通过X-RateLimit-Limit等Header ![](https://mua.io/api/file/getImage?fileId=590f34a92fa1ec184a7e6106) 一般互联厂商对外接口的有关于频率限制的说明,如[twitter](https://dev.twitter.com/rest/public/rate-limiting), [github](https://developer.github.com/v3/rate_limit/)等等 # 频率限制的header是如何返回到http上的 从上面的控制接口来看,直接使用,在http处理时,通过调用Get接口获取频率限制信息,可以做到。但是limiter提供了http的middleware,可以更高方便地处理频率信息。 ulule/limiter/middleware_http.go的源码中,middleware提供的Handler函数返回的是http.Handler,如果达到指定的频率,那么直接返回。如果还没有达到,那么继续由Handler的参数h,继续处理。 ``` // Handler the middleware handler. func (m *HTTPMiddleware) Handler(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { context, err := m.Limiter.Get(GetIPKey(r)) if err != nil { panic(err) } w.Header().Add("X-RateLimit-Limit", strconv.FormatInt(context.Limit, 10)) w.Header().Add("X-RateLimit-Remaining", strconv.FormatInt(context.Remaining, 10)) w.Header().Add("X-RateLimit-Reset", strconv.FormatInt(context.Reset, 10)) if context.Reached { http.Error(w, "Limit exceeded", 429) return } h.ServeHTTP(w, r) }) } ``` limiter提供的Handler,当janus在处理请求时,是如何调用到的呢 #通过janus的plugin 频率限制等功能通过plugin的方式注册到janus上 RateLimit实现了Plugin接口,在Load函数中将RateLimit等功能添加到plugin Loader上。 ``` // Load loads all the basic components and definitions into a router func Load(params Params) { pluginLoader := plugin.NewLoader() pluginLoader.Add( plugin.NewRateLimit(params.Storage), plugin.NewCORS(), plugin.NewOAuth2(params.OAuthRepo, params.Storage), plugin.NewCompression(), ) prx := proxy.WithParams(params.ProxyParams) // create proxy register register := proxy.NewRegister(params.Router, prx) apiLoader := NewAPILoader(register, pluginLoader) apiLoader.LoadDefinitions(params.APIRepo) oauthLoader := NewOAuthLoader(register, params.Storage) oauthLoader.LoadDefinitions(params.OAuthRepo) } ``` RateLimit的实现的Plugin接口函数,GetMiddlewares返回了router.Constructor, Constructor成员包含了上面提到的limiter的Handler ``` // GetMiddlewares retrieves the plugin's middlewares func (h *RateLimit) GetMiddlewares(config api.Config, referenceSpec *api.Spec) ([]router.Constructor, error) { limit := config["limit"].(string) policy := config["policy"].(string) rate, err := limiter.NewRateFromFormatted(limit) if err != nil { return nil, err } limiterStore, err := h.getLimiterStore(policy, referenceSpec.Name) if err != nil { return nil, err } return []router.Constructor{ limiter.NewHTTPMiddleware(limiter.NewLimiter(limiterStore, rate)).Handler, middleware.NewRateLimitLogger().Handler, }, nil } ``` 那router又是如何关联起来呢,这涉及到janus的内部proxy,router的机制,已经不是频率限制的范畴,将在其他文章讨论。 #结论 频率限制通过插件来实现,逻辑上分离比较好。如果直接调用limiter,那样会显得耦合太紧密。 频率限制允许对外暴露信息,可以直接告知限制的情况,避免用户无谓的尝试。
Pre:
浮点数精度问题笔记
Next:
protobuf源码-编码的过程
0
likes
701
Weibo
Wechat
Tencent Weibo
QQ Zone
RenRen
Table of content