这几年我参与过不少用活字格搭起来的业务系统,从几十人的内部管理后台,到面向C端日活过万的业务平台都有。每次跟人聊起低代码,对方第一反应几乎都是同一个问题:这东西做做内部工具还行,真能扛住高并发吗?

这个质疑放在几年前,确实有道理。早期的低代码平台强在“快速出页面”,弱在“深层性能可控”,一聊到集群、扩容、数据库分片这些词,基本就露怯了。但活字格这类企业级低代码平台,走的是另一条路线:它没把自己定位成“给业务人员拖拽表单的小玩具”,而是更像一套“可视化开发框架+运行时引擎”。也就是说,你用它做出来的应用,本质上是跑在它提供的服务端引擎上的真实Web应用,而不是一堆解释型脚本的堆叠。那么问题来了:这套引擎到底能不能扛住高并发?扩容的路子通不通?这篇文章我就结合自己的实际落地经验,把活字格的性能底细和扩容设计从头到尾聊一遍。

1. 先撕开“低代码扛不住高并发”这个刻板印象

1.1 低代码被质疑高并发,到底在质疑什么

大家提到低代码和高并发,默认觉得这两件事是矛盾的,这里面其实藏着一个旧认知:很多人印象里的低代码,是“拖一堆控件,生成一堆低效代码,跑起来又慢又吃资源”。这个印象确实对应了一批表单型工具,做几个增删改查界面可以,一旦并发上来,服务端线程被拖死、SQL乱飞、页面渲染卡顿,各个瓶颈都冒出来了。

但企业级低代码平台和“表单工具”是两类东西。活字格的定位是“低代码开发平台”,它解决的不只是页面生成问题,还包含服务端业务逻辑、数据访问层、权限体系、工作流、计划任务、外部接口集成这些完整闭环。也就是说,应用跑起来之后,真正干活的是活字格的后端引擎,而不是浏览器里那几个脚本。瓶颈在哪里,取决于你如何设计应用,而不是“因为用了低代码所以一定慢”。

1.2 活字格应用的真实运行形态

活字格开发的页面,在浏览器里看到的确实是网页,但它的渲染方式是服务端渲染+前端增强。换句话讲,页面交互时,浏览器会跟活字格的服务端通信,服务端再去处理数据库请求、执行业务逻辑、返回渲染结果。这种模型的优点是首屏加载快、对前端性能要求低,缺点是服务端压力会集中一些。理解了这一点,你就明白为什么活字格官方一直强调“生产环境建议外联数据库”和“合理使用服务端命令”。

实际部署时,活字格应用和常规ASP.NET Core应用一样,跑在Windows或Linux服务器上,通过反向代理对外提供服务。单机部署时,它就是一个Web服务;要做高并发,就要走多机部署、负载均衡、数据库扩展这条路。架构上它并没有自绝于标准Web体系之外,只要按Web应用的通用套路去扩展,它是能跟上并发需求的。

2. 活字格的性能底座:核心机制逐个拆

2.1 数据访问层:连接复用和SQL执行效率决定了第一道关卡

活字格的数据访问分两条路:内置数据库(默认SQLite)和外联数据库(SQL Server、MySQL、PostgreSQL、Oracle等)。开发阶段用内置库非常方便,因为它零配置、开箱即用、适合做原型和内部小系统。但一旦进入生产环境,尤其是并发起来了,我强烈建议第一时间切到外联数据库。

为什么?SQLite虽然轻量,但它的写入锁是库级锁,并发高了以后写操作会排队,性能曲线直接“断崖式下跌”。MySQL或SQL Server这类数据库在连接管理、锁粒度、缓存机制上要成熟得多。活字格对外联库的连接管理,底层用的是连接池机制,连接会被复用而不是每次新建,这一点做得好坏直接影响高并发下的表现。实操中,外联库连接池的大小、超时时间、最大连接数这些参数,需要在数据库端和活字格侧一起配好。

