写点什么

Flutter IM 跨端架构设计和实现

用户头像
OpenIM
关注
发布于: 11 小时前

本文由阿里闲鱼技术团队祈晴分享,原文参考微信公众号淘系技术,感谢作者的技术分享。


OpenIMgithub 开源地址:


https://github.com/OpenIMSDK/Open-IM-Server


OpenIM 官网 :https://www.rentsoft.cn


OpenIM 官方论坛:**https://forum.rentsoft.cn/


现状


闲鱼 IM 框架构建于 2016-2017 年,期间多次迭代升级导致历史包袱累积多,后经 IM 界面 Flutter 化,造成架构更复杂。


开发层面总结闲鱼当前架构主要存在如下几个问题:


  1. 研发效率较低:当前架构开发需求涉及到 Android/iOS 双端的逻辑代码以及 Flutter 的 UI 界面代码,定位问题往往只能从 Flutter UI 表象追查到 Native 逻辑漏洞;

  2. 架构层次较差:架构设计上分层不清晰,业务逻辑夹杂在核心的逻辑层致使代码变更风险大;

  3. 性能测试略差:核心数据源存储 Native 内存,需经 Flutter Plugin 将数据源序列化上抛 Flutter 侧,在大批量数据源情况下性能表现较差;


从舆情层面总结闲鱼 IM 当前架构的主要问题如下:


  1. 定位问题困难:线上舆情反馈千奇百怪,测试始终无法复现相关场景,因此很多时候只能靠现象猜测本质;

  2. 疑难杂症较多:架构不稳定性造成出现的问题反复出现,当前疑难杂症主要包括未读红点计数,iPhone5C 低端机器架构,以及多媒体发送等多个问题;

  3. 问题差异性大:Android 和 iOS 两端逻辑代码差异大,包括现存埋点逻辑都不尽相同,因此排查问题根源时候双端都会有不同问题根因,解决问题方案也不相同;


业界跨端方案


为解决当前 IM 痛点,闲鱼今年特起关于 IM 架构升级项目,重在解决客户端中双端一致性痛点,初步设想方案就是实现跨端统一的 Android/iOS 逻辑架构;


在当前行业内跨端方案可初步归类如下图架构,在 GUI 层面的跨端方案有 Weex,ReactNative,H5,Uni-APP 等,其内存模型大多需要通过桥接到 Native 模式存储;


在逻辑层面的跨端方案大致有 C/C++等与虚拟机无关语言实现跨端,当然汇编语言也可行;


此外有两个独立于上述体系之外的架构就是 Flutter 和 KMM(谷歌基于 Kotlin 实现类似 Flutter 架构),其中 Flutter 运行特定 DartVM,将内存数据挂载其自身的 isolate 中;



考虑闲鱼是 Flutter 的前沿探索者,方案上优先使用 Flutter;


然而 Flutter 的 isolate 更像一个进程的概念(底层实现非使用进程模式),相比 Android,同一进程场景中,Android 的 Dalvik 虚拟机多个线程运行共享一个内存 Heap,而 DartVM 的 Isolate 运行隔离各自的 Heap,因而 isolate 之间通讯方式比较繁琐(需经过序列化反序列化过程);


整个模型如下图所示:



若按官方混合架构实现 Flutter 应用,开启多个 FlutterAcitivty/FlutterController,底层会生成多个 Engine,对应会存在多个 isolate,而 isolate 通讯类似于进程通讯(类似 socket 或 AIDL),这里借鉴闲鱼 FlutterBoost 的设计理念,FlutterIM 架构将多个页面的 Engine 共享,则内存模型就天然支持共享读取,


原理图如下:



Flutter IM 架构设计

新老架构对比

如下图是一个老架构方案,其核心问题主要集中于 Native 逻辑抽象差,其中逻辑层面还设计到多线程并发使得问题倍增,Android/iOS/Flutter 交互繁杂,开发维护成本高,核心层耦合较为严重,无插拔式概念;



