引言
随着微服务的快速发展,越来越多的公司选择使用“金丝雀发布”的模式进行软件的发布。在本文中我将通过华为的开源微服务框架:go-chassis,向各位介绍如何通过对router的管理从而达到金丝雀发布的目的。
图一
Go-chassis实现金丝雀发布
金丝雀发布:又叫灰度发布,使程序可以在黑白之间进行新老版本平滑过度地发布,使应用可以更加平稳地升级。 如图一所示,需要将原100%访问 version 1.0的流量一半分流至新的版本version 2.0,此时我们通过以下简单的配置,我们就可以实现图一所示的效果。
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,然后将新本版全部下线。
此时也许会有人问,我们不仅仅是为了将所有的请求进行分流,我们仅仅需要某些功能进行分流,如图二
图二
图二中,我们只是需要将header含有chassis:v2进行分流,将80%的请求访问新斑斑,其余的请求将继续访问老版本。此时我们可以使用如下配置已达到图二中特定的效果
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%到这里复制代码
1 Go-chassis介绍
go-chassis 是华为的一个开源微服务框架,该框架集成了许多功能,为求为广大使用者这提供一站式的服务。
2 Go-chassis的路由管理介绍
go-chassis的路由管理可根据版本设置流量权重,header匹配,模板匹配等规则进行设置,让你轻松通过路由管理实现金丝雀发布。
2.1 路由管理配置说明
配置项 | 说明 |
precedence | 优先级配置,数字越大则优先级越高 |
match | 匹配特定请求,未定义则匹配任何请求 |
headers | header规则设置,支持正则, 等于, 小于, 大, 于不等于等匹配方式 |
router | 配置路由规则列表 |
tags | tags属性的配置,用于路由分发规则的version和appID |
weight | router下的tag规则设置,设置请求权重,值为0-100之间 |
version | router下的tag规则设置,设置请求的version,与weight搭配使用 |
refer | 指定需要引用的模板,使用定义的模板名称 |
sourceTemplate | 定义请求模板 |
- header说明
如果headers下同时配置多个匹配条件,则需要同时满足所有配置条件,才会执行该header下的router规则
exact : 精确匹配, 必须等于该配置值
regex:按正则匹配header内容
noEqu:不等于。Header不等于配置值
noLess:大于等于。header不小于配置值
noGreater:小于等于。header不大于配置值
greater:大于。header大于配置值
less:小于。header小于配置值
在文中,将会通过展示demo使用的router配置进行详细的介绍,以及实现的功能进行说明
3快速入门---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复制代码
- address:为注册服务中心的地址,下载后为进行任何修改,默认为127.0.0.1:30100,如你已经进行修改,请使用自己修改后的值
- rest: 所选用的通讯协议,如为grpc,则填写grpc
- listenAddress:为服务监听的IP和端口
代码演示
在这里使用的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的进行了解