2 回答
TA贡献1851条经验 获得超4个赞
是的,每个服务有一个 GRPC 客户端连接很好。此外,我在这里看不到任何其他选项。GRPC 在幕后完成所有繁重的工作:例如,您不需要编写自己的客户端连接池(就像您为典型的 RDBMS 所做的那样),因为它不会提供比单个 GRPC 连接更好的结果。
但是我建议您避免使用全局变量和初始化函数,尤其是对于网络设置。此外,您不需要c := user.NewUserClient(userConn)在每次向 GRPC 服务发送请求时都创建 GRPC 客户端 ( ):这只是垃圾收集器的额外工作,您可以在应用程序启动时创建客户端的唯一实例。
更新
假设您正在编写服务器应用程序(因为它可以从您调用远程 GRPC 服务的方法中看出),您可以简单地定义一个类型,该类型将包含与整个应用程序本身具有相同生命周期的所有对象。按照传统,这些类型通常被称为“服务器上下文”,尽管这有点令人困惑,因为 Gocontext在其标准库中有一个非常重要的概念。
// this type contains state of the server
type serverContext struct {
// client to GRPC service
userClient user.UserClient
// default timeout
timeout time.Duration
// some other useful objects, like config
// or logger (to replace global logging)
// (...)
}
// constructor for server context
func newServerContext(endpoint string) (*serverContext, error) {
userConn, err := grpc.Dial(endpoint, grpc.WithInsecure())
if err != nil {
return nil, err
}
ctx := &serverContext{
userClient: user.NewUserClient(userConn),
timeout: time.Second,
}
return ctx, nil
}
type server struct {
context *serverContext
}
func (s *server) Handler(ctx context.Context, request *Request) (*Response, error) {
clientCtx, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
response, err := c.GetUserFromTokenID(
clientCtx,
&user.GetUserFromTokenRequest{
TransactionID: transactionID,
OathToken: *oathToken,
},
)
if err != nil {
return nil, err
}
// ...
}
func main() {
serverCtx, err := newServerContext(os.Getenv("USER_SERVICE_URL"))
if err != nil {
log.Fatal(err)
}
s := &server{serverCtx}
// listen and serve etc...
}
细节可能会根据您的实际工作而改变,但我只是想表明,将应用程序的状态封装在不同类型的实例中比感染全局命名空间要好得多。
TA贡献1776条经验 获得超12个赞
一些事情使这个实现工作。
gRPC 通道(即
c
fromc := user.NewUserClient(userConn)
)由 http/2 连接支持。当连接关闭或失效时,它会自动重新连接或重试连接。http/2 支持在单个连接中同时发送消息。考虑到这种场景,service Order 对service Product 一次获取一个产品库存,更新产品库存,更改产品优惠券。三个 grpc 请求可以重用单个 http/2 连接,grpc 将并发处理数据交换。所以只使用一个连接是可以的,而不是创建三个连接(比如 http/1)来实现这一点。
为避免过早优化,一个连接应该可以为一项服务启动。为了将来的池性能,考虑为热点 grpc 请求创建一个单独的 tcp 连接(然后单独的 http/2 连接)。
保持连接活动可能是件好事,以防某些代理可能会终止空闲连接。
- 2 回答
- 0 关注
- 203 浏览
添加回答
举报