隨着微服務的快速發展,愈來愈多的公司選擇使用「金絲雀發佈」的模式進行軟件的發佈。在本文中我將經過華爲的開源微服務框架:go-chassis,向各位介紹如何經過對router的管理從而達到金絲雀發佈的目的。git
圖一
github
金絲雀發佈:又叫灰度發佈,使程序能夠在黑白之間進行新老版本平滑過分地發佈,使應用能夠更加平穩地升級。 如圖一所示,須要將原100%訪問 version 1.0的流量一半分流至新的版本version 2.0,此時咱們經過如下簡單的配置,咱們就能夠實現圖一所示的效果。apache
routeRule:
RESTServer:
- precedence: 1 # 優先級,數字越大優先級越高
route: #路由規則列表
- tags:
version: 1.0 #對接service center的話,若是不填就自動爲0.1
weight: 50 #全重 50%到這裏
- tags:
version: 2.0
weight: 50 #全重 50%到這裏複製代碼
經過流量的分流,在老版本依舊正常服務的狀況下,經過引流部分流量訪問新的版本,進行新版本的發佈。若是新版本一切正常,以及用戶對新功能也滿意,咱們就能夠逐步將本來分流至version 1.0的流量所有分流至version 2.0。等所有請求version 2.0後,咱們就能夠將version 1.0 服務逐步下線。可是若是version 2.0在運行過程當中出現嚴重的問題,也能夠迅速將請求所有訪問version 1.0,而後將新本版所有下線。json
此時也許會有人問,咱們不單單是爲了將全部的請求進行分流,咱們僅僅須要某些功能進行分流,如圖二bash
圖二restful
圖二中,咱們只是須要將header含有chassis:v2進行分流,將80%的請求訪問新斑斑,其他的請求將繼續訪問老版本。此時咱們可使用以下配置已達到圖二中特定的效果app
routeRule:
RESTServer: #這裏就是請求裏的host,也是sc裏的service name
- precedence: 1 # 優先級,數字越大優先級越高
route: #路由規則列表
- tags:
version: 1.0 #對接service center的話,若是不填就自動爲0.1
weight: 100 #全重 100%到這裏
- precedence: 2
match:
headers:
Chassis: # 請求header中有V2
regex: V2
route: #路由規則列表
- tags:
version: 2.0
weight: 80 #權重80%到這裏
- tags:
version: 1.0
weight: 20 #權重100%到這裏複製代碼
go-chassis文檔less
go-chassis 是華爲的一個開源微服務框架,該框架集成了許多功能,爲求爲廣大使用者這提供一站式的服務。curl
go-chassis的路由管理可根據版本設置流量權重,header匹配,模板匹配等規則進行設置,讓你輕鬆經過路由管理實現金絲雀發佈。
配置項 |
說明 |
precedence |
優先級配置,數字越大則優先級越高 |
match |
匹配特定請求,未定義則匹配任何請求 |
headers |
header規則設置,支持正則, 等於, 小於, 大, 於不等於等匹配方式 |
router |
配置路由規則列表 |
tags |
tags屬性的配置,用於路由分發規則的version和appID |
weight |
router下的tag規則設置,設置請求權重,值爲0-100之間 |
version |
router下的tag規則設置,設置請求的version,與weight搭配使用 |
refer |
指定須要引用的模板,使用定義的模板名稱 |
sourceTemplate |
定義請求模板 |
若是headers下同時配置多個匹配條件,則須要同時知足全部配置條件,纔會執行該header下的router規則
exact : 精確匹配, 必須等於該配置值
regex:按正則匹配header內容
noEqu:不等於。Header不等於配置值
noLess:大於等於。header不小於配置值
noGreater:小於等於。header不大於配置值
greater:大於。header大於配置值
less:小於。header小於配置值
在文中,將會經過展現demo使用的router配置進行詳細的介紹,以及實現的功能進行說明
例子中使用的go-chassis的rest協議
運行前,請前往一下地址下載 server center,並啓動server center
go get -u github.com/go-chassis/go-chassis複製代碼
server-v1
在conf文件下新增或修改你的配置文件
配置microservice.yaml
---
#Private property of microservices
service_description:
name: RESTServer
version: 1.0複製代碼
name:配置你的服務名
version:配置服務版本號
配置chassis.yaml
---
cse:
service:
registry:
address: http://127.0.0.1:30100 #
scope: full #set full to be able to discover other app's service
protocols:
rest:
listenAddress: 127.0.0.1:9091複製代碼
在這裏使用的rest協議,須要聲明所須要的struct,以及實現URLPatterns,該方法主要用於路由的設置。
HelloServer
// HelloServer
type HelloServer struct {
}複製代碼
URLPatterns
// URLPatterns
func (*HelloServer) URLPatterns() []restful.Route {
return []restful.Route{
{Method: http.MethodGet, Path: "/hello/{name}", ResourceFuncName: "Hello"},
}
}複製代碼
完整代碼以下
package main
import (
"net/http"
"github.com/go-chassis/go-chassis"
"github.com/go-chassis/go-chassis/core/lager"
"github.com/go-chassis/go-chassis/core/server"
"github.com/go-chassis/go-chassis/server/restful"
)
func main() {
chassis.RegisterSchema("rest", &ServerStruct{}, server.WithSchemaID("Hello"))
if err := chassis.Init(); err != nil {
lager.Logger.Error("Init failed." + err.Error())
return
}
chassis.Run()
}
// HelloServer
type HelloServer struct {
}
// Hello
func (*HelloServer) Hello(ctx *restful.Context) {
name := ctx.ReadPathParameter("name")
ctx.Write([]byte("Chassis V1.0 hello : " + name))
}
// URLPatterns
func (*ServerStruct) URLPatterns() []restful.Route {
return []restful.Route{
{Method: http.MethodGet, Path: "/hello/{name}", ResourceFuncName:"Hello"},
}
}複製代碼
啓動server V1
go build main.go
./main複製代碼
若是你直接啓動,須要設置環境變量:CHASSIS_HOME,若是未設置該環境變量,啓動時將找不到你的配置文件
CHASSIS_HOME=/{path}/{to}/serverV1/複製代碼
也就是你的conf所在的目錄
server-v2
與server-v1相似,只要修改一下幾個地方便可
在 microservice.yaml 中將version修改成2.0,此處沒強制性要求,只要修改和不一致便可。修改Hello 方法,爲了方便區分服務之間的不一樣
// Hello
func (*HelloServer) Hello(ctx *restful.Context) {
name := ctx.ReadPathParameter("name")
ctx.Write([]byte("Chassis V2.0 hello : " + name))
}複製代碼
修改chassis.yaml中的監聽端口
完成以上修改了,就能夠啓動server-v2了
Client
前面介紹到的路由管理,也是在client端進行配置,下面展現在例子中使用到router.yaml配置
routeRule:
RESTServer: # server設置的name,也是sc裏的service name
- precedence: 1 # 優先級,數字越大優先級越高
route: #路由規則列表
- tags:
version: 1.0 #對接service center的話,若是不填就自動爲0.1
weight: 50 #全重 50%到這裏
- tags:
version: 2.0 #對接service center的話,若是不填就自動爲0.1
weight: 50 #全重 50%到這裏
- precedence: 2
match:
headers:
Chassis: # 請求header中有v1
regex: v1
route: #路由規則列表
- tags:
version: 1.0
weight: 80 #權重 80%到這裏
- tags:
version: 2.0
weight: 20 #權重 20%到這裏
- precedence: 2
match:
headers:
Chassis: # 請求header中有v2
regex: v2
route: #路由規則列表
- tags:
version: 2.0
weight: 80#權重 80%到這裏
- tags:
version: 1.0
weight: 20 #權重 20%到這裏複製代碼
說明:
以上配置中當沒有在請求的header中設置任何值的狀況下,全部的請求將平均分配到兩個sever,可是當在header中設置 v1,此時請求將有80%分流到版本爲1.0的服務中,20%分流到版本爲2.0的服務中,而若是在header中設置了v2,則和設置v1時恰好相反。在配置文件中使用到的RESTServer爲你開發的服務名稱,若是你服務名爲 RESTxxx,則此處的RESTServer就改成RESTxxx便可。同時在設置header匹配條件是的chassis:v1也是任意的鍵值對,只要在請求時的header設置與配置一致事,router策略的配置便可生效。
client修改microservice.yaml和chassis.yaml文件,與服務端相似,name咱們修改成:RESTClient,監聽地址配置爲:8080
client代碼演示
package main
import (
"context"
"fmt"
"net/http"
"strconv"
"github.com/go-chassis/go-chassis/core"
"github.com/go-chassis/go-chassis"
"github.com/go-chassis/go-chassis/client/rest"
"github.com/go-chassis/go-chassis/core/lager"
"github.com/go-chassis/go-chassis/core/server"
"github.com/go-chassis/go-chassis/server/restful" "github.com/go-chassis/go-chassis/pkg/util/httputil"
)
func main() {
chassis.RegisterSchema("rest", &ClientStruct{}, server.WithSchemaID("Hello"))
if err := chassis.Init(); err != nil {
lager.Logger.Error("Init failed." + err.Error())
return
}
chassis.Run()
}
type ClientStruct struct {
}type result struct {
Reply string
Error string
}
func (*ClientStruct) Hello(ctx *restful.Context) {
url := ctx.ReadRequest().URL
times := ctx.ReadQueryParameter("times")
sefverName := ctx.ReadQueryParameter("server")
timeNum, _ := strconv.Atoi(times)
results := []result{}
for i := 0; i < timeNum; i++ {
result := invoker(url.Path, sefverName)
results = append(results, result)
}
ctx.WriteJSON(results, "application/json")
}
func invoker(url, serverName string) result {
callUrl := fmt.Sprintf("cse://%s/%s", serverName, url)
req, err := rest.NewRequest(http.MethodGet, callUrl,nil)
if err != nil {
return result{Error: err.Error()}
}
// req.Header.Set("Chassis", "v1") resp, err :=core.NewRestInvoker().ContextDo(context.TODO(), req)
if err != nil {
return result{Error: err.Error()}
}
if resp.StatusCode >= 200 || resp.StatusCode <=304 {
return result{Reply: string(httputil.ReadBody(resp))}
}
return result{Reply: string(httputil.ReadBody(resp))}
}
// URLPatterns
func (*ClientStruct) URLPatterns() []restful.Route
{
return []restful.Route{
{Method: http.MethodGet, Path: "/hello/{name}", ResourceFuncName: "Hello"},
}
}複製代碼
演示代碼中使用到的times主要爲了實現一次請求,咱們能夠屢次向sever發起請求,簡化測試。爲了簡化,在client所提供的route和server是一致的,直接將請求在拼接「cse://」後就能夠轉發至server,查詢參數中server獲取值爲啓動的服務名稱,通常client和server對外提供通常是不一致的,這裏只是爲了demo演示方便。
client代碼編寫完而且啓動後,使用以下命令進行訪問
curl http://127.0.0.1:8080/hello/chassis?times=10&server=RESTServer複製代碼
最後推薦關於go-chassis的文章,如下的文章可讓你更加深刻的對go-chassis的進行了解