通過(guò)項(xiàng)目學(xué)習(xí)Go語(yǔ)言之gatekeeper項(xiàng)目介紹

目前湿滓,go語(yǔ)言已然成為各大廠的標(biāo)配開發(fā)語(yǔ)言,各大廠都在使用go語(yǔ)言構(gòu)建一些重要的支撐系統(tǒng)。當(dāng)今最流行的容器Docker及容器編排和管理工具k8s都是使用go來(lái)構(gòu)建的复亏,并且是開源的,這給go開發(fā)者提供了很好的學(xué)習(xí)資料缭嫡。但是缔御,這些項(xiàng)目對(duì)初學(xué)者來(lái)說(shuō)過(guò)于復(fù)雜,很難通讀其源碼械巡。國(guó)內(nèi)大廠也開源了一些go開發(fā)的項(xiàng)目,比如baidu的應(yīng)用負(fù)載均衡產(chǎn)品bfe饶氏、bilibili開源的go微服務(wù)框架kratos讥耗、didi最近開源的gatekeeper都是開發(fā)者值得學(xué)習(xí)的好項(xiàng)目。

接下來(lái)疹启,通過(guò)didi開源的gatekeeper為例古程,來(lái)深入學(xué)習(xí)go語(yǔ)言。為什么選擇gatekeeper為例來(lái)學(xué)習(xí)go語(yǔ)言呢喊崖?

  • 微服務(wù)已經(jīng)成為各個(gè)公司構(gòu)建系統(tǒng)首先倡導(dǎo)的架構(gòu)風(fēng)格挣磨,對(duì)于微服務(wù)架構(gòu)來(lái)說(shuō)一個(gè)很重要的組件就是網(wǎng)關(guān),gatekeeper就是一個(gè)這樣的項(xiàng)目
  • go語(yǔ)言以其簡(jiǎn)單和高性能荤懂,部署快捷簡(jiǎn)單著稱茁裙,很多公司都是用go開發(fā)前端接口服務(wù),getekeeper使用了最著名的一個(gè)http web開發(fā)框架gin
  • gatekeeper既包含了接口服務(wù)也包括了管理后臺(tái)节仿,使用了mysql也使用了redis
    鑒于以上原因晤锥,筆者選中了它來(lái)作為需要使用go語(yǔ)言開發(fā)生產(chǎn)級(jí)項(xiàng)目的剖析原型。

在此也非常感謝didi的gatekeeper團(tuán)隊(duì)開源出來(lái)這么優(yōu)秀的產(chǎn)品廊宪。

項(xiàng)目介紹

GateKeeper 是一個(gè)使用 Go (golang) 編寫的不依賴分布式數(shù)據(jù)庫(kù)的 API 網(wǎng)關(guān), 使用它可以高效進(jìn)行服務(wù)代理 以及 在線化服務(wù)配置并且你無(wú)需重啟服務(wù)器矾瘾。具備以下特性:

  • http、websocket箭启、tcp服務(wù)代理
  • 服務(wù)探測(cè)
  • 加權(quán)負(fù)載輪詢
  • URL地址重寫
  • 服務(wù)限流:支持獨(dú)立IP限流
  • 高拓展性:支持自定義 請(qǐng)求前驗(yàn)證request方法壕翩、請(qǐng)求后更改response方法、tcp中間件傅寡、http中間件 等放妈。
  • 高可用性:?jiǎn)蝹€(gè)服務(wù)異常不影響其他服務(wù)。
  • 最少依賴:無(wú)需任何額外組件即可運(yùn)行荐操,mysql大猛、redis 只做管理和統(tǒng)計(jì)使用可隨時(shí)關(guān)閉。
    通過(guò)官方的介紹淀零,我們可以看到getekeeper具備了網(wǎng)關(guān)的幾乎全部功能挽绩。其中最重要的特性就是支持http的代理,還支持websocket和tcp驾中,同時(shí)也支持插件唉堪。gatekeeper處理http代理時(shí)模聋,直接是使用了go內(nèi)置的代理組件ReverseProxy。

運(yùn)行

getakeeper官方給了很詳細(xì)的運(yùn)行說(shuō)明唠亚,我們看一下其部署的步驟:

  1. 下載github上的源代碼到本地
git clone git@github.com:didichuxing/gatekeeper.git
  1. 設(shè)置運(yùn)行環(huán)境链方,gatekeeper是通過(guò)go mod來(lái)管理構(gòu)建項(xiàng)目的,需要按照如下步驟開啟go mod模式
export GO111MODULE=on
export GOPROXY=https://goproxy.cn