考虑到历史架构的问题,演进如下新架构设计:



架构从上至下依次为业务层,分发层,逻辑层以及数据源层,数据源层来源于推送或网络请求,其封装于 Native 层,通过 Flutter 插件将消息协议数据上抛到 Flutter 侧的核心逻辑层,处理完成后变成 Flutter DB 的 Enitity 实体,实体中挂载一些消息协议实体;


核心逻辑层将繁杂数据扁平化打包挂载到分发层中的会话内存模型数据或消息内存模型数据,最后通过观察者模式的订阅分发到业务逻辑中;


Flutter IM 重点集中改造逻辑层和分发层,将 IM 核心逻辑和业务层面数据模型进行封装隔离,核心逻辑层和数据库交互后将数据封装到分发层的 moduleData 中,通过订阅方式分发到业务层数据模型中;


此外在 IM 模型中 DB 也是重点依赖的,个人对 DB 数据库管理进行全面封装解,实现一种轻量级,性能佳的 Flutter DB 管理框架;

DB 存储模型

Flutter IM 架构的 DB 存储依赖数据库插件,目前主流插件是 Sqflite,其存储模型如下:



依据上图 Sqflite 插件的 DB 存储模型会有 2 个等待队列,一个是 Flutter 层同步执行队列,一个是 Native 层的线程执行队列,其 Android 实现机制是 HandlerThread,因此 Query/Save 读写在会同一线程队列中,导致响应速度慢,容易造成 DB SQL 堆积,此外缺失缓存模型,于是个人定制如下改进方案:



Flutter 侧通过表的主键设计查询时候会优先从 Entity Cache 层去获取,若缓存不存在,则通过 Sqflite 插件查询,同时改造 Sqflite 插件成支持 sync/Async 同步异步两种方式操作,对应到 Native 侧也会有同步线程队列和异步线程队列,保证数据吞吐率;


但是这里建议查询使用异步,存储使用同步更稳妥,主要怕出现多个相同的数据元 model 同一时间进入异步线程池中,存储先后顺序无法有效的保证;


ORM 数据库方案


IM 架构重度依赖 DB 数据库,而当前业界还没有一个完备的数据库 ORM 管理方案,参考了 Android 的 OrmLite/GreenDao,个人自行设计一套 Flutter ORM 数据库管理方案,其核心思想如下:



由于 Flutter 不支持反射,因此无法直接像 Android 的开源数据库方式操作,但可通过 APT 方式,将 Entity 和 Orm Entity 绑定于一身,操作 OrmEntity 即操作 Entity,整个代码风格设计也和 OrmLite 极其相似,参考代码如下:


IM 内存数据模型

FlutterIM 架构在内存数据模型主要划分为会话和消息两个颗粒度,会话内存数据模型交托于 SessionModuleData,消息内存数据模型交托于 MessageModuleData;


会话内存数据有一个根节点 RootNotice,然后其挂载 PSessionMessageNotice(这里 PSessionMessageNotice 是 ORM 映射的会话 DB 表模型)子节点集合;


消息内存数据会有一个 MessageConatiner 容器管理,其内部挂载此会话中的 PMessage(PMessage 是 ORM 映射的消息 DB 表模型)消息集合。


依据上一章节,PSessionMessageNotice 设计了一个 OrmEnitity Cache,考虑到 IM 中会话数是有限的,因此 PSessionMessageNotice 都是直接缓存到 Cache 中,这种做法的好处是各地去拿会话数据元时候都是缓存中同一个对象,容易保证多次重复读写的数据一致性;


而 PSessionMessageNotice 考虑到其数量可以无限多的特殊性,因此这里将其挂载到 MessageContainer 的内存管理中,在退出会话的时机会校验容器中 PMessage 集合的数量,适当缩容可以减少内存开销,模型如下图所示:


状态管理方案

