深入解读TuGraph计算引擎模型推理系统

作者:李文凯

TuGraph计算引擎模型推理系统将基于迭代计算的图计算框架与模型推理系统相结合,推理系统可自定义推理依赖环境,图迭代计算与推理链路实现隔离。基于共享内存的跨进程通信方式,提高了推理数据交换效率,满足流图近线推理的时效性。在蚂蚁集团内部的实际应用场景中,大幅缩短了模型推理上线的链路与开发时间,用户迭代模型版本更方便快捷。

1. 图算法概述

在计算机科学中,图是一种表示实体(节点或顶点)以及实体之间关系(边)的数据结构。图模型可以天然地描述网络结构,能更清晰地表达复杂的数据关系和依赖,简化关联数据的理解和分析。在不同的场景下,图中点边具备不同的语义信息。比如在资金交易场景下,每个人可以抽象成一个点表示,人与人之间的转账关系可以抽象成一条边表示。如下图,通过图数据模型反映出各个实体之间的资金往来关系,让数据的关联分析更加直观和高效。

资金交易图谱示例

在图数据模型上可以执行多种图算法,如社区检测,最短路径匹配,环路检测算法等。通过点边上的迭代计算,探索图模型中各个实体之间的关系。探索过程不依赖于数据的线性结构,从而便于识别隐藏的模式和关联关系。在主流迭代图算法中,节点通过消息传递的方式进行通信。每次迭代,节点可以接收来自它们邻居的消息,处理这些消息,然后决定是否发送新的消息给其他节点。迭代算法中,每个节点有一个状态,每次迭代它们都有可能更新这个状态直至收敛。例如,在PageRank算法中,每个节点的状态是其PageRank值,这个值在迭代过程中会随着邻居的值的更新而更新。

图迭代算法解决了经典的图计算问题,但随着业务需求的复杂度提升,基于迭代的图算法存在着表达能力不足、自适应性能力差、异质图处理难度大等缺点。近年来随着深度学习的研究和应用的发展,以图神经网络(Graph Neural Networks,GNNs)为代表的一类神经网络算法,被设计用来捕获图中实体(节点)和关系(边)间的复杂模式。图神经网络能够结合节点特征和图的结构来学习节点和边的表示,相比之下,传统的迭代图算法通常不会直接从原始特征中学习,而更多地专注于结构特征。依赖于深度学习的天然优势,GNNs具有更强的表示学习能力,可以自动从数据中学习复杂的模式,这使得 GNNs 能够更好地处理多任务学习和迁移学习等问题。在社交网络分析、知识图谱、生物分子网络、推荐系统以及交通网络等领域,得到广泛应用。

GNN算法示意

2. 流图推理简介

TuGraph计算引擎(TuGraph Analytics)是蚂蚁集团开源的大规模分布式实时图计算引擎(流图引擎),实现了流批一体的图计算模型,支持了丰富的图计算算法。TuGraph Analytics的流图计算能力,能处理连续输入的数据流,并支持增量的计算模式,极大得提高了数据的计算效率和实时性。TuGraph Analytics解决了业界大规模数据关联分析的实时计算问题,已广泛应用于数仓加速、金融风控、知识图谱以及社交推荐等场景。

随着业务场景中问题复杂度的提升,基于传统的迭代图算法已无法满足业务的实际需求。例如在反洗钱场景中,利用图神经网络算法处理复杂的交易关系,能够捕获到节点的局部图结构信息。通过聚合邻接节点的特征信息,每个交易节点都可以感知到周边图网络结构的信息。类似的图神经网络等AI模型的推理逻辑,是无法基于传统的图迭代计算模式直接高效地表达的。

受上述问题启发,我们思考是否可以将TuGraph Analytics的流图计算能力与图神经网络等深度学习模型相结合,开发一套基于流图计算的模型推理系统。最终期望的推理系统具备如下能力:

  • 对于图算法工程师,在图迭代计算过程中,能够方便地使用机器学习模型的推理能力。
  • 对于AI算法工程师,可以通过TuGraph Analytics分布式流式计算的能力实现实时的模型推理。

