ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

UniGUI生产环境部署细节:参数配置、静态资源与稳定性排障指南

2026/10/5 1:30:57 拓冰建站 浏览量
UniGUI生产环境部署细节:参数配置、静态资源与稳定性排障指南 部署选项这个系列写到第五篇前四篇基本把 UniGUI 的几种部署形态都过了一遍——standalone 的 exe、IIS 下的 ISAPI dll、Linux 环境下的模块、Windows 服务化。按理说部署模式选定、文件拷上去就能跑但现实中真正让人头疼的反而是那些在开发机上根本不会出现、一上服务器就原形毕露的问题页面样式全丢、用户用一会儿就掉线、服务跑几天内存暴涨、莫名其妙白屏……这些问题大多不是部署模式选错了而是 ServerModule 参数没调对、静态资源没放对、异常没处理好。这篇就把这些容易被忽略的部署细节一次说透重点讲 UniGUI 部署时的参数配置、资源目录规划、生产环境稳定性和现场排障思路。适合已经能跑通 Demo、正准备把 UniGUI 应用真正发布到测试环境或生产服务器的朋友特别是那些开发十分钟、部署两小时的典型场景。1. ServerModule 里的配置项部署前必须逐项过一遍很多朋友拿到 UniGUI 项目开发阶段一直用默认的 ServerModule 配置从没打开过属性面板好好看一遍。其实部署到服务器后很多玄学问题根源都在 ServerModule 的配置项上。1.1 端口、监听地址与连接数上限先说最基础的端口。开发阶段通常用的是 IDE 自动分配的调试端口发布后一定要在 ServerModule 里固定成明确的端口。有人习惯把端口改成 0以为这样是自动选择空闲端口实际上这个用法在生产环境非常危险——端口不可预期客户端无法连接日志也不一定打印出真实端口。固定端口时还要注意一件事Windows 服务器上 Hyper-V、WSL、Docker 这些功能会预留一段 TCP 端口范围如果 UniGUI 的端口恰好落在保留范围内服务启动时会报 bind 失败。我曾经被这个问题坑过进程明明起来了客户端就是连不上最后用命令查了一下才知道端口被系统保留。netsh interface ipv4 show excludedportrange protocoltcp看到输出里有大段保留区间就换个端口或者用下面的命令给 UniGUI 单独预留一个排除范围。这个细节在云服务器上尤其常见因为云厂商的镜像经常会预装 Hyper-V 组件。连接数上限是另一个关键项。UniGUI 默认的并发连接数对小型内部系统够用但一旦部署到公网或者给几十上百人同时用默认值可能成为瓶颈。注意这里的连接数和数据库连接池里的连接数是两个概念别搞混。UniGUI 是 Session 线程模型每个浏览器会话对应服务端的一个独立线程上下文连接数上限直接决定了同时在线人数的天花板。如果用户量超出限制会被拒绝访问或排队等待而不是自动扩容。1.2 Session 超时调大还是调小要先想清楚SessionTimeout 是部署 UniGUI 时最容易纠结的参数。默认值往往比较小开发时感觉不到问题因为一直在操作页面。部署后就会发现用户打开页面去处理别的事情过十几分钟回来再点按钮页面直接白屏或者重新加载用户会以为系统崩溃了。很多人第一反应是把 SessionTimeout 调到很大比如 24 小时但这往往不是最佳方案。Session 意味着内存占用每个 Session 里可能挂着数据库连接、业务对象、界面状态。Session 时间越长服务器上累积的死 Session 越多内存消耗越明显。而且 UniGUI 的 Session 和 HTTP 连接不是一回事Session 超时只是会话状态过期不代表 TCP 连接一定断开。有个经验值可以参考内部管理系统 60 到 120 分钟比较合适公网系统 30 到 60 分钟之间再配合前端页面的自动提示和刷新机制让用户在超时前有感知。比单纯调大 SessionTimeout 靠谱得多。1.3 同步与异步这个开关直接影响并发吞吐UniGUI 提供了同步操作相关的配置选项不少老项目为了省事会在 ServerModule 里把某些操作设成同步执行。开发机和低并发环境看不出问题但用户量一上来性能会急剧恶化。原因是 UniGUI 的每个 Session 线程在同步模式下执行耗时代码比如数据库查询、调用外部 HTTP 接口时这个线程是阻塞的。如果很多用户同时触发了一个需要 3 秒才返回的同步调用线程池很快就会被全部占满新请求只能排队或超时。异步模式则会在等待期间释放线程去处理其他请求吞吐量有天壤之别。这里我的建议很直接新项目从第一天就按异步模式写代码老项目迁移到 UniGUI 时至少保证 ServerModule 层面的配置不阻塞线程池。如果你的业务代码里还写了大量 TIdHTTP、TIdTCPClient 之类的同步调用部署前务必给这些调用加上超时时间否则第三方接口一抖动整个系统跟着卡死。1.4 浏览器兼容检查与压缩开关UniGUI 自带浏览器兼容性检查机制服务器会根据请求的 User-Agent 判断浏览器是否满足运行要求不满足时返回提示页。这个机制在早期能解决很多兼容性问题但部署到内部系统后如果用户用的是定制版浏览器或旧版 IE 内核反而会被拦在门外。公司内部系统建议直接关闭兼容性检查或者将判定条件放宽否则光为什么这个浏览器打不开的工单就够你忙一周。压缩开关Compression部署前也值得打开。UniGUI 前端基于 Ext JSJS 和 CSS 文件体积不小开启 gzip 压缩后传输体积能减少一半以上对公网部署尤其明显。不过有个细节如果前面还套了 Nginx 或其他反向代理而且在代理层已经开启了 gzip后端的压缩基本就是白费 CPU建议关掉后端压缩统一由代理层处理避免双重压缩。配置项开发环境建议生产环境建议说明Port随机调试端口固定端口避开系统保留端口范围SessionTimeout默认即可30-120 分钟配合前端超时提示同步操作随意尽量关闭/异步避免阻塞 Session 线程浏览器兼容检查开启可关闭定制浏览器兼容问题Compression关闭视代理层决定避免双重 gzip2. 部署目录结构与静态资源为什么刷新后页面变丑了UniGUI 应用部署和普通 Delphi 程序不一样它不只是拷一个 exe 过去就完事。UniGUI 项目编译出来的是服务端逻辑前端页面和静态资源Ext JS 框架文件、CSS、字体、图标等还需要一并准备。这是部署时最容易踩坑、也最不容易被定位的地方。2.1 exe 和前端资源到底哪个才是完整程序部署 UniGUI 应用时服务器上实际需要两类内容程序本体standalone 模式的 exe或 ISAPI 模式的 dll前端资源UniGUI 运行库附带的 Ext JS 资源文件包括框架脚本、主题样式、图片字体等这两类内容缺一不可。如果你进入 ServerModule 的 ExtRoot 属性会发现它有两种工作方式一种是内置模式把资源文件直接编译进 exe/dll另一种是外置模式运行时从一个外部目录读取资源文件。内置模式的好处是部署简单只需要分发一个 exe特别适合给客户做演示、或者不想暴露资源文件路径的场景。缺点也明显exe 体积会增大几十甚至上百 MB每次修改一个前端主题都要重新编译整个程序而且这种模式下客户端每次加载页面都要从服务器拉取完整的 JS/CSS不配合缓存会拖慢首屏速度。外置模式则是把资源目录放在程序旁边类似资源文件夹 可执行文件的结构。这种方式下程序体积小、资源可以单独更新还可以把资源部署到 CDN 或另一台静态服务器上减轻应用服务器的压力。我第一次用外置模式时犯过一个低级错误只拷了 exe 忘了拷资源目录结果页面能打开但完全没有样式控制台报一堆 404排查了很久才反应过来是资源路径不对。2.2 ExtRoot 外置的路径处理用外置模式时ExtRoot 可以是指向服务器本地目录的物理路径也可以是一个 URL 地址。物理路径适合大多数场景比如把资源解压到服务器的 C 盘某个固定目录ExtRoot 指向这个目录即可。注意路径末尾的斜杠、以及服务器账号是否有这个目录的读取权限IIS 下运行还要确认应用程序池账号有权限访问该目录否则同样会 404。如果你有多个 UniGUI 应用部署在同一台服务器上强烈建议把前端资源抽取成公共目录让多个应用的 ExtRoot 都指向同一个地方。这样升级一次资源文件所有应用都生效不用每个应用单独更新也方便切 CDN。2.3 静态资源 404 的排查套路资源加载出问题时别急着改代码按下面的路径走一遍基本能定位打开浏览器 F12 开发者工具切到 Network 面板刷新页面过滤 404 请求看是哪些资源加载失败。这一步能快速确定是程序本体问题还是资源没找到问题。如果 404 的都是 /ext/ 路径下的资源说明 ExtRoot 配置不对或者资源目录没有正确发布。如果资源确实在服务器上但浏览器访问不到就要看是否有反向代理——有些 Nginx 配置只把根路径 / 转发到后端/ext/ 路径没有对应的 location结果资源请求打到 Nginx 默认页面上了。还有一个容易忽略的点如果你把资源部署到了另一个域名比如用了 CDN浏览器跨域加载 JS/CSS 时会受同源策略限制需要在 CDN 或静态服务器上配置正确的 CORS 头和 Content-Type。特别是字体文件常见的问题是字体加载被跨域策略拦掉导致图标显示成方框。3. 生产环境必做的四个稳定性开关部署上线只是开始真正考验部署质量的是稳定性。以下四个问题如果不在部署阶段处理好后面一定会在半夜被电话叫醒。3.1 数据库连接不能放在全局变量里这是我见过最多人犯的错也是后果最严重的错。Delphi 传统桌面程序中全局放一个数据库连接组件很常见但在 UniGUI 的 Web 模型下全局连接是灾难的种子。UniGUI 是 Session 线程模型每个客户端会话对应一个独立的线程上下文。如果多个线程共享同一个数据库连接轻则操作互相干扰、事务错乱重则直接触发 Access Violation 或数据库连接被强制断开。更隐蔽的是这种问题没有规律偶发性极强生产环境出了故障很难复现。正确做法是把数据库连接放到 UniMainModule 这个特殊的 DataModule 里。UniMainModule 在每一个 Session 创建时自动实例化Session 销毁时自动释放天然就是一个每会话一个连接的完美容器。在它的 OnCreate 事件里创建连接、打开数据库OnDestroy 里关闭连接就能保证连接的生命周期和会话完全一致。如果需要更精细的数据库连接管理比如连接池复用可以用 FireDAC 自带的连接池或者自己写一个基于数据库连接的资源池组件。但除非你的系统确实存在大量短连接重复创建的开销否则先按每 Session 一个连接来做简单可靠排障也容易。3.2 Session 持久化与回收重启不丢登录没那么简单UniGUI 默认把 Session 放在内存里服务器一重启所有在线用户的会话状态全部丢失用户刷新页面就回到登录页。对于内部管理系统来说还能接受但对于要求高的公网应用服务重启导致全员下线是不可接受的。UniGUI 本身支持 Session 状态的持久化选项可以把 Session 内容持久化到数据库或其他存储介质。启用后服务重启用户刷新页面能自动恢复会话。但这里有个性能权衡持久化会增加每次请求的读写开销Session 数据越大性能影响越明显。所以要不要开启持久化取决于业务的重要程度。我的建议是内部系统不必须公网对外系统强烈建议。Session 的回收策略也要在部署时考虑清楚。与其依赖默认的 SessionTimeout 到期清理不如在服务器上做一个定时任务定期调用 UniGUI 提供的清理接口主动回收那些长时间空闲的 Session。高峰期以后再配合清理一次能有效控制内存占用让系统长时间运行不卡顿。3.3 日志与全局异常处理别让用户看技术报错默认情况下 UniGUI 遇到未处理异常会在页面上弹出一个包含异常信息的对话框。这对测试环境没问题生产环境让用户看到满是英文异常的对话框既不专业又泄露技术细节。而且异常发生后没有记录你根本不知道用户是操作什么才触发的。部署前务必在 ServerModule 的全局异常事件里做统一处理把异常信息写入日志文件同时给用户返回一个友好的提示页。日志至少应包含发生时间、SessionID、用户身份如果有、异常类名和消息、调用堆栈、当前请求的 URL、客户端 IP。有了这些信息远程排障才能进行否则用户一句点了按钮报错你根本无从下手。自己写日志时注意一个问题UniGUI 是多线程模型多个 Session 线程可能同时写同一个日志文件如果不加锁日志文件会互相覆盖、内容错乱。建议自己封装一个线程安全的日志写入方法或者用第三方日志库按天滚动。日志文件要定期清理不然磁盘写满后整个应用就彻底罢工了。3.4 内存增长监控与进程看护Web 服务长时间运行后内存缓慢增长这是常见现象但增长到一定程度导致服务器卡死就危险了。部署时就在监控上做好安排至少在 Windows 任务管理器里把进程的私有工作集和工作集两列加进去观察运行一段时间后内存是否单调上升。内存持续不回落常见原因有几个全局对象或静态变量里放了不断膨胀的缓存事件处理器没解除挂接用第三方组件时没有正确释放资源。排查时重点关注 Global 单元和长期存活对象的生命周期。另外无论 standalone 模式还是 Windows 服务模式都建议做一个进程看护机制。standalone 的 exe 如果意外崩溃退出不会自动重新启动服务就彻底中断了。可以做一个定时检查的计划任务或者用 NSSM 把 exe 注册成 Windows 服务并配置失败自动重启。注意自动重启不等于解决问题进程看护只是应急手段真正的根因还是要通过日志分析找出来。4. 部署现场问题排查链路一次真实故障的完整复盘部署排障能力和开发能力同样重要。拿一个我实际经历过的故障来说完整复盘一遍排查链路你就知道 UniGUI 部署出了问题该从哪下手。4.1 故障现象与第一现场信息收集当时是一个内部管理系统部署了 standalone 模式的 UniGUI 应用运行了大概一周用户开始反映系统用着用着突然没响应所有按钮点击都没反应页面一直转圈等多久都不恢复只有重启服务才能恢复但用几天又会复发。接到反馈的第一时间我没有去看代码而是先去收集第一现场信息。做了三件事检查服务器上的 UniGUI 日志文件看异常发生时有没有记录到异常信息查看进程的线程数和句柄数在没有任何用户操作的情况下观察是否还在增长尝试用浏览器直接访问一个最简单的页面判断是整个服务挂了还是部分功能挂了日志里发现了大量Session Timeout和Socket Error记录进程的线程数在故障期间明显高于正常水平而且居高不下。这说明问题不是简单的页面逻辑错误而是有线程被卡住没有释放。4.2 从现象到根因线程为什么只增不减UniGUI 的每个 Session 对应一个线程线程数高说明同时存在的 Session 很多或者 Session 没有正常退出。进一步分析日志后发现故障发生的时间点集中在每次外部接口调用之后——系统有一个功能会调用第三方 HTTP 接口查询数据调用失败率在那段时间明显升高。问题就出在这段代码它用的是 TIdHTTP 同步调用第三方接口而且没有设置连接超时和读取超时。第三方接口响应变慢时前端用户发起请求后Session 线程会阻塞在 HTTP 调用上一直等第三方返回。用户等得不耐烦刷新页面又触发新的 Session又堵在新的请求上。线程越积越多直到线程池被占满所有新请求都进不来系统表现就是卡死了。这是 UniGUI 部署后非常典型的表面现象复杂、根源简单的问题。不是内存泄漏不是数据库死锁就是一段没有超时保护的同步调用。4.3 修复方案与恢复验证修复做了两件事一是给所有外部 HTTP 调用加上连接超时和读取超时测试后选了 5 秒和 10 秒二是把这类调用从 Session 线程里挪出去用独立的线程或异步模式去执行执行完成后通过回调或消息通知界面更新。修复后在测试环境做了验证给第三方接口做一个模拟的延迟返回观察系统的线程数和响应时间。结果很明确单个 Session 卡住时只影响它自己其他用户不受牵连线程池不再被拖垮。上线后再观察到那个第三方接口慢的情况系统没有再出现假死。这个案例说到底是同步阻塞 线程池耗尽的问题在 UniGUI 的部署场景里特别容易踩中。如果你发现系统偶发性假死、重启就恢复优先检查所有同步耗时的调用是否设置了超时以及是否占用了 Session 线程。5. 多实例、HTTPS、反向代理从单机走向正规军系统从几个人用到几百人用部署架构就要跟着升级了。这里不是让你一步到位搞微服务而是把单机的 UniGUI 应用部署得更有韧性和可维护性。5.1 IIS ISAPI 的关键配置如果你选择 ISAPI 模式部署在 IIS 上有几个配置不处理好应用能跑起来但各种奇怪问题不断。应用程序池要设置为无托管代码托管管道模式选集成。如果编译出来的 DLL 是 32 位的还要在应用程序池的高级设置里把启用 32 位应用程序设为 True否则加载 DLL 会失败。这个坑在 64 位服务器上特别容易遇到默认 64 位池直接跑 32 位 DLL服务启动就报 500。ISAPI DLL 还会被应用程序池的回收策略影响。IIS 默认的空闲超时设置为 20 分钟意味着如果 20 分钟内没有请求应用程序池会被回收。UniGUI 应用被回收后在内存中的 Session 全部丢失大量全局状态也没了用户会突然被踢下线。部署时建议把空闲超时设为 0不超时或调到一个很大的值。同时调整回收里的固定时间间隔避免在业务高峰期自动回收。还有一个经常被忽略的问题ISAPI DLL 加载后被 IIS 进程锁定直接覆盖 DLL 文件会报文件被占用。更新版本时要先回收应用程序池再替换文件最后启动池。这两步顺序反了就会遇到文件复制失败。5.2 Nginx 反向代理与 WebSocket 升级很多团队喜欢用 Nginx 做入口把请求转发到后端的 UniGUI 应用。UniGUI 某些功能依赖 WebSocket 和服务器保持长连接而 Nginx 默认不转发 Upgrade 头导致这些功能在反代后全部失灵。配置其实就几行但缺一不可location / { proxy_pass http://127.0.0.1:8077; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_set_header Host $host; proxy_read_timeout 3600s; proxy_send_timeout 3600s; }一个容易踩的坑是 proxy_read_timeout。Nginx 默认 60 秒内没有数据返回就断开连接而 UniGUI 后端如果某个操作耗时较长比如报表查询 1 分钟以上代理层会先切断连接客户端表现就是请求发出去没响应。按前面的经验这个超时时间应该大于 UniGUI 的 SessionTimeout通常设置成 3600 秒以上比较稳妥。Nginx 层还可以开启 gzip压缩 JS/CSS 资源。但如果 UniGUI 后端已经开过压缩这里就不要开了两处叠加只是白白消耗 CPU。5.3 Windows 服务化与开机自启standalone 模式的 exe 直接裸奔有风险用户一注销系统程序就跟着退出系统重启后也不会自动启动。所以正式环境建议把 exe 注册成 Windows 服务。推荐用 NSSM 这个小工具命令很简单nssm install UniGUIApp C:\App\UniGUIApp.exe nssm set UniGUIApp AppDirectory C:\App nssm set UniGUIApp AppExit Default Restart nssm set UniGUIApp Start SERVICE_AUTO_START nssm start UniGUIApp注册成服务后还要设置失败自动重启和服务恢复里的重新启动服务。另外如果服务依赖数据库、Redis 等服务建议把启动类型设为自动延迟启动给数据库服务留出启动时间否则开机顺序不对会启动失败。5.4 版本热更新怎么发布不影响在线用户发布新版本时最怕用户正在使用系统你这边把程序重启了。内部的系统还好说公网系统每一次重启都可能让用户丢操作、产生不满。我的做法是分模式处理。standalone 模式没有平滑升级的天然能力只能在低峰期操作先停止服务替换 exe保留配置文件和资源目录再启动。ISAPI 模式则可以利用 IIS 的应用程序池回收机制把新 DLL 放到临时目录在 IIS 里回收应用程序池等池重启后迅速替换 DLL再把池重新启动。这样能把服务中断的时间压缩到非常短。但无论哪种模式都不要只替换文件就完事。每次发布前做三件事备份旧的 exe/dll 和配置文件用测试环境完整验证新版本准备一条回滚方案。数据库结构有变更时尤其谨慎程序的回滚可能很容易数据库的回滚就麻烦了所以数据库脚本和程序发布一定要分离先确认数据层稳定再切换程序。发布前最后两份清单部署这件事做了这么多年我的体会是真正点击发布的时间只占 10%剩下 90% 的功夫都在准备和验证上。每次发布前我会把 ServerModule 的所有配置项截图存档升级后逐项对照防止新版本把默认值覆盖掉。在测试环境模拟一次服务器重启确认 Session 清理后用户看到的是友好提示页而不是异常堆栈。还有一个很少被提到的小细节UniGUI 的时区和服务器时区。如果服务器时区设置不对日志时间、Session 过期时间、数据库时间字段会全部错乱查问题的时候非常痛苦。部署完第一件事就是把服务器时区校准然后反复确认日志里的时间戳是本地时间。部署选项这个系列写到这里从模式选择、参数配置、资源处理、稳定性保障到故障排查基本覆盖了 UniGUI 从开发机走向服务器会遇到的完整问题。每一篇里的坑都是真实踩过的如果你在部署时遇到了系列里没讲到的情况欢迎留言说说你的现象和环境大家一起把 UniGUI 部署这事彻底摸透。