goFrame 原始碼學習之 Server

zxdstyle發表於2021-07-07

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 是 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 。

本作品採用《CC 協議》,轉載必須註明作者和本文連結
更多文章去我的部落格 看看

相關文章