众所周知,在深度学习为代表的数据科学领域,Python已经成为数据分析、模型训练和推理框架的主流开发语言,并提供了丰富的开发库和框架生态。而以Hadoop全家桶为代表的大数据计算引擎领域,基于Java语言开发的系统仍占据一席之地,当然TuGraph Analytics也在其中。这种语言差异带来的“互操作性”成本,使得相当一部分大数据和AI生态组件无法轻松地融合,这也是TuGraph Analytics支持图推理需要亟待解决的问题。

Java与Python的生态融合

3. 系统设计

我们对业内的跨Python & Java语言的方案进行了充分的调研,通过深入对比现有的跨语言交互方案的性能与效率,最终决定将模型推理任务运行于Python原生环境中以发挥出最佳的性能。

  1. OONX

OONX是一个开发的生态系统,为不同的机器学习框架之间提供一个标准的模型表示格式。它使得开发人员能够在不同的框架、工具、运行时环境之间以一种标准方式交换模型,从而简化了模型的迁移和部署。

优点缺点
框架互操作性转换成本高
生态系统支持更新滞后
优化推理版本兼容性
规范化模型格式性能不一致
  1. Jython

以Jython为代表的方式,主要思想是在运行的宿主虚拟机上,使用宿主语言重新编写实现。

优点缺点
Java集成版本管理复杂
跨平台支持库有限
线程更新滞后
-支持Python3有限
  1. Py4j

Py4j桥接库为代表的方式,以Socket通信模型为基础,实现Python和Java互相访问对象,方法,提供两个程序相互通信的能力。

优点缺点
跨语言交互网络传输
动态代理部署分发难度大
支持复杂类型版本难兼容
API使用简易运行时环境依赖复杂
  1. Web服务化

Web服务化是一种将机器学习模型部署成网络服务,调用者通过相应的api获取模型推理结果。

优点缺点
扩展性好性能差
简易且轻量不适合计算密集型场景
社区支持无状态管理
机器学习类库易集成并发连接有限

在TuGraph Analytics模型推理系统的架构设计中,核心部分是通过C++原生语言建立起来的一座桥梁,实现Python环境和Java虚拟机之间高效的数据交互和操作指令的传递。通过使用C++作为媒介语言,我们不仅能够利用其接近硬件的执行效率,确保数据交互的性能,还能够保证在两个虚拟环境之间数据交换的计算精度和稳定性。基于共享内存的设计允许Python和JVM进程各自独立运行,既保证了运行环境的安全隔离,又能实现数据的高效共享。

TuGraph Analytics模型推理系统设计

4. 技术原理

TuGraph Analytics模型推理系统工作流中,Driver端(即控制节点)发挥着至关重要的角色。该节点运行在Java虚拟机进程,是整个推理流程的控制中心。Driver端初始化了一个非常关键的组件——InferenceContext对象,InferenceContext对象被设计为模型推理流程的核心,在JVM环境中创建并负责加载和预处理用户提供的模型文件和环境依赖信息。在模型推理任务之前,InferenceContext会详细检查并准备好模型文件,确保能够正确加载到预期的执行环境中。InferenceContext也负责初始化和配置与模型推理相关的虚拟环境,确保正确的Python环境或其他必要的运行时库得以安装和配置。

如图所示,由流式数据源源不断的触发图迭代计算与模型推理工作。TuGraph计算引擎提供了DeltaGraphCompute计算接口,用户可自主定义增量图数据的处理逻辑,并更新历史的图存储(Graph Store)。通过TuGraph计算引擎模型推理系统,增量图迭代的中间计算结果,经过推理前置数据处理接口,并基于共享内存的跨进程通信方式,将处理后的数据流输入到推理进程,完成推理工作后的结果参与后续图迭代计算逻辑。下文将详细介绍各个数据接口的使用。

流图推理工作流程

4.1 计算推理隔离

在Tugraph Analytics模型推理系统的架构中,集群的工作负载分配给多个worker节点。每个worker节点上运行着两个关键进程:负责图数据迭代计算的Java进程,以及执行模型推理的Python进程。为了充分利用计算系统的资源,推理进程在没有接收到推理请求时,会进入睡眠状态。这样的设计不仅减少了系统资源的占用,而且降低了系统的整体能耗。当推理请求到达时,推理进程会被立即唤醒,接收和执行新的推理任务。借助睡眠与唤醒机制以及智能的任务调度策略,可以保证系统能够以高效、稳定、节能的方式运行,同时满足了大规模图数据处理和实时推理的需求。

