Go语言微服务实战之API网关

上一篇文章我们用etcd做为服务发现组件,替换了micro默认的基于mnds的服务发现,并简单通过跟踪源码了解了服务注册以及发现的原理。这篇文章,我们来认识微服务架构中另外一个很常见的东东:API Gateway。

1、API网关是什么

我们把一个应用拆分成了一个一个的微服务后,客户端如何调用就是个问题,因为服务是部署在不同的机器上面,这样客户端(比如iOS,android,web)势必将使用很多不同的URL,API网关最主要的作用实际上就是作为一个统一的入口,所有的请求都指向网关,再由网关负责协议转换,并将请求路由到后端服务上,以下是micro github主页上关于api gateway的一张图:

Go语言微服务实战之API网关_第1张图片

除了做为统一的请求入口外,API网关还具备安全防护、限流、熔断等功能。通常大厂都有自己自研的一套网关用来接入其内部服务,比如腾讯的TGW。

micro框架也实现了基于HTTP协议的网关,用户通过http协议向网关发送请求,再由网关将请求转发给后端服务。

从安全性来讲,micro实现的这个网关支持ACME和TLS。

至于网关的性能,目前还没做过测试,不得而知,因为网关并不做特别重的逻辑,主要是负责转发请求,因此猜测中小规模的APP应该是可以支持到。

2、增加网关支持

现在我们就继续开撕代码,给我们的示例程序加个网关玩玩。

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

Go语言微服务实战之API网关_第2张图片

我们用到以下几个选项:

--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网关,统一请求入口,现在我们这个极简单的微服务已经是“麻雀虽小五脏俱全了”

你可能感兴趣的:(后端,微服务,API网关,go)