go-zero api server 源码解析

1,182 阅读5分钟

本文已参与新[人创作礼]活动,一起开启掘金创作之路。

go-zero是一个当下很火,社区很活跃的微服务框架,虽然是微服务框架,但是其中的api server完全可以独立出来进行单体开发。官网给出的基准测试与gin不相上下。好好研究一下很有必要的。

go-zero api server 简单demo

因为要研究其构造原理,所以不用其goctl进行生成,直接用代码书写一个简单demo

func main() {
	//文件配置
	c := &Config{}
	c.Host = "0.0.0.0"
	c.Port = 9100
	c.Log.Path = "./log"

	//go zero的api server
	server := rest.MustNewServer(c.RestConf)
	defer server.Stop()

	//路由 处理器
	server.AddRoutes(
            []rest.Route{
                {
                    Method: http.MethodGet,
                    Path:   "/api/myhandler",
                    Handler: func(w http.ResponseWriter, r *http.Request) {
                            w.Write([]byte("myHandler handler \n"))
                    },
                },
                {
                    Method: http.MethodGet,
                    Path:   "/api/zshandler",
                    Handler: func(w http.ResponseWriter, r *http.Request) {
                            w.Write([]byte("zhang san handler \n"))
                    },
                },
            },
	)

	fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
	server.Start()
}

代码写好了,拿来浏览器直接测试一下,访问http://localhost:9100/api/myhandler 能通,接下来仔细看看代码, 看着跟goctl生成的不一样啊?的确不一样,这段代码少了一个功能注册的svc.ServiceContext,还少了一个struct形式的handler(logic),那些都是不必要的(对于研究demo来说),只是组织业务代码的技巧(用gin写业务代码也可按照这样的结构搞),省掉那些有助于去去除干扰。

go-zero的Server

server := rest.MustNewServer(c.RestConf) 这段代码返回一个server 但这个不是http.Server 而是go-zero的Server,其位置在github.com/zeromicro/go-zero/rest/server.go中 22行。其定义如下

Server struct {
        ngin   *engine  // 引擎 主要存放路由的地方---------0--------
        router httpx.Router  //这是一个包含http.handler的接口,也是一个处理器,gozero中实现的httpx.Router相当于http.ServeMux
}

Server主要有两个属性,ngin(引擎)和router(路由)。从目前看到的,还无法理解Server是如何跑起来的,下面一步步分析,看看普通处理器是如何通过路由绑定到http.Server的。

engine中的路由注册

这个结构体在 github.com/zeromicro/go-zero/rest/engine.go中, 其结构如下,其属性含义都已注释:

//--------------0-----------
type engine struct {
        conf                 RestConf                      //配置数据
	routes               []featuredRoutes              //分组路由。------1--------
	unauthorizedCallback handler.UnauthorizedCallback  //授权未通过后进行的回调 jwt相关
	unsignedCallback     handler.UnsignedCallback      //无sing的回调,jwt相关
	middlewares          []Middleware                  //中间件
	shedder              load.Shedder                  //自适应将在保护
	priorityShedder      load.Shedder                  //自适应将在保护
	tlsConfig            *tls.Config                   //https相关
}

//分组路由
//github.com/zeromicro/go-zero/rest/types.go line 33
//-------1-------------
featuredRoutes struct {
        timeout   time.Duration
        priority  bool
        jwt       jwtSetting
        signature signatureSetting
        routes    []Route                       //路由 -------2------------
        maxBytes  int64
}

//github.com/zeromicro/go-zero/rest/types.go line 13
//---------2---------
Route struct {
        Method  string                          //GET POST PUT DELETE ...
        Path    string                          //url path
        Handler http.HandlerFunc                //业务处理器
}


这里边最重要的就是routes,其他的暂可忽略。注意这里的routes是真正存储所有路由的地方,而rest.Server.router只是一个路由处理的接口。后边会讲到。

下面看一下,用户写的处理器是如何注册到这个路由中的。

// github.com/zeromicro/go-zero/rest/server.go line 61
//rest.Server的方法

func (s *Server) AddRoutes(rs []Route, opts ...RouteOption) {
	r := featuredRoutes{          //创建新的分组路由对象用于注册金engine里
		routes: rs,
	}
	for _, opt := range opts {    //注册路由的时候可以设置改组路由的一些配置项 比如jwt 超时,用户可以设置每组路由的超时!!
		opt(&r)
	}
	s.ngin.addRoutes(r)          
}

//github.com/zeromicro/go-zero/rest/engine.go line 50

func (ng *engine) addRoutes(r featuredRoutes) {
	ng.routes = append(ng.routes, r)
}