在每个worker工作节点下,按照不同的推理作业级别划分基础的虚拟环境,从而保证一个wroker节点也可以支持不同推理任务,支持标准的requirements.txt管理推理依赖库。

单机虚拟环境

在图迭代计算进程和推理进程之间通过数据队列实现双边数据的交互,通过在数据包的头文件中插入参数个数,长度等信息,推理进程在连续若干次收到空消息包后,将自动进入睡眠状态,释放cpu等资源。图迭代计算进程调用推理接口时,推理进程将快速退出睡眠状态,接收输入数据并完成推理流程。

跨进程数据交互格式

4.2 跨进程数据交换

对于推理数据的交换部分,底层通过C++开发共享内存管理模块,实现两个进程之间的数据交互。在推理初始化阶段,由InferenceContext对象开辟进程共享内存,Java进程负责创建并初始化推理(Python)进程,通知推理进程共享内存的地址信息,并映射到相应的进程。如图,Java进程和推理进程均采用C++作为桥梁语言,实现共享内存中数据的流动操作。

跨进程数据通信架构

在推理系统的性能测试阶段,我们发现推理进程读写进程时,接口的调用开销不容忽视。常规的理解认为C++能够优化Python的执行效率,但前提是Python的执行内存足够复杂,优化执行内容的收益远大于接口的调用开销。然而,在我们系统设计中,共享内存的读写接口只是操作了内存地址,实现读写指针的移动。因此,接口的调用开销也是影响推理性能的关键因素,为此,我们充分调研了业界主流的方案。

解决方案描述
CPythonC语言实现Python及其解释器(JIT编译器)
ctypesPython标准库
SWIG开发工具,封装native程序接口
Pybind11/Boost.python/Nanobind轻量级头文件库
CythonPython的超集,使用C语言特性,静态编译

如图所示,我们对比了多种Python调用C链接库的方案,性能是第一要素,因此选择Cython作为推理进程和底层内存交互的工具。Cython是一个编程语言,是Python语言的一个超集,它将/C++的静态类型系统融合在了Python中,允许开发者可以在Python代码中直接使用C语言的特性,从而提高程序的执行效率。Cython将Python源代码翻译为C或C++代码,然后将其编译为二进制代码,能够显著提高数值计算和循环场景的代码执行性能。

Python调用C链接库方案性能对比

4.3 推理接口设计

上文介绍了采用Cython作为推理进程内存管理的链接工具,如下为TuGraph Analytics模型推理系统的内存管理接口设计,提供了初始化,读和写三个接口。

  1. 初始化接口:负责共享内存地址的映射和读指针的初始化。
  2. 读接口:数据bytes的长度作为输入参数,直接在内存端上移动相应长度返回数据段,并移动到读指针。
  3. 写接口:将bytes和bytes长度写入到共享内存,并移动至写指针。
@cython.final
cdef class InferIpc:

    cdef MmapIPC * ipc_bridge;
    cdef uint8_t* read_ptr;

    def __cinit__(self, r, w):
        self.ipc_bridge = new MmapIPC(r, w)
        self.read_ptr = self.ipc_bridge.getReadBufferPtr()

    cpdef inline bytes readBytes(self, bytesSize):
        if bytesSize == 0:
            return b""
        cdef int readSize
        cdef int len_ = bytesSize
        with nogil:
            readSize = self.ipc_bridge.readBytes(len_)
        if readSize == 0:
            return b""
        cdef unsigned char * binary_data = self.read_ptr
        return binary_data[:len_]

    cpdef inline bool writeBytes(self, bytesBuf, length):
        cdef bool writeFlag
        cdef int len_ = length
        cdef char* buf_ = bytesBuf
        with nogil:
            writeFlag = self.ipc_bridge.writeBytes(buf_, len_)
        return writeFlag

    def __dealloc__(self):
        del self.ipc_bridge

如下为用户实现推理的Java接口,同其它图迭代计算接口一样,需要推理的时候直接调用该接口,将图迭代的中间结果inputs发送到推理进程并返回模型结果。

