底层通信选型
设计一个微服务框架的起始点,是确定底层通信的方式。因为上层的服务治理,都是建立在这个基础之上。
可行的选择是:
- 直接使用 gRPC直接
- 使用 HTTP
- 设计自己的 RPC 协议:实践中建议直接使用 gRPC 或者 HTTP
RPC要解决的核心问题
RPC的全称是Remote Procedure Call,即远程过程调用。核心就是:如同本地调用一般调用服务器上的方法。
调用信息
要完成这种映射,首先要解决第一个问题:映射什么?
举个例子,假如说我们在客户端调用的是userService.GetByld,传入的参数是 int 类型的值 123。那么服务端究竟怎么知道客户端调用的是userService.GetByld,参数是 int 类型的 123?
那么调用信息需要包含什么?
服务名:也就是userService
方法名:GetByld
参数值:123
要不要参数类型?如果你在支持重载的语言上设计微服务框架,并且决定支持重载,那么你就需要传递参数类型,否则你不需要传递参数类型。
重载是什么?同名方法,有不同参数
RPC协议 不需要传参数类型,如果硬要传就可以实现重载的功能。
服务名是什么?
以前Dubbo 接口名注册 gRPC 应用名注册
客户端捕捉本地调用
既然要传递调用信息,那么问题就在于:RPC 客户端怎么获得这些调用信息?这个问题是指:用户调用的是userService.GetByld(123),我底层框架怎么知道 userService, GetByld, 123这种信息?
做法1: 代码生成策略,如gRPC、go-micro
代码生成策略,代码写死的
做法2: 代理机制,如Dubbo
Go不支持动态代理,不支持动态生成新的类型,不支持动态加载。
代理模式
这里就要用到代理模式了:
定义一个结构体,为结构体里面的方法类型字段,注入调用逻辑。
要记住,Go 是没有办法修改方法实现的,所以我们只能迂回救国
我们约定:
- 方法参数的第一个必须是 context.Context,
第二个就是请求结构体指针,
并且只有这两个参数; - 方法返回值的第一个是响应,并且必须是指针,
第二个是 error,
并且只有这两个返回值。
这种限制主要就是为了简化微服务框架的代码,在真实生产里面,你可以保持这个限制,也可以考虑去掉。