4

RPC在点我达网关的实践一

 1 year ago
source link: http://muyunyun.cn/blog/exzzjh9z/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

RPC 是什么

  • RPC(Remote Procedure Call) 译为远端过程调用。即在一台机子上能调用到另外一台机子上的服务;
  • RPC 可以基于 HTTP 调用也可以基于 TCP 调用。基于 TCP 调用性能更佳, 但是实现也更为复杂;
  • RPC 通常要实现两部分协议, 一个是应用层协议(如 JSON), 一个是用来传输数据的通讯层协议(如 Dubbo);

RPC 调用过程

  1. 调用方通过本地网关调用相应服务;
  2. 网关将服务名和参数封装为 RPC 对象传给客户端 RPC 框架(@dwd/noob-client);
  3. 客户端 RPC 框架(@dwd/noob-client)将数据转化成二进制形式, 然后以 TCP 的形式传递给服务端 RPC 框架;
  4. 服务端 RPC 框架将二进制数据反序列化为 RPC 对象, 并交由服务方处理, 服务端处理后返回结果;
  5. 执行上述阶段的逆序操作;

关于 RPC 细节点, 可以阅读 聊聊 Node.js RPC(一)— 协议

结合代码分析 Java 包的调用过程

初始化阶段 —— RPC 调用之前

在项目刚启动阶段, 网关项目会通过 zk(路由管理系统) 进行路由寻址(寻找相应后端组的服务), 后续网关项目就能和相应的后端组的服务直接通讯了。

在初始化阶段, 网关项目会将项目信息、注册区域(异地多活)、所需服务等传递给 @dwd/noob-client, 相关代码如下:





import { Client } from '@dwd/noob-client'
const client = new Client({
application: { // 项目信息
name: config.name,
registry: config.registry, // 注册区域
reference: config.references, // 所需服务
routerServer: { address: routerAddress },
return client.init().then(() => {
for (const r of config.references) {
loadService(r.id)

根据 xml 文件生成相关的包文件

在项目伊始时, 会通过后端给出的 xml 文件, 根据脚本生成相应包的 ts 文件。比如以下代码为骑手服务组的一个包的 ts 文件。





import { Reference } from '@dwd/noob'
import { provideService, javaType} from '../../util/dubbo'
@provideService('com.dianwoba.rider.elastic.provider.RiderElasticProvider')
export default class RiderElasticProvider implements com.dianwoba.rider.elastic.provider.RiderElasticProvider {
constructor(private _ref: Reference<com.dianwoba.rider.elastic.provider.RiderElasticProvider> ) {}
async pageSearch(@javaType({"name":"com.dianwoba.rider.elastic.domain.dto.param.RiderEsParamDTO","isPrimitive":false,"isArray":false,"isGeneric":false}) paramDTO: com.dianwoba.rider.elastic.domain.dto.param.RiderEsParamDTO) : Promise<com.dianwoba.dubbo.base.result.Pagination<com.dianwoba.rider.elastic.domain.dto.result.RiderEsDTO>> {
return this._ref.invoke('pageSearch', Array.from(arguments))

RiderElasticProvider 的实例化

网关的核心架构使用了 IoC 框架 inverify。在 IoC 架构下, 实例化的过程在容器内进行。

在上述代码点开 provideService 方法, 可以看到实例化的过程 new ServiceClass(reference), 代码如下:





// dubbo.ts
import { Client } from '@dwd/noob-client'
export const provideService = <T>(interfaceName: string) => (ServiceClass: interfaces.Newable<T>) => {
const reference = Client.reference.get(interfaceName) // 获取远程引用资源
const service = proxyService(new ServiceClass(reference), interfaceName) // 代理实例对象, 下文解析
moduleBind<T>(interfaceName).toConstantValue(service) // 依赖注入

reference 对象的 __proto__ 属性上有 invoke 方法(继承自 @dwd/noob)

proxyService 代理

proxyService 方法的作用给包类的每个方法做了一层代理, 代理的具体作用是将传入参数和包名包装为 RPC 对象。





// dubbo.ts
function proxyService<T>(service:T, identifier: string) {
let handler = {
apply: function(target: Function, thisArgument: any, argumentsList: any[]) {
let funcName = target.name
// 获取前面代码 @javaType({}) 中声明的对象
let paramTypes: JavaType[] = Reflect.getMetadata(JAVATYPE_SYMBOL, service, funcName)
// 工厂模式创建转化为 RPC 对象的方法
let transform = converter.methodParameterTransformerFactory(...paramTypes)
// 将传入参数和包名转化为 RPC 对象
let args = transform(argumentsList)
return target.apply(thisArgument, args)
Object.getOwnPropertyNames(Object.getPrototypeOf(service)).filter(name => name !== 'constructor' && !name.startsWith('_')).forEach(methodName => {
service[methodName] = new Proxy(service[methodName], handler) // 将原型链上 service[methodName] 赋值到 service[methodName] 上, 并用 handler 进行代理
return service

ts 的语法





class Test {
constructor(private _ref) {}

ts 转换为 js 的形式如下





function Test(_ref) {
this._ref = _ref

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK