目前湿滓,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ō)明唠亚,我們看一下其部署的步驟:
- 下載github上的源代碼到本地
git clone git@github.com:didichuxing/gatekeeper.git
- 設(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)建了如下表:
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
6.配置一個(gè)http api
訪問(wèn)http://127.0.0.1:8081/gatekeeper/pvst/stat/ping
至此鹏溯,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é)完萨赁。