关于SQL执行效率,活字格有两种模式:一种是页面上的表格/数据绑定自动生成的SQL,另一种是用服务端命令里写的SQL或调用存储过程。自动生成的SQL,胜在快,但在复杂查询场景下未必是最优的,容易出现必要的索引没走、查询条件组合不佳等问题。所以我在做高并发项目时,核心业务的高频查询都会在服务端命令里手写SQL,或者直接建视图、存储过程,把性能握在自己手里。

2.2 服务端命令是性能分水岭,用好了事半功倍

活字格的服务端命令,是它区别于普通低代码工具的关键能力之一。它可以在服务端执行业务逻辑、批量操作数据库、调用外部API、发邮件、生成文件等。页面端只负责把参数传过去、接收返回值。这个机制对高并发非常友好,因为你把大量计算和数据处理压在服务端,客户端做“轻前端”,网络传输的数据量也会小很多。

实操中,我见过很多活字格项目性能出问题,毛病都出在同一个地方:把数据处理逻辑写在了页面端,用“页面端循环+逐条调用数据表操作”的方式处理批量数据。比如页面上勾选了一千行记录,逐条去数据库更新,并发一上来,数据库连接和事务开销直接爆炸。正确做法是用服务端命令接收一个数组参数,在服务端一次性处理,循环里尽量用批量SQL或临时表方案,减少往返。

另外,服务端命令本身也有性能参数要关注。比如它的并发执行线程数、超时设置、日志级别。生产环境建议把服务端命令日志关掉或只保留错误日志,避免大量IO写日志把磁盘拖垮。

2.3 页面渲染与静态资源:服务端渲染带来的优势与代价

活字格的页面是服务端渲染的,用户请求一个页面,服务端完成数据查询和页面组装,返回给浏览器。这个机制的好处很明显:首屏数据不需要前端再发Ajax拉取,页面打开速度快,体验稳定。但坏处就是服务端CPU和内存的消耗会更高。并发上来时,每一个页面请求都要占用服务端资源去渲染,如果页面很复杂、绑定的数据表数据量又很大,服务端压力会成倍增加。

应对办法有几种。首先,页面上能不加的数据表绑定就不要加,能用查询参数尽量缩小返回行数。其次,表格尽量开启按需加载或分页,不要一次性把十万行数据塞到页面上。再次,启动活字格自带的静态资源压缩和合并功能,减少浏览器请求的数量和体积。最后,对于纯展示类的页面,尽量做成静态化的欢迎页或引导页,避免每个访客都打到后端。

我在一个实际项目里做过对比:同一个列表页,数据量为五万行,优化前直接绑定整张表,页面加载需要三四秒;改成服务端分页+查询条件收紧后,加载时间降到五百毫秒以内。这不是活字格本身的问题,而是绝大多数Web应用的通用优化逻辑:别把“全部数据”传到前端,让前端去处理,数据量一大必崩。

2.4 缓存:你有没有把高频数据放到Redis里

关于缓存,很多活字格初学者容易忽视。活字格本身有内置缓存机制,比如用户权限缓存、某些数据字典类缓存。但对于业务数据的高频读取,光靠内置缓存显然不够。比如一个商品分类下拉框、一个城市联动列表,每次页面打开都去数据库查一遍,不值当。

高并发项目里,我会把这类“读多写少”的数据主动放到Redis里。活字格的服务端命令可以调用外部API,所以它可以通过HTTP调用一个自己维护的Redis服务,或者通过中间件直接读写缓存。这个方案虽然需要写一点外部代码,但收益非常明显,数据库压力能降下来一大截。更彻底的做法是,对搜索页、首页这一类热点接口,在Nginx层做一层页面级缓存或API缓存,直接挡掉一部分流量,后端应用压力会更小。

