goFrame 源码学习之 Server

Golang
354
0
0
2022-05-12

源码分析

goFrame 框架是一款国产的 golang web 框架,它事无巨细的把我们可能用到的组件都进行了封装,目前 Github star 已高达 5.7K ,人送“封装狂魔”的称号。既然如此,那我们就来学习一下别人是怎么来封装的。

我们知道 goFrame 框架的 main 文件非常简单,以下是官方模板的 main 文件示例:

package main

import (
    _ "github.com/gogf/gf-demos/router" 
    "github.com/gogf/gf/frame/g"
)

// @title       `gf-demo`示例服务API
// @version     1.0
// @description `GoFrame`基础开发框架示例服务API接口文档。
// @schemes     http
func main() {
    g.Server().Run()
}

正常我们拉起一个 Server ,拿到实例之后再去注册路由,比如 Gin 拉起服务是这样的:

package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })
    r.Run() // listen and serve on 0.0.0.0:8080
}

为什么 GoFrame 注册路由不需要 main 中 初始化的实例就可以直接注册路由呢?

package router

import (
    "github.com/gogf/gf-demos/app/api" 
    "github.com/gogf/gf/frame/g" 
    "github.com/gogf/gf/net/ghttp"
)

func init() {
    s := g.Server()

    s.Group("/", func(group *ghttp.RouterGroup) {
        group.ALL("/chat", api.Chat)
    })
}

为什么这么简短的代码就能拉起一个 Server 服务呢?我们追踪代码来看下 Server() 方法做了些什么事情!

// 位于 github.com/gogf/gf/frame/g/g_object.go 文件 30 行
func Server(name ...interface{}) *ghttp.Server {
    return gins.Server(name...)
}

Server() 方法很简单,我们继续往下追踪:

// 位于 github.com/gogf/gf/frame/gins/gins_server.go 文件
package gins

import (
    "fmt" 
    "github.com/gogf/gf/net/ghttp" 
    "github.com/gogf/gf/util/gutil"
)

const (
    frameCoreComponentNameServer = "gf.core.component.server"
    configNodeNameServer         = "server"
)

// Server returns an instance of http server with specified name.
func Server(name ...interface{}) *ghttp.Server {
    instanceKey := fmt.Sprintf("%s.%v", frameCoreComponentNameServer, name)
    return instances.GetOrSetFuncLock(instanceKey, func() interface{} {
        s := ghttp.GetServer(name...)
        // To avoid file no found error while it's not necessary. 
        if Config().Available() {
            var m map[string]interface{}
            nodeKey, _ := gutil.MapPossibleItemByKey(Config().GetMap("."), configNodeNameServer)
            if nodeKey == "" {
                nodeKey = configNodeNameServer
            }
            m = Config().GetMap(fmt.Sprintf(`%s.%s`, nodeKey, s.GetName()))
            if len(m) == 0 {
                m = Config().GetMap(nodeKey)
            }
            if len(m) > 0 {
                if err := s.SetConfigWithMap(m); err != nil {
                    panic(err)
                }
            }
            // As it might use template feature, 
            // it initialize the view instance as well.
            _ = getViewInstance()
        }
        return s
    }).(*ghttp.Server)
}

到这里也就初见端倪了,instances 是一个全局的 Map:

// 位于 github.com/gogf/gf/frame/gins/gins.go 文件
package gins

import (
    "github.com/gogf/gf/container/gmap"
)

var (
    // instances is the instance map for common used components.
    instances = gmap.NewStrAnyMap(true)
)

GetOrSetFuncLock 则是一个加锁的操作,先检索 Map 中是否存在指定 key 的值,没有则执行传入的 func 并将返回值添加到 Map 中:

func (m *StrAnyMap) GetOrSetFuncLock(key string, f func() interface{}) interface{} {
    if v, ok := m.Search(key); !ok {
        return m.doSetWithLockCheck(key, f)
    } else {
        return v
    }
}

我们知道导入包的 init 方法会先于 main方法执行,所以当 main 文件引入的 router.go 中执行 g.Server()时,全局 Map instances 中并没有对应的 Server,Server() 方法则会初始化一个服务并放入全局 Map 中,然后 main 方法再执行 g.Server() 获取到的则是我们在 router 文件中初始化并注册好路由的 Server。

这就是经典的设计模式 单例 的实现了,那有没有其他的方式实现单例呢?

sync.Once

sync.Once 是 golang 基础包中自带的一个方法,跟他的名字一样,代码仅执行一次。

package main

import (
    "sync" 
    "fmt"
)

var once sync.Once

func main() {
  for i := 0; i < 3; i++ {
        Server()
    }
}

func Server() {
  once.Do(func() {
    fmt.Println("new server")
  })

  fmt.Println("get server")
}

// new server
// get server
// get server
// get server

这也就达到了我们的要求,初始化代码只执行一次。那他是怎么实现的呢?我们来看看 sync.Once 的源码。

package sync

import (
    "sync/atomic"
)

type Once struct {
    done uint32
    m    Mutex
}

func (o *Once) Do(f func()) {

    if atomic.LoadUint32(&o.done) == 0 {
        o.doSlow(f)
    }
}

func (o *Once) doSlow(f func()) {
    o.m.Lock()
    defer o.m.Unlock()
    if o.done == 0 {
        defer atomic.StoreUint32(&o.done, 1)
        f()
    }
}

代码其实很简单,底层其实也是加锁实现。用 done 标志位来标识是否已经执行过了,已执行就直接跳过。

这里的加锁是非常有必要的,如果不加锁,当 f 还没执行完,done 标志位还未置为 1 时,这时并发执行就会导致再次执行 f 。