客户端跨平台交互框架-Starbridge

 

方案定义

本框架为后续跨平台以及容器化方向开发提供基础运行框架,建设基础核心组件,规定基础交互协议等方面的规范。

设计目标

本框架设计的主要目标为以下两点:

  1. 实现容器化后不同层级不同组件之间的跨语言功能调用;
  2. 构建容器组件的虚拟运行平台。

边界

跨语言交互框架范围为PB支持范围语言FFI支持的交集。 容器组件为业务层容器组件。 本框架设计暂不包含跨进程消息传递。

架构设计

XBridge.drawio

架构解释

整个技术架构为星型拓扑(Star Topology),整体结构功能如下:

  1. 中央节点为星桥(Star Bridge),功能为多语言消息互传,通过跨语言FFI来实现。一般星型拓扑的瓶颈在于中央节点的负担过重,因此星桥内虽然具有不同平台的消息路由功能,但不包括路由表本身,只负责将二进制消息传递给对应平台的Mediator,传递方式为先广播给消息源之外的其他所有Mediator,如果有可以响应的Mediator则将二进制数据传递过去,如果没有可以响应的则由星桥返回一个异常消息。同时星桥内包含一个消息队列,用于处理跨平台跨线程消息的消息传递,由于客户度负载相对较小且对实时性要求高,消费模式设置为push模式,消息传递模式则为发布订阅模式。
  2. 圆形ALPC部分则为二进制消息模型,利用PB性质生成二进制消息,每一组完整请求分为Request与Response,Request由发送端生成,Response由接受端生成。
  3. 圆角矩形为函数路由(Mediator)部分为各个平台独立实现,并且都需要接入星桥的对应FFI接口。函数路由提供同一语言内对于不同模块不同方法函数的动态调用能力,对于跨语言调用则利用星桥提供的接口进行访问。同时Mediator还需要实现当前平台的容器模块注册能力,并提供模块生命周期管理能力。
  4. 方形的是虚拟容器(Container),作用是为外部功能调用、生命周期管理、全局常量获取以及监控信息埋点等方面提供模块级的统一入口。

技术节点

总体技术选型

  1. 基于ALPC的本地调用概念模型,为每一种语言构建消息发送与消息接受入口。
  2. 同一进程相同语言之间使用Mediator模式进行调用。
  3. 同一进程内不同语言之间使用Mediator转发给FFI来完成,提高运行效率。
  4. 跨进程调用使用IPC实现,考虑到iOS的IPC实现手段有限,也可以使用Local Socket方案(暂时不做)。
  5. 数据传输的载体使用Protobuf3,利用Protobuf的跨平台构建与二进制传输的特性,将不同语言之间的数据结构差异抹平。并且每一次数据传输本质上是一次值拷贝,因此不会有内存泄漏问题。
  6. 由于Protobuf对于超过1MB的数据量,Ser/De开销以及内存开销会暴增[注1],因此大批量数据使用Protobuf作为协议载体,具体资源通过本地文件形式同步,然后通过对应平台文件IO接口进行访问。

星桥设计

StarBridge.drawio

星桥的实质为基于Rust的消息队列,通过FFI提供不同平台的输入接口来实现跨平台的消息互传,对于异步消息,则从线程池中选取空闲线程进行等待异步结果,否则直接使用主线程。异步调用时回调将以msg_id作为handle进行回调。

ALPC协议设计

ALPC通过Protobuf二进制数据进行传输,具体协议如下:

syntax = "proto3";
import "google/protobuf/any.proto";

package moqipobing;
option java_package = "com.moqipobing.ALPC";
option java_outer_classname = "ALPCMessage";

// 消息类型枚举
enum ALPCMessageType {
    // 请求消息
    REQUEST = 0;
    // 响应消息
    RESPONSE = 1;
    // 
}

// 消息体类型枚举
enum ALPCMessageBodyType {
    // 无消息体
    NOBODY = 0;
    // urlencoded类型消息体,字符串形式key1=value1&key2=value2
    URLENCODED = 1;
    // Json数据类型
    JSON = 2;
    // ProtoBuf类型
    PROTOBUF = 3;
    // Resource文件消息类型,为本地文件Path,使用Unix文件路径进行表达
    RESOURCE = 4;
    // 其他类型
    OTHER = 5;
}

// 资源消息类型枚举
enum ALPCResourceType {
    // 图片资源
    IMAGE = 0;
    // 文本资源
    TEXT = 1;
    // 视频资源
    VIDEO = 2;
    // 游戏资源包
    GAMEPACK = 3;
    // 其他资源
    OTHER = 4
}

// 资源型消息结构
message ALPCResource {
    // 资源消息类型
    ALPCResourceType type = 1;
    // 资源路径
    string resource_path = 2;
    // 是否为临时文件
    bool is_temp = 3;
}

// 基础消息协议
message ALPCMessage {
    // 消息id,由MQ分配
    int32 message_id = 1;
    // 消息类型:0 请求 / 1 响应
    ALPCMessageType message_type = 2;
    // 消息源,字符串类型
    string source = 3;
    // 目标模块,字符串类型
    string target = 4;
    // 目标功能,字符串类型
    string action = 4;
    // 请求/响应实体
    google.protobuf.Any body = 5;
}