这个方法 AddRoutes在上边写的demo里可以找到,正是这个函数把,把用户的路由及处理器添加到分组路由中的。路由注册到engine具体流程看下图。

1650292897(1).png

server的启动

启动就要从demo中的server.Start()代码说起了。看一下具体实现代码。

//github.com/zeromicro/go-zero/rest/server.go line 79

func (s *Server) Start() {
	handleError(s.ngin.start(s.router))  
}

handlerErrors是对启动异常的处理。启动主要还是调用的engine中的start函数,注意这里的参数是一个gozero实现的处理器。该处理器也可用http.ServeMux进行实现。功能类似都是根据路由进行用户注册器调用。

//github.com/zeromicro/go-zero/rest/engine.go line 239

func (ng *engine) start(router httpx.Router) error {   //注意启动时候需要一个router参数,这个接口也是一个http.handler类型,是一个处理器
	if err := ng.bindRoutes(router); err != nil {
		return err
	}

	if len(ng.conf.CertFile) == 0 && len(ng.conf.KeyFile) == 0 {              //http启动
		return internal.StartHttp(ng.conf.Host, ng.conf.Port, router)
	}

	return internal.StartHttps(ng.conf.Host, ng.conf.Port, ng.conf.CertFile,   //https启动
		ng.conf.KeyFile, router, func(svr *http.Server) {
			if ng.tlsConfig != nil {
				svr.TLSConfig = ng.tlsConfig
			}
		})
}

internal.StartHttp(ng.conf.Host, ng.conf.Port, router) 这段代码最终调用http.Server进行启动,具体实现可以跟踪进去查看。

这段代码的重点是 ng.bindRoutes(router) 是把之前注册的用户启动器与router进行了关联,http.Server才最终能调用用户的启动器。


//github.com/zeromicro/go-zero/rest/engine.go line 110
func (ng *engine) bindRoutes(router httpx.Router) error {
	metrics := ng.createMetrics()  //监控相关 

	for _, fr := range ng.routes {
		if err := ng.bindFeaturedRoutes(router, fr, metrics); err != nil {
			return err
		}
	}

	return nil
}
// line 70
func (ng *engine) bindFeaturedRoutes(router httpx.Router, fr featuredRoutes, metrics *stat.Metrics) error {
	verifier, err := ng.signatureVerifier(fr.signature)
	if err != nil {
		return err
	}

	for _, route := range fr.routes {
		if err := ng.bindRoute(fr, router, metrics, route, verifier); err != nil {
			return err
		}
	}

	return nil
}

//line 85
func (ng *engine) bindRoute(fr featuredRoutes, router httpx.Router, metrics *stat.Metrics,
	route Route, verifier func(chain alice.Chain) alice.Chain) error {
	chain := alice.New( //注册系统默认中间件
		handler.TracingHandler(ng.conf.Name, route.Path),
		ng.getLogHandler(),
		handler.PrometheusHandler(route.Path),
		handler.MaxConns(ng.conf.MaxConns),
		handler.BreakerHandler(route.Method, route.Path, metrics),
		handler.SheddingHandler(ng.getShedder(fr.priority), metrics),
		handler.TimeoutHandler(ng.checkedTimeout(fr.timeout)),
		handler.RecoverHandler,
		handler.MetricHandler(metrics),
		handler.MaxBytesHandler(ng.checkedMaxBytes(fr.maxBytes)),
		handler.GunzipHandler,
	)
	chain = ng.appendAuthHandler(fr, chain, verifier)

	for _, middleware := range ng.middlewares {  //客户注册的中间件
		chain = chain.Append(convertMiddleware(middleware))
	}
	handle := chain.ThenFunc(route.Handler) 

	return router.Handle(route.Method, route.Path, handle)   //最终绑定到了engine.router处理器里。
}

这三个函数实现了把engine中注册的用户处理器绑定到engine.router处理器中。而新生成的处理器最终带进http.Server中。

bindRoute 中的alice是一个中间件处理对象,处理的最后会生成一个http.handler类型的对象。

总结

整个gozero api server代码条例还是比较清晰,gozero的rest.Server中的engine和router是关键,engine负责注册用户编写的路由及处理器,同时启动的时候还负责加载各种中间件。router是gozero实现的一个类似http.ServeMux的处理器,其实现的代码有点复杂,以后有机会在出专门博文进行梳理,这里也完全可以用http.ServeMux来实现httpx.Router接口。有了router后最后通过http.server进行绑定tcp。

这里边还有个中间件知识点,后边会专门出个博文进行梳理。go-zero中的中间件功能强大,如果你一直在用gin其实可以直接把这些中间件拿来用的。做一个gin版本gozero。