3.getekeeper為我們提供了管理后臺(tái)灶搜,用于配置管理的api和租戶祟蚀,查看調(diào)用統(tǒng)計(jì),需要我們?cè)O(shè)置mysql和redis
創(chuàng)建數(shù)據(jù)庫(kù)

mysql -h localhost -u root -p -e "CREATE DATABASE gatekeeper DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;"
mysql -h localhost -u root -p gatekeeper < install/db.sql --default-character-set=utf8

創(chuàng)建數(shù)據(jù)庫(kù)導(dǎo)入表后割卖,我們看到創(chuàng)建了如下表:


數(shù)據(jù)庫(kù)表

redis為了方便將通過(guò)docker啟動(dòng)

docker run -p 6379:6379 -d redis:latest redis-server
6fce8bf5cc2faa0ca462939161960dcdf137a07f1fd587d93da51dc2b5f3a8cb
docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
6fce8bf5cc2f        redis:latest        "docker-entrypoint.s…"   4 seconds ago       Up 3 seconds        0.0.0.0:6379->6379/tcp   brave_shtern

4.調(diào)整配置文件前酿,修改 ./conf/dev/mysql.toml 和 ./conf/dev/redis.toml 為自己的環(huán)境配置

# this is mysql config  將數(shù)據(jù)庫(kù)的用戶名和密碼 數(shù)據(jù)庫(kù)ip和端口修改成實(shí)際的環(huán)境配置即可
[list]
    [list.default]
        driver_name = "mysql"
        data_source_name = "root:123456@tcp(127.0.0.1:3306)/gatekeeper?charset=utf8&parseTime=true&loc=Asia%2FChongqing"
        max_open_conn = 20
        max_idle_conn = 10
        max_conn_life_time = 100
# this is redis config file  將redis地址和端口修改為實(shí)際環(huán)境配置
[list]
    [list.default]
        proxy_list = ["127.0.0.1:6379"]
        conn_timeout = 50 #ms
        read_timeout = 100
        write_timeout = 100

5.運(yùn)行

go run main.go 

2019/12/12 21:12:16 ------------------------------------------------------------------------
2019/12/12 21:12:16 [INFO]  config=./conf/dev/
2019/12/12 21:12:16 [INFO]  start loading resources.
2019/12/12 21:12:16 [INFO]  success loading resources.
2019/12/12 21:12:16 ------------------------------------------------------------------------
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)
....省略路由映射日志
2019/12/12 21:12:16  [INFO] HttpServer :8081 listening
2019/12/12 21:12:16  [INFO] TCPServer :8900 listening

訪問(wèn):http://127.0.0.1:8081/admin/login,打開頁(yè)面登陸:admin / 123456

管理頁(yè)面

6.配置一個(gè)http api
添加api

訪問(wèn)http://127.0.0.1:8081/gatekeeper/pvst/stat/ping

返回正確結(jié)果

至此鹏溯,api網(wǎng)關(guān)已經(jīng)正常運(yùn)行起來(lái)了罢维。

組件分析

在上文中我們也提到getekeeper的http部分是基于優(yōu)秀的框架gin構(gòu)建的,由gin來(lái)處理全部的http請(qǐng)求丙挽。

//HTTPServerRun 服務(wù)啟動(dòng)
func HTTPServerRun() {
    gin.SetMode(lib.ConfBase.DebugMode)
//初始化engine
    r := InitRouter()
    HTTPSrvHandler = &http.Server{
        Addr:           lib.GetStringConf("base.http.addr"),
        Handler:        r,
        ReadTimeout:    time.Duration(lib.GetIntConf("base.http.read_timeout")) * time.Second,
        WriteTimeout:   time.Duration(lib.GetIntConf("base.http.write_timeout")) * time.Second,
        MaxHeaderBytes: 1 << uint(lib.GetIntConf("base.http.max_header_bytes")),
    }
    go func() {
        defer func() {
            if err := recover(); err != nil {
                public.SysLogger.Error("HttpServerRun_recover:%v", err)
            }
        }()
        log.Printf(" [INFO] HttpServer %s listening\n",lib.GetStringConf("base.http.addr"))
        if err := HTTPSrvHandler.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf(" [ERROR] HttpServer %s err:%v\n", lib.GetStringConf("base.http.addr"), err)
        }
    }()
}

通過(guò)r := InitRouter()肺孵,對(duì)gin Engine進(jìn)行初始化,以接管來(lái)自外部的全部http請(qǐng)求颜阐,接下來(lái)我們看看 InitRouter()做了哪些事平窘。

