Post

rpc远程服务内嵌思路

rpc远程服务内嵌思路

笔者最近遇到这样的需求,存在一个微服务,希望将其服务嵌入到项目代码中。怎么理解呢?就是把微服务项目当做第三方库使用,这样项目中微服务就是本地调用,并且依赖的中间件资源也可以使用本地的。

如何设计微服务能够做到既能独立部署,又能嵌入到项目中呢?在这里,笔者不得不再次感叹go的interface的强大。通过interface就可以实现,实际上grpc框架已经提前考虑这个问题,其暴露出来的大部分都是interface结构。

调用一个grpc远程服务,需要客户端(由proto文件生成)。grpc的客户端是这样的结构:

1
2
3
4
type Client interface {
    RpcCall(context.Context, in, ...grpc.CallOption) (out, error)
    ...
}

可以看到client是interface,说明我们可以自己伪造client,调用自己的本地调用。现在又有另外一个问题,如何向调用框架一般调用远程rpc服务呢?首先需要我们在设计上把远程服务的实现逻辑和调用逻辑解耦开来。

我们的rpc服务需要这样设计:

1
2
3
4
5
func NewService(opts ...Option) *Service
type Service struct {
    unimplemented.UnimplementedServiceServer
    opts *options
}

将这个服务传递给grpcserver来注册服务,同时与grpcserver解耦开,可以本地创建。我们在本地嵌入的地方,创建一个本地的Service:

1
2
3
4
var localService *Service
func init() {
    localService = NewService(opts...)
}

然后我们注册在伪造的客户端中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func NewLocalClient(srv *Service) Client {
    return &localClient{
        Srv:    srv,
    }
}

type localClient struct {
    Client
    Srv *Service
}

func (c *localClient) RpcCall(ctx context.Context, in, opts ...grpc.CallOption) (out, error)· {
    // 这里可以直接调用本地的服务逻辑
    return c.Srv.HandleRequest(ctx, in)
}

这样就实现了客户端的伪造,同时由于客户端实现grpcclient的接口,所以也不会影响到项目调用逻辑,这样就能实现将远程服务的调用无感的迁移到嵌入第三方框架服务的调用。

反思

笔者的第一版实现是对client进行封装,通过条件判断是否调用远程还是本地,这样的设计的吃力不讨好的,首先远程服务的代码没有复用,同时封装提高系统的复杂度,其实完全没有必要,妙用interface能够优雅地解决很多问题。

This post is licensed under CC BY 4.0 by the author.