public interface GraphInferContext<OUT> extends Closeable {
    OUT infer(Object... inputs);
}

5. 最佳实践

我们以PageRank任务结合群组打分模型推理流程为例,演示具体的操作流程。

5.1 数据处理

定义推理数据前后置处理逻辑如下:

import abc
import json
import sys
import os
import torch

class MyInference(TransFormFunction):
    def __init__(self):
        super().__init__(2)

    def transform_pre(self, *args):
        return args[0], args[1]

    def transform_post(self, *args):
        return args[0]

5.2 图迭代推理

定义图迭代计算结合推理逻辑如下:

    public static class PRVertexCentricComputeFunction implements
        IncVertexCentricComputeFunction<Integer, Integer, Integer, Integer> {

        private IncGraphComputeContext<Integer, Integer, Integer, Integer> graphContext;
        private IncGraphInferContext<String> inferContext;

        @Override
        public void init(IncGraphComputeContext<Integer, Integer, Integer, Integer> graphContext) {
            this.graphContext = graphContext;
            this.inferContext = (IncGraphInferContext<String>) graphContext;
        }

        @Override
        public void evolve(Integer vertexId,
                           TemporaryGraph<Integer, Integer, Integer> temporaryGraph) {
            long lastVersionId = 0L;
            IVertex<Integer, Integer> vertex = temporaryGraph.getVertex();
            HistoricalGraph<Integer, Integer, Integer> historicalGraph = graphContext
                .getHistoricalGraph();
            if (vertex == null) {
                vertex = historicalGraph.getSnapShot(lastVersionId).vertex().get();
            }

            if (vertex != null) {
                List<IEdge<Integer, Integer>> newEs = temporaryGraph.getEdges();
                List<IEdge<Integer, Integer>> oldEs = historicalGraph.getSnapShot(lastVersionId)
                    .edges().getOutEdges();
                if (newEs != null) {
                    for (IEdge<Integer, Integer> edge : newEs) {
                        graphContext.sendMessage(edge.getTargetId(), vertexId);
                    }
                }
                if (oldEs != null) {
                    for (IEdge<Integer, Integer> edge : oldEs) {
                        graphContext.sendMessage(edge.getTargetId(), vertexId);
                    }
                }
            }

        }

        @Override
        public void compute(Integer vertexId, Iterator<Integer> messageIterator) {
            int max = 0;
            while (messageIterator.hasNext()) {
                int value = messageIterator.next();
                max = max > value ? max : value;
            }
            IVertex<Integer, Integer> vertex = graphContext.getTemporaryGraph().getVertex();
            IVertex<Integer, Integer> historyVertex = graphContext.getHistoricalGraph().getSnapShot(0).vertex().get();
            if (vertex != null && max < vertex.getValue()) {
                max = vertex.getValue();
            }
            if (historyVertex != null && max < historyVertex.getValue()) {
                max = historyVertex.getValue();
            }
            graphContext.getTemporaryGraph().updateVertexValue(max);
        }

        @Override
        public void finish(Integer vertexId, MutableGraph<Integer, Integer, Integer> mutableGraph) {
            IVertex<Integer, Integer> vertex = graphContext.getTemporaryGraph().getVertex();
            List<IEdge<Integer, Integer>> edges = graphContext.getTemporaryGraph().getEdges();
            if (vertex != null) {
                mutableGraph.addVertex(0, vertex);
                graphContext.collect(vertex);
            } else {
                LOGGER.info("not found vertex {} in temporaryGraph ", vertexId);
            }
            if (edges != null) {
                edges.stream().forEach(edge -> {
                    mutableGraph.addEdge(0, edge);
                });
            }
            List<String> inferInput = new ArrayList<>();
            inferInput.add(String.valueOf(vertexId));
            inferInput.add("param2");
            String infer = this.inferContext.infer(inferInput.toArray(new Object[]{0}));
        }
    }

5.3 创建作业

在Console作业管理平台创建一个HLA任务,上传图迭代计算jar包,模型文件和依赖管理文件。

创建图推理作业

5.4 配置参数

配置相关参数,启动运行作业即可。

