go-micro V2 从零开始(四)集成micro api网关
本文相关代码:gitee文章目录前言具体步骤一、启动服务二、启动网关2.1 网关启动命令2.2 service not found 异常处理2.3 接口调用三、编写api服务3.1 安装go-restful3.2 REST 映射前言上一章我们参考demo程序hello-service手写了第一个go-micro服务task-srv,并通过编写task-cli.go成功实现了微服务调用。这一章,我们
本文相关代码:gitee
文章目录
前言
在之前的部分,我们分别用gRpc和消息实现了微服务间的相互调用。
这一章,我们重点研究如何运用网关向前端提供统一的http
接口调用服务。
具体步骤
一、启动服务
启动task-srv
服务:
> go run main.go
2020-09-11 09:55:16 file=v2@v2.9.1/service.go:200 level=info Starting [service] go.micro.service.task
2020-09-11 09:55:16 file=grpc/grpc.go:864 level=info Server [grpc] Listening on [::]:56487
2020-09-11 09:55:16 file=grpc/grpc.go:697 level=info Registry [mdns] Registering node: go.micro.service.task-8d0a083e-b117-4498-bc2f-7b5cb72cc707
二、micro api 网关
之前已经说过micro
工具包为我们微服务开发提供了很多强大的辅助功能,现在我们来试试它提供的API网关服务。
2.1 启动网关
在另一个命令行窗口,运行micro
网关命令:
> micro api --namespace=go.micro --type=service
2020-09-11 11:45:42 file=api/api.go:285 level=info service=api Registering API Default Handler at /
2020-09-11 11:45:42 file=http/http.go:90 level=info service=api HTTP API Listening on [::]:8080
2020-09-11 11:45:42 file=v2@v2.9.1/service.go:200 level=info service=api Starting [service] go.micro.api
2020-09-11 11:45:42 file=grpc/grpc.go:864 level=info service=api Server [grpc] Listening on [::]:58653
2020-09-11 11:45:42 file=grpc/grpc.go:697 level=info service=api Registry [mdns] Registering node: go.micro.api-227b83a3-686c-4766-a44f-b15544cda1b6
可以看到网关自身以go.micro.api-227b83a3-686c-4766-a44f-b15544cda1b6
注册为微服务,同时监听8080端口对外提供web服务。
micro api
命令默认namespace=go.micro
, type=api
。
需要注意的是,一个正常的三层服务架构是这样的:
服务 | 路径 | 说明 |
---|---|---|
micro api | localhost:8080 | 作为http入口点 |
api服务 | go.micro.api.XXX | 为面向公众提供服务 |
后端服务 | go.micro.srv.XXX | 内部范围服务 |
这里我们强行设置--type=service
主要是为了展示网关功能,正常情况不建议这么做。
2.2 service not found 异常处理
这里我必须要强烈吐槽,本来根据micro new
命令的经验,我第一时间就想到了上面的命令写法,但是当时在学习,很多博主东拼西凑的教程里,经常出现这样一种写法micro api --namespace=go.micro.service
,你要是跟着这么写,web调用时就会看到:
{
"id": "go.micro.service.api",
"code": 500,
"detail": "service not found",
"status": "Internal Server Error"
}
实际上翻阅官方早期文档会发现,这种写法早就已经被namespace + type替代了。
2.3 接口调用
网关默认会以服务名/api名/方法名
的方式将服务接口映射为http路径,接受各种类型(POST/GET/…)的请求:
http://{host}:{post}/{serverName}/{apiName}/{methodName}
至此,一个可用的http服务就完成了。但从后端来看他很简陋,同时也并不是标准的restful接口,下面我们自己实现一个restful标准的API服务。
三、编写api服务
正如前面所说,一个正常的三层服务架构是这样的:
服务 | 路径 | 说明 |
---|---|---|
micro api | localhost:8080 | 作为http入口点 |
api服务 | go.micro.api.XXX | 为面向公众提供服务 |
后端服务 | go.micro.srv.XXX | 内部范围服务 |
正常开发不应直接暴露内网的gRPC接口,而是编写专门的api服务,由api服务定义向外暴露那些接口。
3.1 安装gin
不明白网上为什么普遍教程都使用go-restful
来编写API服务,这里我还是推荐我平时使用比较多的gin框架:
go get github.com/gin-gonic/gin
3.2 修改task.proto
gin框架支持自动绑定请求参数到struct,并可以根据用户配置对参数进行校验,主要要在struct的tag增加form
参数。
原则上,我们应该为api接口参数设置专用的struct,不过这只是个演示项目,业务也很简单,我们就直接复用task.ptoto文件里的消息结构,仍然使用之前的protoc-go-inject-tag
工具为生成文件增加form tag:
//声明proto本版
syntax = "proto3";
//服务名
package go.micro.service.task;
//生成go文件的包路径
option go_package = "proto/task";
//定义task服务的接口,主要是增删改查
//结构非常类似于go语言的interface定义,只是返回值必须用括号包裹,且不能使用基本类型作为参数或返回值
service TaskService {
rpc Create(Task)returns (EditResponse){}
rpc Delete(Task)returns (EditResponse){}
rpc Modify(Task)returns (EditResponse){}
rpc Finished(Task)returns (EditResponse){}
rpc Search(SearchRequest)returns (SearchResponse){}
}
//下面是消息体message的定义,可以暂时理解为go中的struct,其中的1,2,3...是每个变量唯一的编码
message Task {
//每条任务的ID,本项目中对应mongodb记录的"_id"字段
//@inject_tag: bson:"_id" form:"id"
string id = 1;
//任务主体文字
//@inject_tag: bson:"body" form:"body"
string body = 2;
//用户设定的任务开始时间戳
//@inject_tag: bson:"startTime" form:"startTime"
int64 startTime = 3;
//用户设定的任务截止时间戳
//@inject_tag: bson:"endTime" form:"endTime"
int64 endTime = 4;
//任务是否已完成
//@inject_tag: bson:"isFinished" form:"isFinished"
int32 isFinished = 5;
//用户实际完成时间戳
//@inject_tag: bson:"finishTime" form:"finishTime"
int64 finishTime = 6;
//任务创建时间
//@inject_tag: bson:"createTime" form:"createTime"
int64 createTime = 7;
//任务修改时间
//@inject_tag: bson:"updateTime" form:"updateTime"
int64 updateTime = 8;
//用户ID
//@inject_tag: bson:"userId" form:"userId"
string userId=9;
}
//增删改接口返回参数
message EditResponse {
//操作返回的消息
string msg = 1;
}
//查询接口的参数
message SearchRequest{
//分页查询页码,从第一页开始
//@inject_tag: form:"pageSize"
int64 pageSize = 1;
//分页查询每页数量,默认20
//@inject_tag: form:"pageCode"
int64 pageCode = 2;
// 排序字段
//@inject_tag: form:"sortBy"
string sortBy = 3;
// 顺序 -1降序 1升序
//@inject_tag: form:"order"
int32 order=4;
//关键字模糊查询任务body字段
//@inject_tag: form:"keyword"
string keyword = 5;
}
message SearchResponse{
//分页查询页码,从第一页开始
//@inject_tag: form:"pageSize"
int64 pageSize = 1;
//分页查询每页数量,默认20
//@inject_tag: form:"pageCode"
int64 pageCode = 2;
// 排序字段
//@inject_tag: form:"sortBy"
string sortBy = 3;
// 顺序 -1降序 1升序
//@inject_tag: form:"order"
int32 order=4;
//数据总数
//@inject_tag: form:"total"
int64 total = 5;
//具体数据,这里repeated表示可以出现多条,类似于go中的slice
//@inject_tag: form:"rows"
repeated Task rows = 6;
}
3.3 创建新项目
我们再创建一个新项目task-api
mkdir task-api
此时整个项目目录如下:
新建并编辑task-api/main.go
:
package main
import (
"github.com/gin-gonic/gin"
"github.com/micro/go-micro/v2/client"
"github.com/micro/go-micro/v2/web"
pb "go-todolist/task-srv/proto/task"
"log"
)
// task-srv服务的restful api映射
func main() {
g := gin.Default()
service := web.NewService(
web.Name("go.micro.api.task"),
web.Address(":8888"),
web.Handler(g),
)
cli := pb.NewTaskService("go.micro.service.task", client.DefaultClient)
v1 := g.Group("/task")
{
v1.GET("/search", func(c *gin.Context) {
req := new(pb.SearchRequest)
if err := c.BindQuery(req); err != nil {
c.JSON(200, gin.H{
"code": "500",
"msg": "bad param",
})
return
}
if resp, err := cli.Search(c, req); err != nil {
c.JSON(200, gin.H{
"code": "500",
"msg": err.Error(),
})
} else {
c.JSON(200, gin.H{
"code": "200",
"data": resp,
})
}
})
}
service.Init()
if err := service.Run(); err != nil {
log.Fatal(err)
}
}
3.4 REST 映射
因为只是演示,整个调用没有额外的业务逻辑,所有代码就简单写到main.go中了。
新建并编辑go-todolist/task-api/main.go
package main
import (
"github.com/gin-gonic/gin"
"github.com/micro/go-micro/v2/client"
"github.com/micro/go-micro/v2/web"
pb "go-todolist/task-srv/proto/task"
"log"
)
// task-srv服务的restful api映射
func main() {
g := gin.Default()
service := web.NewService(
web.Name("go.micro.api.task"),
web.Address(":8888"),
web.Handler(g),
)
cli := pb.NewTaskService("go.micro.service.task", client.DefaultClient)
v1 := g.Group("/task")
{
v1.GET("/search", func(c *gin.Context) {
req := new(pb.SearchRequest)
if err := c.ShouldBind(req); err != nil {
c.JSON(200, gin.H{
"code": "500",
"msg": "bad param",
})
return
}
if resp, err := cli.Search(c, req); err != nil {
c.JSON(200, gin.H{
"code": "500",
"msg": err.Error(),
})
} else {
c.JSON(200, gin.H{
"code": "200",
"data": resp,
})
}
})
}
service.Init()
if err := service.Run(); err != nil {
log.Fatal(err)
}
}
运行main.go
,使用postman访问8888端口,熟悉的restful调用就完成了:
四、micro api反向代理
完成api服务后,我们当然可以使用传统的nginx反向代理,通过配置文件对外暴露api,但是micro工具包为我们提供了更方便的方式。
再次启动micro 网关,这次我们加一个参数--header=http
他会将网关变为一个基于服务发现的http代理:
> micro api --handler=http
现在我们访问micro网关的8080端口,不需要任何额外配置,http接口被自动发现并代理了:
五、grpc-gateway(选读)
grpc-gateway是一个protoc的插件,能够从proto文件读取gRPC服务定义,并生成将RESTful JSON API转换为gRPC的反向代理服务器。
这是我在网上看到转发最多的一个go-micro集成grpc-gateway的例子,推荐大家可以跟着教程试一试,感受一下他的用法:
不论你是否跟着上面链接的教程尝试过grpc-gateway,这里说结论:
- 优点:实现接口代理非常方便,只需要在proto文件定义路径和请求方式,main.go文件虽然有一点点代码,但几乎可以完全复用不需要大的修改。
- 缺点:不支持像micro api一样的服务发现能力,需要用户手动指定被代理方的地址,同时多个服务的负载均衡也需要在代码中自己实现。
最后,我选择了功能更加强大的且与整个系统集成度更高的micro api
方案
总结
这一章是本系列文章第一部分的最后一章。
现在,我们已经简单体验了go-micro框架的基础功能,能够实现服务的自动注册和发现,基于gRpc和消息实现服务调用,使用默认网关对外提供http服务接口,完成了从零开始
的第一步——从无到有。
下一阶段,我们将围绕go-micro的插件设计,学习如何快速且灵活的为项目集成服务治理(etcd)消息队列(nats)熔断器(hystrix)鉴权(JWT)以及链路追踪(jaeger)
支持一下
原创不易,支持一下买杯咖啡,谢谢:p
更多推荐
所有评论(0)