探讨实现Go服务发起RPC调用最优串并行关系

2023年 7月 25日 60.7k 0

背景

在服务端开发中我们经常会在一个服务中发起rpc请求调用其他服务。很多服务主要逻辑就是根据产品逻辑调用各个rpc请求,再把各个请求的结果组合在一起返回。业内把专注于开发这类服务的开发者称为API Boy。这类服务的特点是io密集,耗时主要是rpc请求。所以优化这类服务的耗时就是优化rpc请求的串并行关系。那么怎样的串并行关系才是最优的呢?其实很简单,只要做到下面两点就可以了。

  • 所有逻辑都在依赖数据准备好的第一时间开始跑,每个rpc请求都在请求数据准备好的第一时间发起。
  • 相同的数据只请求一次。

于是我们就把一个全局的问题分解成各个局部的问题,就可以分而治之,逐个击破。本文主要讨论如何在Go语言开发的服务中管理rpc调用,实现上述两点。

下面我们先讨论几种情况。

情况1 多个相同数据的调用合并为一个

func func1() {
    resp := RPC()
    handle1(resp)
}

func func2() {
    resp := RPC()
    handle2(resp)
}

func Case1() {
    go func1()
    go func2()
}

func1与func2分别调用同一个rpc获得同一个结果,然后分别对结果做处理。这里func1与func2应该合并调用一次rpc,减少下游的压力。一个问题是这里并不确定func1与func2谁会先发起rpc,解决方法是使用sync.Once。

情况2 一个协程生产,一个协程消费

func func1(resp *Resp) {
    resp = RPC()
}

func func2(resp *Resp) {
    handle(resp)
}

func Case2() {
    var resp Resp
    go func1(&resp)
    go func2(&resp)
}

这里func2在handle之前必须保证func1已经生产出resp。在Go,这一般用channel来实现。这里不能用sync.Once是因为func2没有生产能力,func2调用sync.Once会导致RPC调用不了。

最初的想法:混合sync.Once与channel

我想做一个通用的包裹来包住resp,做一个通用的数据获取接口获取resp,使其能囊括上面两种情况。于是我混合了sync.Once与channel,写出了下面的代码。核心是提供了InitAndGet接口来生产数据,Get接口来获取数据。

type DataGetter[T any] struct {
Data T
DataOnce sync.Once
SelfOnce sync.Once
IsReady chan struct{}
}

func (d *DataGetter[T]) InitAndGet(initFunc func(*T)) *T {
d.InitSelf()
d.DataOnce.Do(func() {
DoInit(initFunc)
})

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论