//InitRouter 聲明http路由
func InitRouter() *gin.Engine {
    router := gin.New()
    router.Use(middleware.Recovery())

    //admin
    admin := router.Group("/admin")
    admin.Use(middleware.RequestTraceLog())
    {
        controller.AdminRegister(admin)
    }

    //assets
    router.Static("/assets", "./tmpl/green/assets")

    //gateway
    gateway := controller.Gateway{}
    router.GET("/ping", gateway.Ping)

    //cluster
    csr:=router.Group("/")
    csr.Use(middleware.ClusterAuth())
    csr.GET("/reload", gateway.Reload)
//注冊(cè)網(wǎng)關(guān)路由
    gw:=router.Group(lib.GetStringConf("base.http.route_prefix"))
    gw.Use(
        middleware.RequestTraceLog(),
        middleware.MatchRule(),
        middleware.AccessControl(),
        middleware.HTTPLimit(),
        //todo 拓展中間件
        middleware.LoadBalance())
    {
        gw.GET("/*action", gateway.Index)
        gw.POST("/*action", gateway.Index)
        gw.DELETE("/*action", gateway.Index)
        gw.OPTIONS("/*action", gateway.Index)
    }
    return router
}

通過(guò)gw:=router.Group(lib.GetStringConf("base.http.route_prefix"))注冊(cè)了請(qǐng)求URI以/gatekeeper開頭的全部請(qǐng)求會(huì)進(jìn)入gw路由組的處理邏輯,即經(jīng)過(guò)注冊(cè)的一系列的插件轉(zhuǎn)發(fā)到實(shí)際的realserver進(jìn)行請(qǐng)求的處理和響應(yīng)凳怨。

在請(qǐng)求代理處理部分初婆,getekeeper直接使用了go內(nèi)置的代理組件ReverseProxy來(lái)處理請(qǐng)求的轉(zhuǎn)發(fā)代理:proxy.ServeHTTP(c.Writer, c.Request)。

//LoadBalance 負(fù)載均衡中間件
func LoadBalance() gin.HandlerFunc {
    return func(c *gin.Context) {
        gws,ok:=c.MustGet(MiddlewareServiceKey).(*service.GateWayService)
        if !ok{
            public.ResponseError(c, http.StatusBadRequest, errors.New("gateway_service not valid"))
            return
        }
        proxy, err := gws.LoadBalance()
        if err != nil {
            public.ResponseError(c, http.StatusProxyAuthRequired, err)
            return
        }
        requestBody,ok:=c.MustGet(MiddlewareRequestBodyKey).([]byte)
        if !ok{
            public.ResponseError(c, http.StatusBadRequest, errors.New("request_body not valid"))
            return
        }
        c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(requestBody))
        proxy.ServeHTTP(c.Writer, c.Request)
        c.Abort()
    }
}

go語(yǔ)言net包為我們提供了豐富的關(guān)于http的處理工具猿棉,其中http代理就是其提供的一個(gè)強(qiáng)大的正向代理和反向代理的工具磅叛,在這里我們就對(duì)其進(jìn)行展開分析了。

本節(jié)完萨赁。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末弊琴,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子杖爽,更是在濱河造成了極大的恐慌敲董,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件慰安,死亡現(xiàn)場(chǎng)離奇詭異腋寨,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)化焕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門萄窜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事查刻〖担” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵穗泵,是天一觀的道長(zhǎng)普气。 經(jīng)常有香客問(wèn)我,道長(zhǎng)佃延,這世上最難降的妖魔是什么现诀? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮履肃,結(jié)果婚禮上仔沿,老公的妹妹穿的比我還像新娘。我一直安慰自己榆浓,他們只是感情好于未,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布撕攒。 她就那樣靜靜地躺著陡鹃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪抖坪。 梳的紋絲不亂的頭發(fā)上萍鲸,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音擦俐,去河邊找鬼脊阴。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蚯瞧,可吹牛的內(nèi)容都是我干的嘿期。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼埋合,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼备徐!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起甚颂,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蜜猾,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后振诬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蹭睡,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年赶么,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了肩豁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蓖救,靈堂內(nèi)的尸體忽然破棺而出洪规,到底是詐尸還是另有隱情,我是刑警寧澤循捺,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布斩例,位于F島的核電站,受9級(jí)特大地震影響从橘,放射性物質(zhì)發(fā)生泄漏念赶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一恰力、第九天 我趴在偏房一處隱蔽的房頂上張望叉谜。 院中可真熱鬧,春花似錦踩萎、人聲如沸停局。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)董栽。三九已至,卻和暖如春企孩,著一層夾襖步出監(jiān)牢的瞬間锭碳,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工勿璃, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留擒抛,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓补疑,卻偏偏與公主長(zhǎng)得像歧沪,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子莲组,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容