"geaflow.infer.env.enable":"true",
// 初始化虚拟环境等待时间
"geaflow.infer.env.init.timeout.sec":120,
// 是否接收日志
"geaflow.infer.env.suppress.log.enable":"true"

6. 总结

通过将AI模型推理引入TuGraph Analytics流图计算系统,让我们能够对图数据进行深度地分析和预测。利用最新的机器学习和深度学习技术,TuGraph Analytics图计算引擎不仅可以对图数据进行分类和回归分析,还可以预测未来趋势,从而在多个维度上提供决策支持。

希望通过以上的介绍,可以让大家对TuGraph Analytics模型推理系统有个比较清晰的了解,非常欢迎大家加入我们社区(https://github.com/TuGraph-family/tugraph-analytics),一起构建图数据上的智能化分析能力!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/637160.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

VUE3好看的酒网站模板源码

文章目录 1.设计来源1.1 首页界面1.2 十大名酒界面1.3 名酒新闻界面1.4 联系我们界面1.5 在线留言界面 2.效果和结构2.1 动态效果2.2 代码结构 3.VUE框架系列源码4.源码下载 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/detai…

2024/5/22 学习杂记

为什么功率放大电路在模电中经常提到&#xff1f; 模拟信号&#xff1a;它是连续变化的电信号&#xff0c;它在时间上和幅度上都是连续的&#xff0c;能够代表信息的连续变化。大多数物理量为模拟信号&#xff0c;如&#xff1a;温度、压力、流量… 非电物理量通过传感器变换成…

Nginx - 健康检查终极指南:探索Upstream Check模块

文章目录 概述upstream_check_module模块安装和配置指南模块安装步骤基本配置示例详细配置说明检查类型和参数常见问题及解决方案 SSL检查和DNS解析功能SSL检查配置示例和说明配置示例 DNS解析配置示例和说明配置示例 结合实际应用场景的高级配置示例综合SSL检查与DNS解析 总结…

Discourse 中可能使用的 HMAC 算法 Java 实现

在 DiscourseConnect 中&#xff0c;对数据的签名使用的是 HMAC 算法。 实际使用的算法为 HmacSHA256。 Java 生成签名的方法很简单。 String hmac new HmacUtils(HmacAlgorithms.HMAC_SHA_256, "55619458534897682511405307018226").hmacHex(ssoPayload);HmacUti…

robosuite导入自定义机器人

目录 目的&#xff1a;案例一&#xff1a;成果展示具体步骤&#xff1a;URDF文件准备xml文件生成xml修改机器人构建 目的&#xff1a; 实现其他标准/非标准机器人的构建 案例一&#xff1a; 成果展示 添加机器人JAKA ZU 7 这个模型 具体步骤&#xff1a; URDF文件准备 从…

在深度学习中常见的初始化操作

目录 截断正态分布来初始化张量 逐行代码解释 相关理论解释 截断正态分布函数 截断正态分布的定义 截断正态分布的作用 计算截断点的作用 具体步骤 正态分布的累积分布函数&#xff08;CDF&#xff09; 正态分布的累积分布函数与误差函数的关系 示例计算 误差函数 应…

切换分支报错:Untracked Files Prevent Checkout

切换分支报错&#xff1a;Untracked Files Prevent Checkout 分支切换 Untracked Files Prevent Checkout 新起的项目在切换master分支到工作分支时&#xff0c;出现下图的问题&#xff1a; Untracked Files Prevent Checkout Move or commit them before checkout 网上的解决…

pip(包管理器) for Python

pip是什么 pip是Python的包安装程序&#xff0c;即python包管理器。您可以使用 pip 从Python包索引和其他索引安装包。 1. pip 安装 python 包 pip install 包名 例如&#xff1a;pip install pymssql &#xff1a; 使用pip安装数据库驱动包 pymssql 2.pip 卸载 python 包 pi…

数据结构2(初):顺序表和链表

目录 1、线性表 2、顺序表 2.1、概念及结构 2.2、顺序表的实现 2.3、顺序表的问题及思考 3、链表 3.1、链表的概念及结构 3.2、链表的分类 3.3、无头单向非循环链表的实现 3.4、带头双向循环链表的实现 4、顺序表和链表的区别和联系 1、线性表 线性表是n个具有相同特…

200+有趣的HTML前端游戏项目合集(5月17日更新,持续更新中)

&#x1f482; 个人网站:【 摸鱼游戏】【神级代码资源网站】【工具大全】&#x1f91f; 一站式轻松构建小程序、Web网站、移动应用&#xff1a;&#x1f449;注册地址&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交…

工作干到抑郁了,要不要辞职?

在知乎上看到以为网友提问&#xff1a;工作干到抑郁&#xff0c;该不该辞职&#xff1f; 今天和大家聊聊这个话题&#xff0c;如果你也有类似的情况&#xff0c;希望这篇文章能帮到你。 熟悉瑶琴的朋友&#xff0c;都知道瑶琴在去年有一次裸辞的经历。离职前&#xff0c;严重的…

多台Centos快速区分,让Centos开机自动显示它的IP地址!

背景说明&#xff1a;当公司拥有多台Centos服务器&#xff0c;管理员很容易弄混淆导致不好区分&#xff0c;在这样的情况下我们可以写个简单脚本来实现开机自动显示它的IP地址&#xff0c;从而达到区分开来的结果&#xff01; 首先我们来开下效果&#xff0c;登录之前的 下面是…

【加密与解密(第四版)】第十八章笔记

第十八章 反跟踪技术 18.1 由BeginDebugged引发的蝴蝶效应 IsDebuggerPresent()函数读取当前进程PEB中的BeginDebugged标志 CheckRemoteDebuggerPresent() 反调试总结&#xff1a;https://bbs.kanxue.com/thread-225740.htm https://www.freebuf.com/articles/others-articl…

细胞冻存——让你的细胞“长生不老”

《星际穿越》电影中提到漫长的太空旅程中&#xff0c;宇航员可以进入休眠水床休眠&#xff0c;并自行设定唤醒时间。在《异形》《深空失忆》《三体》等科幻作品中&#xff0c;都出现此类技术。《三体》中&#xff0c;休眠后来成为人类最普遍的一项技术。技术上的人类低温休眠&a…

JavaEE-网络初识

文章目录 一、网络背景1.1 起源1.2 国内网络的发展 二、关键概念2.1 网络2.2 设备2.3 ip地址与端口号 三、协议3.1 协议分层3.2 OSI七层模型3.3 TCP/IP五层模型3.4 数据传输过程的简单叙述 一、网络背景 1.1 起源 在国外大概时上世纪70年代左右&#xff0c;网络就出现了&…

项目集成SkyWalking,基于k8s搭建

一、搭建SkyWalking 官方文档&#xff08;英文&#xff09;&#xff1a;skywalking/docs at master apache/skywalking 中文可以使用&#xff1a;GitHub - SkyAPM/document-cn-translation-of-skywalking: [已过期,请使用官网AI文档] The CN translation version of Apache…

【LeetCode:496. 下一个更大元素 I + 单调栈】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

深度学习——图像分类(CNN)—训练模型

训练模型 1.导入必要的库2.定义超参数3.读取训练和测试标签CSV文件4.确保标签是字符串类型5.显示两个数据框的前几行以了解它们的结构6.定义图像处理参数7.创建图像数据生成器8.设置目录路径9.创建训练和验证数据生成器10.构建模型11.编译模型12.训练模型并收集历史13.绘制损失…

【AD21】PCB板尺寸与层名称标注

PCB绘制完成后&#xff0c;需要给上级或生产制造商发送输出文件&#xff0c;输出文件中包含板尺寸标识和层标识可以方便工作的交接。 1. 板尺寸标识 首先板尺寸标识所在的层要在与板框不同的机械层&#xff0c;这里我选择机械5层。 点击放置->尺寸->线性尺寸 这里板尺…

微信小程序uniapp+django洗脚按摩足浴城消费系统springboot

原生wxml开发对Node、预编译器、webpack支持不好&#xff0c;影响开发效率和工程构建。所以都会用uniapp框架开发 前后端分离&#xff0c;后端给接口和API文档&#xff0c;注重前端,接近原生系统 使用Navicat或者其它工具&#xff0c;在mysql中创建对应名称的数据库&#xff0…