go-kit微服務:一個簡單的API閘道器

rayson發表於2019-02-26

概述

在上一篇文章《go-kit微服務:服務註冊與發現》中,算術服務註冊至consul,發現服務使用go-kit工具集實現了對算術服務的發現功能。通過檢視原始碼可以發現,其中僅僅實現了一個介面/calculate的處理邏輯,那如果算術服務的介面很多怎麼辦呢?

這幾天這個問題一直困擾著我,我認為go-kit會提供一種合理的解決方案,可是始終沒有找到,對於sd.Factory的設計始終不理解,若哪位朋友瞭解還請指點迷津。

為了解決這個問題,我決定另闢蹊徑:根據客戶端HTTP請求,動態查詢註冊中心的服務例項,通過反向代理實現對後臺服務的呼叫。

這樣就相當於實現一個簡單的閘道器,凡是符合規則的請求都可以通過此閘道器呼叫後端服務。這裡的規則是指http請求的資源路徑,規則為:/{serviceName}/#。即:路徑第一部分為註冊中心服務例項名稱,其餘部分為服務例項的REST路徑。如:

/arithmetic/calculate/Add/10/2
複製程式碼
  • arithmetic為服務名稱;
  • /calculate/Add/10/2為算術服務的介面。

實現閘道器

Step-1:實現思路

客戶端向閘道器發起請求,閘道器解析請求資源路徑中的資訊,根據服務名稱查詢註冊中心的服務例項,然後使用反向代理技術把客戶端請求轉發至後端真實的服務例項,請求執行完畢後,再把響應資訊返回客戶端。

閘道器實現思路

  • HTTP請求的規則遵循:/{serviceName}/#,否則不予通過。
  • 使用golang提供的反向代理包httputil.ReverseProxy實現一個簡單的反向代理,它能夠對請求實現負載均衡,隨機地把請求傳送給服務例項。
  • 使用consul客戶端api動態查詢服務例項。

Step-2:編寫反向代理方法

arithmetic_consul_demo下建立目錄gateway,然後新建go檔案main.go。NewReverseProxy方法接受兩個引數:consul客戶端物件和日誌記錄工具,返回反向代理物件。該方法的實現過程如下所述:

  • 獲取請求路徑,檢查是否符合規則,不符合規則直接返回;
  • 解析請求路徑,獲取服務名稱(第一個部分為服務名稱);
  • 使用consul客戶端查詢服務例項,若查詢到結果,則隨機選擇一個作為目標例項;
  • 根據選定的目標例項,設定反向代理引數:SchemaHostPath

完整程式碼如下:

// 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}

}
複製程式碼

Step-3:編寫main方法

main方法的主要任務是建立consul連線物件、建立日誌記錄物件、開啟反向代理HTTP服務。整個過程與前面幾個示例類似,直接貼程式碼(為了測試方便,我直接指定了consul服務地址資訊):

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)
}
複製程式碼

Step-4:執行

  1. 使用docker啟動consul。終端切換至arithmetic_consul_demo目錄,執行以下命令:
sudo docker-compose -f docker/docker-compose.yml up
複製程式碼
  1. 啟動算術運算服務。為了測試負載均衡效果,我啟動了兩個例項。注意需要使用不同的埠
./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
複製程式碼
  1. 在瀏覽器輸入http://localhost:8500可看到arithmetic例項有兩個,如下圖:

服務例項

  1. 在終端cd至目錄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
複製程式碼

Step-5:測試

使用postman按下圖方式執行請求測試,會發現服務呼叫成功。

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等,還包含了限流、請求過濾、身份認證等功能。該閘道器僅僅實現了請求的代理,重點在於瞭解其內部過程,加深理解。

本文參考

本文首發於本人微信公眾號【兮一昂吧】,歡迎掃碼關注!

go-kit微服務:一個簡單的API閘道器

相關文章