// ALPC Request的请求实体
message ALPCRequest {
    // 消息id,由Router分配
    int32 msg_id = 1;
    // 请求的模块
    int32 request_module = 2;
    // 响应的模块
    int32 response_module = 3;
    // 响应的功能
    int32 response_func = 4;
    // 消息体类型
    BodyType body_type = 5;
    // 保留字段序号
    reserved 6, 7, 8, 10 to max;
    // 如果body_type=NOBODY,则该值不存在
    // 如果body_type=URLENCODED,则该值为字符串类型
    // 如果body_type=JSON,则该值为json object
    // 如果body_type=PROTOBUF,则该值为ProtoBuf map,具体类型需自行跟业务进行协调
    // 如果body_type=RESOURCE,则该值为ALPCResource类型的pb message
    // 如果body_type=OTHER,则该值需要自行序列化
    google.protobuf.Any body = 9;
}

// 响应实体
message ALPCResponse {
    //  消息id,由Router分配
    int32 msg_id = 1;
    // 请求的模块
    int32 request_module = 2;
    // 响应的模块
    int32 response_module = 3;
    // 响应的功能
    int32 response_func = 4;
    // 消息体类型
    BodyType body_type = 5;
    // 响应码
    // 0为成功
    // 负值为路由异常
    // 正值为能力异常
    int32 code = 6;
    // 响应消息
    // 在异常状态下可能包含报错信息
    string msg = 7;
    // 保留字段序号
    reserved 8, 10 to max;
    // 如果body_type=NOBODY,则该值不存在
    // 如果body_type=URLENCODED,则该值为字符串类型
    // 如果body_type=JSON,则该值为json object
    // 如果body_type=PROTOBUF,则该值为ProtoBuf map,具体类型需自行跟业务进行协调
    // 如果body_type=RESOURCE,则该值为ALPCResource类型的pb message
    // 如果body_type=OTHER,则该值需要自行序列化
    google.protobuf.Any body = 9;
}

Mediator设计

Mediator采用hashmap映射方案实现方法动态调用,调用入口上分为三种入口:

  1. 本地调用(指单进程内调用)采用target+action+params的方式,接口形如:

     wb::mediator
     .target("callee_target")
     .action("callee_action")
     .params(value)
     .callback(|in_params|{ //...lambda });
        
     wb::mediator
     .target("callee_target")
     .action("callee_action")
     .params(value)
     .handle(my_handle_func);
    
  2. 远程调用依旧使用URI进行调用
  3. 跨语言调用通过FFI调用星桥完成

虚拟容器设计

虚拟容器主要提供四种功能:

  1. action入口
  2. 公共参数
  3. 生命周期回调
  4. 监控探针点

action入口

action入口实质为定义一种特定类型,利用继承等特性完成类型固定,然后让mediator只在该类型范围内进行函数查询。每个module只允许有一个action入口,内部功能通过action入口进行功能开放,以保证动态函数调用边界。

公共参数

利用星桥的全局特性,可以将简单key-value类型数据存在其中,其他语言平台可通过key获取该值,小批量数据可以利用该功能共享。

生命周期回调

通过星桥进行全局生命周期统一管理,在module的指定类型中提供一系列生命周期回调函数,同时提供具有优先级的服务注册功能。

监控探针点

形态类似action入口,为module提供统一的业务监控探针类,通过函数轮询与继承特性完成消息自动上报。

调用时序

a) 同语言时序

sequenceDiagram
module a->>Mediator: call_func(target, action, callback)
Mediator->>Mediator: find_target(target)
alt 找不到target
Mediator-->>module a: return(error)
else 找到target
Mediator->>module b: exec_action(action)
alt 找到action
module b->>module b: do something...
module b->>Mediator: return(result)
Mediator->>module a: return(result)
else 找不到action
Mediator-->>module a: return(error)
end
end

b) 异语言同步时序

sequenceDiagram
module a->>Mediator Core: call_func(target, action, callback)
Mediator Core->>Star Bridge: find_target(target)
alt 找不到target
Star Bridge-->>Star Bridge: find_target_cache(target):failed
Star Bridge-->>module M: notification_find_target_cache(target):failed
Star Bridge-->>Mediator Core: return(error)
Mediator Core-->>module a: return(error)
else 找到target
Star Bridge-->>Star Bridge: find_target_cache(target):pass
Star Bridge-->>Mediator Native: call_func(target, action)
Mediator Native->>module 1: exec_action(action)
alt 找到action
module b->>module b: do something...
module b->>Mediator: return(result)
Mediator->>module a: return(result)
else 找不到action
Mediator->>module a: return(error)
end
end

c) 异语言异步时序

sequenceDiagram
module a->>Mediator Core: call_func(target, action, callback)
Mediator Core->>Star Bridge: find_target(target)
alt 找不到target
Mediator Core-->>module a: return(error)
else 找到target
Mediator Core->>Mediator Native: find_action(action)
Mediator Native->>module 1: find_action(action)
alt 找到action
module b->>module b: do something...
module b->>Mediator: return(result)
Mediator->>module a: return(result)
else 找不到action
Mediator->>module a: return(error)
end
end