这里要注意一个取舍问题:缓存和数据一致性的博弈。对于实时性要求高的数据(比如订单状态、库存),不能盲目缓存,容易超卖或显示脏数据。我的经验是:字典类数据放心缓存,聚合统计类数据可以短时缓存,交易核心数据一律直查数据库。

3. 扩容设计:从单机到集群,活字格应用怎么一步步长起来

3.1 第一步:搞清楚瓶颈在Web层还是数据库层

做扩容设计之前,必须先做瓶颈分析,否则扩了半天发现瓶颈根本不在那一层,白花钱也白耽误工夫。活字格应用的访问链路是:用户 -> 负载均衡/反向代理 -> 活字格Web节点 -> 数据库。这条链路的任何一环都可能成为瓶颈。

我常用的定位方法是分层看指标。Web层看CPU、内存、请求队列长度,数据库层看连接数、慢SQL数量、磁盘IO。如果Web层CPU跑满,数据库还很闲,说明瓶颈在应用节点,需要加Web服务器节点做水平扩容;如果数据库CPU高、慢SQL多,那先把SQL优化做掉,再考虑读写分离或者分库。

有一次我遇到一个案例:并发一百左右的时候,用户普遍反映系统变慢,查了一圈发现Web服务器CPU不到30%,数据库CPU也不高,但数据库连接数被打满了。原因是页面端做了大量逐条数据库操作,每个请求同时占据多个连接,连接池直接耗尽。这种情况加再多机器也没用,必须先从代码层面减少连接占用。

3.2 水平扩容Web层:活字格多机部署的关键细节

活字格支持多机部署,也就是在同一套数据库之上,跑多个活字格服务实例,通过Nginx或硬件负载均衡把请求分发到不同实例上。这样Web层就具备了水平扩容能力。新增一台服务器,把活字格应用部署好,指向同一个数据库,加入负载均衡,系统处理能力就上去了。

但这里有三个细节必须处理好。

第一,文件存储。活字格应用里如果涉及附件上传(这是极常见的需求),默认情况下文件会存在本地磁盘。单机没问题,多机部署以后,用户在A机器上传的文件,请求被分发到B机器时就可能访问不到。解决办法是将附件存储迁移到共享文件系统或对象存储,比如NAS、MinIO、阿里云OSS等,让所有节点访问同一份文件。

第二,用户会话状态。多机部署时,用户的登录状态如果只存在单台机器内存里,负载均衡把请求转发到另一台,用户就掉登录了。活字格的多机部署场景,我的做法是配合负载均衡做“会话保持”(同一用户的请求始终转发到同一台机器),或者将会话数据外置到Redis等集中式缓存中。前者实现简单,但机器宕机时该机器上的用户会掉线;后者更稳定,但需要额外的Redis运维。

第三,计划任务。活字格有定时任务功能,多机部署后,如果每个节点都执行一遍同一个计划任务,会造成重复执行的问题。这个需要在设计上做取舍:要么指定一台机器作为任务执行节点,要么在任务逻辑里做分布式锁(通过数据库或Redis实现只允许一台机器执行)。刚开始玩多机部署的朋友,最容易在这个坑里翻车。

3.3 数据库层扩容:从连接优化到读写分离

Web层扩了机器之后,数据库很快就会成为新的瓶颈,这是所有Web应用扩容过程中都会遇到的阶段。对活字格应用而言,数据库层扩容的第一步不是分库分表,而是优化连接和索引。

我的实践路径是这样的:先把慢SQL日志打开,把执行时间超过一秒的SQL全部捞出来,逐个分析执行计划,补索引、改写查询。等慢SQL清完了,再考虑连接池参数。活字格外联数据库的连接串和连接池设置是有讲究的,最大连接数不要盲目调大,太大反而会让数据库线程切换开销飙升。一般先根据“并发请求数 × 单请求平均占用连接数”估算一个合理值,再压测调整。

