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

【導讀】本文介紹了用 Go 語言編寫 API Gateway 所需的技術和設計。

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

轉自:

blog.csdn.net/ztemt_sw2/article/details/106208946

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/LFsHiLWEK3jSNa7-Uzf9Ug