在上一篇文章《go-kit微服務:服務註冊與發現》中,算術服務註冊至consul,發現服務使用go-kit工具集實現了對算術服務的發現功能。經過查看源碼能夠發現,其中僅僅實現了一個接口/calculate
的處理邏輯,那若是算術服務的接口不少怎麼辦呢?git
這幾天這個問題一直困擾着我,我認爲go-kit會提供一種合理的解決方案,但是始終沒有找到,對於sd.Factory
的設計始終不理解,若哪位朋友瞭解還請指點迷津。github
爲了解決這個問題,我決定另闢蹊徑:根據客戶端HTTP請求,動態查詢註冊中心的服務實例,經過反向代理實現對後臺服務的調用。golang
這樣就至關於實現一個簡單的網關,凡是符合規則的請求均可以經過此網關調用後端服務。這裏的規則是指http請求的資源路徑,規則爲:/{serviceName}/#
。即:路徑第一部分爲註冊中心服務實例名稱,其他部分爲服務實例的REST路徑。如:docker
/arithmetic/calculate/Add/10/2
複製代碼
arithmetic
爲服務名稱;/calculate/Add/10/2
爲算術服務的接口。客戶端向網關發起請求,網關解析請求資源路徑中的信息,根據服務名稱查詢註冊中心的服務實例,而後使用反向代理技術把客戶端請求轉發至後端真實的服務實例,請求執行完畢後,再把響應信息返回客戶端。後端
/{serviceName}/#
,不然不予經過。httputil.ReverseProxy
實現一個簡單的反向代理,它可以對請求實現負載均衡,隨機地把請求發送給服務實例。在arithmetic_consul_demo
下建立目錄gateway
,而後新建go文件main.go
。NewReverseProxy方法接受兩個參數:consul客戶端對象和日誌記錄工具,返回反向代理對象。該方法的實現過程以下所述:api
Schema
、Host
、Path
。完整代碼以下:瀏覽器
// NewReverseProxy 建立反向代理處理方法
func NewReverseProxy(client *api.Client, logger log.Logger) *httputil.ReverseProxy {
//建立Director
director := func(req *http.Request) {
//查詢原始請求路徑,如:/arithmetic/calculate/10/5
reqPath := req.URL.Path
if reqPath == "" {
return
}
//按照分隔符'/'對路徑進行分解,獲取服務名稱serviceName
pathArray := strings.Split(reqPath, "/")
serviceName := pathArray[1]
//調用consul api查詢serviceName的服務實例列表
result, _, err := client.Catalog().Service(serviceName, "", nil)
if err != nil {
logger.Log("ReverseProxy failed", "query service instace error", err.Error())
return
}
if len(result) == 0 {
logger.Log("ReverseProxy failed", "no such service instance", serviceName)
return
}
//從新組織請求路徑,去掉服務名稱部分
destPath := strings.Join(pathArray[2:], "/")
//隨機選擇一個服務實例
tgt := result[rand.Int()%len(result)]
logger.Log("service id", tgt.ServiceID)
//設置代理服務地址信息
req.URL.Scheme = "http"
req.URL.Host = fmt.Sprintf("%s:%d", tgt.ServiceAddress, tgt.ServicePort)
req.URL.Path = "/" + destPath
}
return &httputil.ReverseProxy{Director: director}
}
複製代碼
main方法的主要任務是建立consul鏈接對象、建立日誌記錄對象、開啓反向代理HTTP服務。整個過程與前面幾個示例相似,直接貼代碼(爲了測試方便,我直接指定了consul服務地址信息):bash
func main() {
// 建立環境變量
var (
consulHost = flag.String("consul.host", "192.168.192.146", "consul server ip address")
consulPort = flag.String("consul.port", "8500", "consul server port")
)
flag.Parse()
//建立日誌組件
var logger log.Logger
{
logger = log.NewLogfmtLogger(os.Stderr)
logger = log.With(logger, "ts", log.DefaultTimestampUTC)
logger = log.With(logger, "caller", log.DefaultCaller)
}
// 建立consul api客戶端
consulConfig := api.DefaultConfig()
consulConfig.Address = "http://" + *consulHost + ":" + *consulPort
consulClient, err := api.NewClient(consulConfig)
if err != nil {
logger.Log("err", err)
os.Exit(1)
}
//建立反向代理
proxy := NewReverseProxy(consulClient, logger)
errc := make(chan error)
go func() {
c := make(chan os.Signal)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
errc <- fmt.Errorf("%s", <-c)
}()
//開始監聽
go func() {
logger.Log("transport", "HTTP", "addr", "9090")
errc <- http.ListenAndServe(":9090", proxy)
}()
// 開始運行,等待結束
logger.Log("exit", <-errc)
}
複製代碼
arithmetic_consul_demo
目錄,執行如下命令:sudo docker-compose -f docker/docker-compose.yml up
複製代碼
./register/register -consul.host localhost -consul.port 8500 -service.host 192.168.192.146 -service.port 9000
./register/register -consul.host localhost -consul.port 8500 -service.host 192.168.192.146 -service.port 9002
複製代碼
http://localhost:8500
可看到arithmetic
實例有兩個,以下圖:gateway
,執行go build
完成編譯,而後啓動網關服務。> ./gateway -consul.host localhost -consul.port 8500
> ts=2019-02-26T07:49:39.0468058Z caller=main.go:54 transport=HTTP addr=9090
複製代碼
使用postman按下圖方式執行請求測試,會發現服務調用成功。微信
同時,在終端能夠看到以下輸出,說明屢次請求訪問了不一樣的服務實例:負載均衡
ts=2019-02-26T07:49:39.0468058Z caller=main.go:54 transport=HTTP addr=9090
ts=2019-02-26T07:49:46.8559985Z caller=main.go:94 serviceid=arithmetic82460623-ccdc-4192-a042-c0603ef18888
ts=2019-02-26T07:50:00.1249302Z caller=main.go:94 serviceid=arithmetic65153818-27b3-4f19-8fd1-d7d698168f20
ts=2019-02-26T09:04:09.0470362Z caller=main.go:94 serviceid=arithmetic65153818-27b3-4f19-8fd1-d7d698168f20
ts=2019-02-26T09:04:10.176327Z caller=main.go:94 serviceid=arithmetic65153818-27b3-4f19-8fd1-d7d698168f20
複製代碼
本文使用反向代理技術,結合註冊中心consul實現了簡單的API網關。因爲golang提供了反向代理工具包,使得整個實現過程比較簡單。實際項目中使用的產品,如Zuul、Nginx等,還包含了限流、請求過濾、身份認證等功能。該網關僅僅實現了請求的代理,重點在於瞭解其內部過程,加深理解。
本文首發於本人微信公衆號【兮一昂吧】,歡迎掃碼關注!