如果数据库压力还是大,就需要上读写分离了。活字格本身不直接提供读写分离配置,但我一般通过数据库自身的读写分离方案来做,比如MySQL的主从复制+中间件(MyCat、ShardingSphere、ProxySQL等)或者云数据库自带的读写分离地址。应用层配置写库和读库两个连接,把查询类的数据表操作指到读库,写操作走主库。这个改造需要一定代码量,但回报很高。

再说分库分表。我建议大部分活字格项目不要轻易走到这一步。分库分表意味着应用层要处理路由逻辑,活字格这种偏可视化配置的平台,硬套分库分表规则,开发复杂度会翻好几倍,得不偿失。只有在单表数据量过亿、且业务确实有明显分片维度(比如按租户、按区域、按时间)时才考虑,而且要提前设计好,不能等线上出问题再临时拆分。

3.4 异步化与消息队列削峰

高并发场景下,并不是所有请求都需要同步处理。比如批量导入数据、生成报表、发送通知这类“重操作”,如果全部同步执行,会把服务端的请求线程全部占满,正常用户的请求就会排队等待。活字格里做异步化,我常用的有两条路。

第一条是活字格自身的“计划任务+数据表状态”方案。需要异步处理的业务,先往“任务表”里插入一条记录,状态置为待处理;计划任务定时扫描这个表,把待处理的任务捞出来执行,执行完更新状态。页面端通过轮询或刷新操作来获取结果。这条方案实现简单,不需要额外组件,适合中等量级的异步需求。

第二条是引入真正的消息队列,比如RabbitMQ或Kafka。服务端命令接收到请求后,把消息发到MQ里立即返回“受理成功”,后续由独立的消费者服务处理任务。这样做削峰效果最好,但活字格这边通常是扮演生产者角色,消费者要自己写独立的处理程序,技术栈会多出一块。我的建议是:在系统起步阶段用计划任务方案足够,等业务量级到了消息队列真的是必选项时再引入,避免过早引入复杂组件。

4. 实操记录:给活字格应用做性能压测和调优的全过程

4.1 压测前的准备工作和环境规划

准备压测活字格应用时,不要上来就拿JMeter乱打,先想清楚要验证什么。一般来说,我会把压测目标拆成三个维度:单页面的最大并发承载能力、核心接口(服务端命令)的吞吐量、数据库连接池在并发下的稳定性。目标不同,压测脚本的设计也不同。

测试环境尽量和生产环境保持同一配置比例,尤其是数据库的规格。如果测试环境数据库是1核2G,生产是8核16G,那压测结果直接参考价值很低。数据库是活字格性能的命门,配置差一大截,结果就是天壤之别。另外,压测前要清空无关的日志表,关闭计划任务,避免测试过程中后台任务干扰指标。

压测工具我用得最多的是JMeter和wrk。JMeter适合做复杂的业务场景模拟,比如登录、填报、查询、提交一条龙;wrk适合打纯HTTP压力,快速看单机极限。如果是内网环境测试,用几台压测机同时打,避免单台压测机自己先成了瓶颈。

4.2 压测中怎么观察瓶颈而不被表象误导

压测过程中,最容易犯的错是只盯着“响应时间”看,而忽略了后端各层的指标。正确的观察姿势是五路同时看:一是负载均衡层的连接数和转发延迟,二是Web节点的CPU、内存、线程数,三是数据库的连接数、CPU、慢SQL数量,四是磁盘IO和网络带宽,五是应用日志里有没有报错或超时。

举个例子。压到300并发时,页面响应从500毫秒变成了三秒。表面上像是Web应用不行了,但一看数据库监控,慢SQL数量激增,其中有一条SQL执行了二十多秒。这条SQL平时只有几十个并发时不觉得慢,一旦并发上来,锁等待和IO竞争放大,问题才暴露出来。这种瓶颈光靠加机器是解决不了的,必须去优化SQL和索引。

还有一个容易忽略的点:压测机器本身的性能。干过压测的朋友都知道,单机JMeter跑到一定线程数后,自己CPU先满了,发出去的请求严重失真。我建议压测机用多台,每台控制线程数不要太高,所有数据汇到一起分析。

