0x1 什么是反向代理?
上一篇介绍了Tyk的限流设计,这篇记录剖析下它的反代设计,反代这个词置信做后端的同学根本都据说过(nginx的罕用姿态),代理分为正向代理和反向代理,因为咱们这里不是专门介绍代理的,我就简略说下他们的区别,记住一个辨别他们的要点就是“正向代理就是拜访要进来”, “反向代理就是拜访要进来”,正向代理多用于一些须要做互联网拜访跳板机的场景,这里就不多说了。而反向代理呢,微服务场景是用得比拟多的,一个API Gateway反对反代是外围性能,Tyk作为这畛域软件的翘楚当然也得反对。GW的反代可用于负载平衡、拜访中间人解决、认证等性能的实现上。
0x2 流程剖析
0x3 要害代码
反代数据处理:
func (p *ReverseProxy) WrappedServeHTTP(rw http.ResponseWriter, req *http.Request, withCache bool) ProxyResponse { // ... outreq := new(http.Request) *outreq = *req // includes shallow copies of maps, but okay // remove context data from the copies setContext(outreq, context.Background()) // ... outreq.Header = cloneHeader(req.Header) // 如果应用缓存 if withCache { // 间接copy reponse p.CopyResponse(&bodyBuffer, res.Body) } } // 如果不应用缓存 p.HandleResponse(rw, res, ses) // copy实时申请的response到body return ProxyResponse{UpstreamLatency: upstreamLatency, Response: inres}}
拷贝数据:
// copy headerfunc copyHeader(dst, src http.Header, ignoreCanonical bool) { removeDuplicateCORSHeader(dst, src) for k, vv := range src { if ignoreCanonical { dst[k] = append(dst[k], vv...) continue } for _, v := range vv { dst.Add(k, v) } }}// copy reponsefunc (p *ReverseProxy) CopyResponse(dst io.Writer, src io.Reader) { if p.FlushInterval != 0 { if wf, ok := dst.(writeFlusher); ok { mlw := &maxLatencyWriter{ dst: wf, latency: p.FlushInterval, done: make(chan bool), } go mlw.flushLoop() defer mlw.stop() dst = mlw } } p.copyBuffer(dst, src)}
当然还有一些细节的解决,值的留神的是,为了放弃高性能,解决数据都是采纳[]byte,多处用到*[]byte的援用,复用数据结构,缩小内存申请销毁。当然真正的解决逻辑比我这边剖析的流程要简单得多,比方会话状态、受权这些的解决,这里还没列出来。
0x4 开展Tyk代码架构模式
通过上一篇的限流和本篇反向的剖析,仔细点其实能够发现限流是扩大于Tyk的中间人(TykMiddleware)设计,遵循了装璜器设计模式,继承于TykMiddleware形象interface(java很相熟的Component接口类),扩大并重写相干的办法。
中间人形象:
type TykMiddleware interface { Init() Base() *BaseMiddleware SetName(string) SetRequestLogger(*http.Request) Logger() *logrus.Entry Config() (interface{}, error) ProcessRequest(w http.ResponseWriter, r *http.Request, conf interface{}) (error, int) // Handles request EnabledForSpec() bool Name() string}
每一个具体的中间人主体的入口办法为ProcessRequest,例如咱们上一篇的RateLimit。
而本篇的反代却是在限流设计的上一层,api_loader模块,所有解决都会通过api_loader的processSpec,GW的一些事后解决(Prepare)都会放在这里,例如会话、CORS配置、反代等、值得注意的是这里有一个对立的自定义中间件装载的封装(loadCustomMiddleware),api_loader就是通过这个封装去注册TykMiddleware的中间件,而它们之间的中间件注册数据结构就是 chainArray,一个贮存链元素的列表
中间人链数据:
for _, obj := range mwPreFuncs { if mwDriver == apidef.GoPluginDriver { // ... } else if mwDriver != apidef.OttoDriver { // ... mwAppendEnabled(&chainArray, &CoProcessMiddleware{baseMid, coprocess.HookType_Pre, obj.Name, mwDriver, obj.RawBodyOnly, nil}) } else { chainArray = append(chainArray, createDynamicMiddleware(obj.Name, true, obj.RequireSession, baseMid)) } } mwAppendEnabled(&chainArray, &VersionCheck{BaseMiddleware: baseMid}) mwAppendEnabled(&chainArray, &RateCheckMW{BaseMiddleware: baseMid}) mwAppendEnabled(&chainArray, &IPWhiteListMiddleware{BaseMiddleware: baseMid}) mwAppendEnabled(&chainArray, &IPBlackListMiddleware{BaseMiddleware: baseMid}) // ...
中间人的ProcessRequest 对立返回error, errorCode, middleware依据这两个值来进行数据流下一步的解决
err, errCode := mw.ProcessRequest(w, r, mwConf)if err != nil { // GoPluginMiddleware are expected to send response in case of error // but we still want to record error _, isGoPlugin := actualMW.(*GoPluginMiddleware) handler := ErrorHandler{*mw.Base()} handler.HandleError(w, r, err.Error(), errCode, !isGoPlugin) meta["error"] = err.Error() finishTime := time.Since(startTime) if instrumentationEnabled { job.TimingKv("exec_time", finishTime.Nanoseconds(), meta) job.TimingKv(eventName+".exec_time", finishTime.Nanoseconds(), meta) } mw.Logger().WithError(err).WithField("code", errCode).WithField("ns", finishTime.Nanoseconds()).Debug("Finished") return}
有意思的彩蛋, middleware有一种状况就是无谬误返回,,然而依然须要返回一个状态码去匹配一些非凡状况, 这个状态码就是 const mwStatusRespond = 666,不禁让我想起难道Tyk的coder也是一位老铁?
// Special code, bypasses all other executionif errCode != mwStatusRespond { // No error, carry on... meta["bypass"] = "1" h.ServeHTTP(w, r)} else { mw.Base().UpdateRequestSession(r)}
0x5 为什么这样设计?
又回到这个为什么设计的环节,其实对于http server/容器/框架的设计, middleware(中间人)这个词应该是在很多驰名的web框架外面都有呈现过的,比方springboot,gin,php的Laravel框架,中间人这种模式特地适宜解决由上到下数据流的场景,相当于是一个数据库的filter。
- middleware反对可插拔(装璜器模式),可随时启用/禁用中间件而整体服务不受影响
- 合乎正向性设计,功能模块都是独立的,每一个中间件从解决、日志都是依据中间人自身的需要而定制
- 装璜go原生的 net.http的办法ServeHTTP(http.Handler形象),其实从这个角度来看,能够套用其余go的web框架来解决http/ws申请,比方gin,httprouter等都是装璜ServeHTTP,不便扩大
- GW load配置时对立注册中间人,不应用的中间人不会有逻辑数据交加,gw运行时的功能设计不波及多个中间人交互,整体数据流解决是Filter Chain
分享迷信人文随笔
感谢您「观看」、「点赞」和「关注」,关注我的公众号。