上一篇文章我們用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網關,統一請求入口,現在我們這個極簡單的微服務已經是“麻雀雖小五臟俱全了”