4.3 压测后必做的几项优化动作

压测结束,数据拿到手里,接下来的优化动作要按性价比排序投入。我先说几个对活字格应用最有效、改动成本最低的优化。

第一,开启数据库慢查询日志,把压测中出现的所有慢SQL收集出来,逐一优化。大部分性能问题都发生在这层,修好了,效果立竿见影。

第二,检查页面数据表的绑定方式。有没有绑定整张表没加筛选的,有没有嵌套子表格导致单页发了几十个查询的,这些都要改掉。活字格表格提供“初始化时不加载数据”等选项,业务场景允许的都打开,需要时再通过查询命令加载。

第三,调整应用池的配置。IIS或者.NET运行时层面的进程回收策略、并发限制等参数,压测后根据实际曲线去调。尤其是进程回收的时机,如果回收策略设置不当,会出现每隔一段时间系统就卡一下的情况,这就是后台回收和高峰请求撞车了。

第四,检查服务端命令和定时任务的执行策略,避免非核心任务占用了太多资源。比如每五分钟跑一次的统计任务,可以改成每三十分钟跑一次,或者错峰执行。

4.4 一套可复制的性能巡检命令和查询脚本

每次压测或线上排查问题,我习惯用一套固定的查询脚本来快速摸底。数据库是MySQL的话,以下几个查询非常实用。

-- 查看当前数据库连接数(区分状态)
SHOW STATUS WHERE Variable_name IN ('Threads_connected', 'Threads_running', 'Max_used_connections');

-- 查看执行时间超过2秒的正在运行的SQL
SELECT id, user, host, db, command, time, state, info
FROM information_schema.processlist
WHERE command != 'Sleep' AND time > 2
ORDER BY time DESC;
-- 查看慢查询日志是否开启及相关配置
SHOW VARIABLES LIKE 'slow_query_log%';
SHOW VARIABLES LIKE 'long_query_time';

-- 查看最占用IO的表(需要开启performance_schema)
SELECT OBJECT_SCHEMA, OBJECT_NAME, COUNT_STAR, SUM_ROWS_EXAMINED
FROM performance_schema.table_io_waits_summary_by_table
ORDER BY SUM_ROWS_EXAMINED DESC
LIMIT 20;

活字格服务端和Web层面的检查,我一般直接看Windows/Linux的系统指标,用 top 、 free 、 iostat 这些命令,配合应用日志的关键字搜索,比如搜索“error”“timeout”“timeout expired”之类的关键词。

5. 常见问题与排查技巧实录

5.1 用户量一多,页面突然变慢?先查“数据表绑定”和“N+1查询”

这是一个典型问题:系统上线初期人少,一切正常;到几十上百个并发用户时,页面开始卡顿,数据库CPU居高不下。排查下来,十有八九是页面数据表绑定不合理造成的。“N+1查询”这个词很多后端开发不陌生,活字格里同样存在:一个页面加载,主表查询一次,之后每条记录又通过子表关联各查一次,页面上的表格行数多了,查询次数就爆炸。

解决办法是:删掉不必要的子表格绑定,改为通过服务端命令一次性把关联数据查好,再以JSON形式返回给页面。页面展示用“设置变量”去服务端取数据,而不是依赖多个数据表绑定自动发请求。这个改动对页面性能提升非常明显,属于“做过一次就再也不想用回笨办法”的优化。

5.2 多机部署后上传的图片时不时打不开?文件存储得统一

这个坑我真的踩过。活字格多机部署后,某个用户上传的图片,过了一段时间再打开,有时候能显示,有时候报404。去服务器上一看,文件在A机器上有,B机器上没有。因为负载均衡把请求轮流分发到不同机器,被分到B机器时就找不到文件了。

