go-micro是一个后台微服务开发框架,它提供了一个分布式系统开发所需的核心要求;其最大的特点是它是一个可插拔的架构,它对分布式系统的各个组成部分都抽象成接口,例如:
什么是可插拔?就是你可以直接使用go-micro
的默认实现或者在go-plugins中选择基于不同组件实现的插件库;甚至你可以基于go-micro
框架抽象的接口来实现自己的插件库。
以上就是关于go-micro
框架的简单说明,下面的内容会记录个人在具体项目中使用go-micro
框架的心得以及问题总结(持续更新),希望能为大家提供一些帮助:
在go-micro
中主要提供了两种创建micro.Service
的方式:
gprc.NewService()
micro.NewService()
第一种方式下底层传输协议使用的是grpc
;第二种方式下底层传输协议使用的是http + protobuf
。从实际比较来看,gprc
的性能明显好于micro
。关于两者的差异可参考这篇stackoverflow提问。
在一个微服务中,一般会以Server
的身份来提供服务,但是很多时候还会以Client
的身份来调用其他微服务。我们通过示例代码来详细看一下:
以下Server
的示例代码(一般为main.go中
),会创建一个micro.Service
实例:
service := gprc.NewService()
service.Init()
// ...
proto.RegisterXXXServiceHandler(service.Server(), handler)
// ....
sevice.Run()
以下Client
的示例代码,每次都创建一个micro.Service
实例:
service := gprc.NewService()
service.Init()
// ...
client := proto.NewXXXService("XXX", service.Client())
// ...
综上,其实我们可以在main.go
中创建一个全局的Service
来进行复用,而不是每次来新创建一个。
micro.Service
初始化使用命令行参数或者环境变量
go-micro
为我们提供了一个叫micro web的工具,它会以页面的形式为我们提供以下三个功能:
help
来使用;以上这些方便我们调试某个微服务,以及查看微服务的健康情况等;不过有一点要注意的是,Call功能默认使用micro.NewService
方式来调用的,如果你的微服务使用的是grpc.NewService
的话,需要在执行命令micro web
时增加相关参数。
原因一:全局环境变量设置了http代理
在go-micro
中你可以使用micro.NewService
或者grpc.NewService
来创建micro.Service
,这两种方式的区别是底层传输协议的不一样,前者是http
,后者用的是grpc
,但是不管怎么样,最终都是基于http
的。所以如果你设置了http
代理,就会出现408 Request Timeout
的问题。以micro.NewService
为例,我们来追溯一下源代码:
// 代码调用流程:Client.Call -----> Transport.Dial
// 涉及代码文件:rpc_client.go, transport.go http_transport.go http_proxy.go
// 源代码位于 micro/transport/http_proxy.go
func getURL(addr string) (*url.URL, error) {
r := &http.Request{
URL: &url.URL{
Scheme: "https",
Host: addr,
},
}
return http.ProxyFromEnvironment(r)
}
上面注释中的Client
和Transport
都是go-micro
抽象出来的接口,请注意最后一行http.ProxyFromEnvironment(r)
,它的作用是:如果你设置了http
代理,那么它就会把代理地址返回,这样就导致Transport.Dial
接口调用超时,go-micro
框架会返回408
错误。
// 源代码位于 net/http/transport.go
func ProxyFromEnvironment(req *Request) (*url.URL, error) {
return envProxyFunc()(req.URL)
}
那么golang
的net/http
包是如何获取代理地址的呢?请看下面的代码(golang version >= 1.11),就是根据几个环境变量来的:
// 源代码位于 x/net/http/httpproxy/proxy.go
func FromEnvironment() *Config {
return &Config{
HTTPProxy: getEnvAny("HTTP_PROXY", "http_proxy"),
HTTPSProxy: getEnvAny("HTTPS_PROXY", "https_proxy"),
NoProxy: getEnvAny("NO_PROXY", "no_proxy"),
CGI: os.Getenv("REQUEST_METHOD") != "",
}
}
在golang 1.10中没有使用上述几个环境变量,通过查阅1.10版本的proxy.go源代码文件可以确认。
原因二: context.Context 使用不规范
func Hello(ctx context.Context, req *Request, rsp *Response) {
go func() {
// use ctx for other rpc call
}()
}
出现408
问题的情况除了第一种之外,还有一个情况。如上述示例代码所示,Hello
是一个rpc接口:在其内部实现中起了一个goroutine
,而这个goroutine
内部又使用外部的ctx
去调用了另外rpc接口。这种情况下,只有在grpc.NewService
这种方式下来创建micro.Service
才会出现该问题。所以这个估计与grpc-go或go-micro grpc的实现有关系,有兴趣的话,可以追一下它们的代码实现。
另外,再推荐三篇关于Context
的文章,其中摘录了一段,可能是上述情况的原因:
当顶层的Request请求处理结束,或者外部取消了这次请求,就可以cancel掉顶层context,从而使整个请求的routine树得以退出。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。