Go語言微服務實戰之API網關

上一篇文章我們用etcd做爲服務發現組件,替換了micro默認的基於mnds的服務發現,並簡單通過跟蹤源碼瞭解了服務註冊以及發現的原理。這篇文章,我們來認識微服務架構中另外一個很常見的東東:API Gateway。

1、API網關是什麼

我們把一個應用拆分成了一個一個的微服務後,客戶端如何調用就是個問題,因爲服務是部署在不同的機器上面,這樣客戶端(比如iOS,android,web)勢必將使用很多不同的URL,API網關最主要的作用實際上就是作爲一個統一的入口,所有的請求都指向網關,再由網關負責協議轉換,並將請求路由到後端服務上,以下是micro github主頁上關於api gateway的一張圖:

除了做爲統一的請求入口外,API網關還具備安全防護、限流、熔斷等功能。通常大廠都有自己自研的一套網關用來接入其內部服務,比如騰訊的TGW。

micro框架也實現了基於HTTP協議的網關,用戶通過http協議向網關發送請求,再由網關將請求轉發給後端服務。

從安全性來講,micro實現的這個網關支持ACME和TLS。

至於網關的性能,目前還沒做過測試,不得而知,因爲網關並不做特別重的邏輯,主要是負責轉發請求,因此猜測中小規模的APP應該是可以支持到。

2、增加網關支持

現在我們就繼續開撕代碼,給我們的示例程序加個網關玩玩。

先來看看micro api的基本用法,我們再命令行輸入:micro api --help,可以看到以下輸出:

我們用到以下幾個選項:

--address:指定網關地址,默認實在8080端口

--namespace:可以通過namespace將服務歸類,例如對外的公共服務和內部服務,注意這裏的域名要跟代碼對應起來,比如域名爲com.test.api,那麼代碼中的服務名就要以com.test.api開頭。

--handler:網關用到的http handler,不同的handler處理不同的請求,對請求的處理方式也有所不同,micro支持以下幾種handler:

api handler:可以處理任意http請求,並以RPC的方式將請求轉發給後端服務,url path爲/service/method,例如http://localhost:8080/greeter/hello,則網關會在註冊中心中尋找endpoints爲Greeter.Hello的服務並轉發;

rpc handler:處理http post請求,http body爲json或者protobuf格式(Content-Type爲application/json或者application/protobuf),url path的要求同api handler;

event handler:將請求做爲消息發送到消息中間件上;

proxy handler:用於http反向代理;

具體用哪種handler需要根據自己業務的需求來定,我們就用rpc handler來實操一下。

2.1 啓動網關

我們先來啓動網關:

注意在啓動網關時的一些參數:

MICRO_REGISTRY:用來指定註冊中心,我們用etcd做註冊中心,所以這個值寫etcd;

MICRO_REGISTRY_ADDRESS:指定註冊中心的地址,本地測試,就寫本機地址(etcd默認端口是2379)

--handler:我們採用rpc handler,網關將會根據服務的EndPoint找到服務並轉發請求;

--namespace:指定命名空間

--address:網關地址

2.2 修改代碼

服務端的代碼與之前一樣,我們稍作修改,改一下服務名:

package main

import (
	"context"
	"fmt"
	"github.com/micro/go-micro/v2"
	"micro/proto/pb"
	"micro/registry/etcd"
)

type Greeter struct {

}

func (g *Greeter) Hello(ctx context.Context, req *pb.Request, rsp *pb.Response) error  {
	//把客戶端的請求回射給客戶端
	rsp.Msg = req.Name
	return nil
}

func main() {
	// 新創建一個服務,服務名爲greeter,服務註冊中心會用這個名字來發現服務
	service := micro.NewService(
		micro.Name("svr.greeter"),
		micro.Registry(etcd.NewRegistry()),
	)

	// 初始化
	service.Init()

	// 註冊處理器
	pb.RegisterGreeterHandler(service.Server(), new(Greeter))

	// 啓動服務運行
	if err := service.Run(); err != nil {
		fmt.Println(err)
	}
}

我們只是在創建服務的時候將服務名改成了svr.greeter,其他沒有任何變化。

然後在終端啓動運行服務即可。

下面要修改客戶端,客戶端實際上是服務端的一個代理,從設計模式的角度來看實際上就是個代理模式,因此客戶端和服務端需要實現相同的接口,在客戶端可以做一些額外的操作(比如參數安全校驗之類的),然後通過rpc調用服務端。

package main

import (
	"context"
	"fmt"
	"github.com/micro/go-micro/v2"
	"micro/proto/pb"
	"micro/registry/etcd"
)

const (
	apiNameSpace   = "com.jupiter.api"
	service        = "greeter"
	backendService = "svr.greeter"	//真正做事情的後端服務
)

/**
 * url path:/greeter/hello
 * 接收網關轉發的請求,通過rpc將請求轉發給後端服務
 * 實際上就是個後端服務的代理(客戶端),實現和服務端相同的接口
 */
type Greeter struct {
	Client pb.GreeterService
}

func (g *Greeter) Hello(ctx context.Context, req *pb.Request, rsp *pb.Response) error {
	//通過rpc調用服務端
	response, e := g.Client.Hello(ctx, &pb.Request{Name: "Hello Micro"})
	if e != nil {
		return e
	}

	rsp.Msg = response.Msg
	return nil
}

func main() {
	// 創建一個服務
	service := micro.NewService(
		micro.Name(apiNameSpace + "." + service),	//注意這裏服務的名稱:namespace + service,這樣網關才能找的到
		micro.Registry(etcd.NewRegistry()))
	// 初始化
	service.Init()

	pb.RegisterGreeterHandler(service.Server(), &Greeter{
		Client: pb.NewGreeterService(backendService, service.Client()),
	})

	if err := service.Run(); err != nil {
		fmt.Println(err)
	}
}

以上代碼中關鍵的地方都做了註釋,在強調一下:

代理服務的名稱必須是namespace.service,namespace是api網關的namespace,service是在註冊中心的EndPoint的服務名,這個名稱一般就是.proto協議文件中定義的service的名字,例如我們的.proto定義:

// 定義微服務對外提供的接口
service Greeter {

    rpc Hello(Request) returns (Response) {}
}

那麼service就是Greeter,完整的名稱就是com.jupiter.api.greeter。

現在我們再終端啓動客戶端運行。

2.3 測試

現在來測試一下加了網關後的服務,我們再終端用curl請求一下:

輸出了我們想要的結果!

3 小結

這篇文章我們進一步擴展了微服務示例代碼,在應用中加入了API網關,統一請求入口,現在我們這個極簡單的微服務已經是“麻雀雖小五臟俱全了”

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章