解决方案是把活字格的附件存储路径改到共享目录或者对象存储。内网环境可以用NAS共享盘,云上环境就直接上OSS/S3。改完之后,所有Web节点读写同一套文件存储,问题根治。这里有个小提醒:迁移历史附件时,要把旧文件全部复制过去,并且注意文件路径的目录结构保持一致,否则数据库里存的文件路径就失效了。

5.3 数据库连接池被打爆,应用大面积超时

高并发压测时,数据库连接池被打满是一个高频故障。表现是错误日志里出现“连接超时”“连接池已满”“timeout expired”,页面上大量请求报错或转圈。原因通常是连接被长时间占用不释放,或者单请求占用连接过多。

排查时,先看数据库当前的连接数,再对照应用端的活跃请求数。如果活跃请求只有一百,连接数却到了五百,说明存在连接泄漏或者每请求多连接的情况。活字格这块要重点检查页面端是否有多个数据源同时操作、服务端命令中的事务是否正确关闭。另外,数据库连接池的最大连接数不要设成“无限”,那样数据库迟早被拖垮,设置一个合理上限,宁可让部分请求排队等待,也不能让数据库直接“宕机”。

5.4 高并发下数据库CPU飙升,但是加机器没用?

还有一个常见场景:并发上来后数据库CPU飙到90%以上,但Web层资源还很空闲。这时候加Web机器是无效的,因为瓶颈在数据库。数据库CPU飙升的内因,不外乎三种:大量慢SQL、索引失效、锁竞争严重。

针对慢SQL和索引失效,用前面提到的慢查询日志和表扫描分析去定位。针对锁竞争,要看一下是否存在批量更新同一张表的操作。比如一个批量审批的功能,循环逐条更新订单表,每条更新都带锁,并发一高,互相等待,CPU就全花在锁管理上了。优化方案是把循环更新改成一条SQL批量更新,或者用临时表批量关联更新,把锁的范围和持有时间都降下来。

5.5 关于活字格内置数据库和正式数据库的“性能代差”

开发阶段大家贪方便,不管是正式项目还是演示项目,都直接用活字格的内置数据库。但内置库和外联库在生产环境下的性能差距是非常明显的,尤其是并发写入场景。我见过一个项目上线前没换库,结果正式使用第一天,几十个人同时录入数据,系统直接就卡死了。后来花了半天时间把数据迁移到SQL Server,同一套应用,性能完全变了个样。

我给所有做活字格项目的朋友的第一个建议都是:从立项第一天就决定好生产环境用的数据库类型,开发阶段就用同一种数据库,别等上线前再切换。数据库切换不是简单的“改个连接串”就完事,涉及字段类型差异、视图和存储过程重写、数据迁移校验等一堆工作,越早切换成本越低。

6. 最后聊几句心里话

做了这么多年低代码平台的自研和交付,我对活字格这类企业级产品的定位是:它扛得住“企业级高并发”,但前提是你得像对待一套正规Web系统一样去对待它。该优化SQL就优化SQL,该上缓存就上缓存,该扩容就扩容,没有什么“因为用了低代码所以可以躺平”的好事,也没有“因为用了低代码所以注定扛不住”的魔咒。

我个人的体会是,低代码平台和传统开发在性能优化上的底层逻辑完全一致,无非就是分层、缓存、异步、扩展这四板斧。区别只在于,活字格把很多基础代码能力替你封装好了,你不需要一行行去写ORM、写权限中间件,但架构上的判断和取舍,还是得你自己来。把这篇里的思路吃透,遇到并发问题不慌不乱,一条条去查,一层层去优化,最终交付出去的系统,是能扛住真实业务压力的。

最后分享一个我常用的判断标准吧:一个活字格应用能不能往外放量,不要问平台,去问监控。慢SQL有没有,连接数是不是够用,Web节点CPU余量还有多少,扩容方案有没有预留好。这些指标都清清楚楚了,你就比绝大多数质疑低代码高并发的人,往前走了一大步。

更多推荐