Flutter IM 状态管理方案比较简单,对数据源 Session/Message 维度使用观察者模式的订阅分发方式实现,架构类似于 EventBus 模式,页面级的状态管理无论使用 fish-redux,scopeModel 或者 provider 几乎影响面不大,核心还是需保留一种插拔式抽象更重要;架构如下图:


IM 同步模型方案

如下是当前现状的消息同步模型,模型中存在 ACCS Thread/Main Thread/Region Thread 等多线程并发场景,导致易出现多线程高并发的问题;


native 的推送和网络请求同步的隔离方案通过 Lock 的锁机制,并且通过队列降频等方式处理,流程繁琐且易出错。


整体通过 Region Version Gap 去判断是否有域空洞,进而执行域同步补充数据。



改进的同步模型如下,在 Flutter 侧天然没多线程场景,通过一种标记位的转化同步异步实现类似 Handler 消息队列,架构清晰简约了很多,避免锁带来的开销以及同步问题。



进展以及性能对比


▐ 针对架构层面


在 FlutterIM 架构中,重点将双端逻辑差异性统一成同一份 Dart 代码,完全磨平 Android/iOS 的代码差异性带来的问题,降低开发维护,测试回归,视觉验收的一半成本,极大提高研发效率;


架构上进行重构分层,实现一种解耦合,插拔式的 IM 架构;同时 Native 到 Flutter 侧的大量数据上抛序列化过程改造成 Flutter 引用传递,解决极限测试场景下的私聊卡顿问题;


▐ 针对线上舆情


补齐 UT 和 TLog 的集团日志方式做到可追踪,可排查;另外针对于很多现存的疑难杂症重点集中专项解决,比如 iphone5C 的架构在 Flutter 侧统一规划,未读红点计数等问题也在架构模型升级中修复,此外多媒体音视频发送模块进行改造升级;

▐ 性能数据对比

当 IM 架构的逻辑层和 UI 层都切换成 Flutter 后,和原先架构模式初步对比,整体内存水位持平,其中私聊场景下小米 9 测试结果内存下降 40M,功耗降低 4mah,CPU 降低 1%;


极限测试场景下新架构内存数据相比于旧架构有一个较为明显的改观,主要由于两个界面都使用 Flutter 场景下,页面切换的开销降低很多;


展望


JS 跨端不安全,C++跨端成本有点高,Flutter 会是一个较好选择;彼时闲鱼 FlutterIM 架构升级根本目的从来不是因 Flutter 而 Flutter,是由于历史包袱的繁重,代码层面的维护成本高,新业务的扩展性差,人力配比不协调以及疑难杂症的舆情持续反馈等等因素造成我们不得不去探索新方案。


经过闲鱼 IM 超复杂业务场景验证 Flutter 模式的逻辑跨端可行性,闲鱼在 Flutter 路上会一直保持前沿探索,最后能反馈到生态圈;总结一句话,探索过程在于你勇于迈出第一步,后面才会不断惊喜发现。


更多原创技术文章:


开源 OpenIM:高性能、可伸缩、易扩展的即时通讯架构

https://forum.rentsoft.cn/thread/3


【OpenIM 原创】简单轻松入门 一文讲解 WebRTC 实现 1 对 1 音视频通信原理https://forum.rentsoft.cn/thread/4


【OpenIM 原创】开源 OpenIM:轻量、高效、实时、可靠、低成本的消息模型


https://forum.rentsoft.cn/thread/1


OpenIM 服务发现和负载均衡 golang 插件:gRPC 接入 etcdv3


https://forum.rentsoft.cn/thread/2


【OpenIM 原创】简单轻松入门 一文讲解 WebRTC 实现 1 对 1 音视频通信原理


https://forum.rentsoft.cn/thread/4


【OpenIM 原创】C/C++调用 golang 函数,golang 回调 C/C++函数


https://forum.rentsoft.cn/thread/36

用户头像

OpenIM

关注

还未添加个人签名 2021.08.30 加入

还未添加个人简介

评论

发布
暂无评论
Flutter IM跨端架构设计和实现