LiaoLiao.WebAPI项目存在的问题-扩展技术选型
1.实际问题
使用Newlife框架(Newlife.core还有Xcode)出现了内存泄露问题(表现是iis线程内存不停增长,请求进不来)

在架构上就是天然冲突。
IIS 是为:
-
网站
-
管理后台
-
企业系统
-
表单提交
-
REST API
设计的。
它的核心假设是:
请求进来 → 快速处理 → 马上结束
IIS 的真实内部模型(简化)

关键点
-
队列是有限的
-
线程池是有限的
-
请求必须“尽快结束”
小游戏服务端:
-
心跳 1s / 2s
-
拉状态
-
同步在线人数
-
房间数据
-
排行榜
-
推送
-
长轮询 / 半长连接
实际上在做的是:
伪长连接游戏服
比如:
10000 玩家 × 每秒 1 次心跳 = 10000 QPS每个请求里你还在做逻辑、查库、状态同步。
在这时会发生:
IIS 并不会告诉你“我扛不住了”
它的表现是:
-
请求排队
-
超时
-
新连接进不来
-
浏览器卡住
-
客户端请求无响应
你看到的现象就是:
“IIS 请求进不来”
但其实是
🔥 IIS 的请求队列满了
导致会“觉得是连接瓶颈”
因为看到的是:
-
CPU 还没满
-
内存还没爆
-
但请求卡死
这正是 IIS 的典型特征。
它不是“慢”,而是:
拒绝接新请求
newlife框架+IIS怎么调都不稳:
-
调线程池 → 好一会
-
调 maxConcurrentRequestsPerCPU → 好一会
-
调队列长度 → 好一会
-
但一上量又崩
因为在对抗 IIS 的设计初衷。
NewLife 在“真正的游戏服场景”反而很强
如果把:
-
IIS
-
ASP.NET
-
HTTP
全部拿掉
只保留:
-
NewLife.Net
-
SocketServer
-
自定义协议
那么:
NewLife 是非常强的
2.问题点
泄露点 ①:所有 XCode 实体默认开启 EntityCache
所有继承:
Entity<T> 的实体类,例如:
-
Room
-
User
-
Liao
-
ChatLog
-
OnlineUser
-
等等
❌ 问题代码(默认行为)
Room.FindById(id); User.Find("xxx"); 背后发生的是:
EntityCache.Entities[id] = entity; 永远不会删除。
泄露等级
⭐⭐⭐⭐⭐(最高)
这是进程内存持续上涨的第一来源。
泄露点 ②:TimerX / Delay / Periodic 未集中托管
项目中存在大量:
TimerX.Delay(...) TimerX.Periodic(...) ❌ 问题本质
TimerX 内部是:
static Scheduler └── List<TimerX> └── Action 回调 └── 捕获 this 只要没 Dispose:
❌ GC 永远回收不了
泄露等级
⭐⭐⭐⭐⭐
这是第二大稳定增长来源。
⚠️ 特别危险的写法
TimerX.Delay(() => { this.DoWork(); }, 1000); 👉 lambda 捕获整个 Service / Controller。
✅ 必须整改
禁止在任意地方直接用 TimerX。
统一:
BackgroundTaskManager.Add( new TimerX(OnTimer, null, 1000, 1000) ); 并在程序关闭时:
Dispose(); 或直接迁移到:
BackgroundService (推荐)
泄露点 ③:Controller / Service 中启动后台线程
Task.Run(() => { while(true) { ... Thread.Sleep(1000); } }); ❌ 问题
-
请求结束
-
HttpContext 被捕获
-
用户信息被捕获
-
DI Scope 被捕获
-
线程永远不结束
泄露等级
⭐⭐⭐⭐⭐(灾难级)
这是 IIS / Kestrel 下的大忌。
✅ 必须整改
❌ Controller 禁止 Task.Run
❌ 禁止 while(true)
✅ 改为:
public class Worker : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { ... await Task.Delay(1000); } } }
泄露点 ④:Static Service / Manager 类
例如:
public static class RoomManager { public static Dictionary<int, Room> Rooms = new(); } ❌ 问题
-
static 永生
-
Dictionary 无限增长
-
没有过期策略
泄露等级
⭐⭐⭐⭐
✅ 建议
-
static 改为 DI 单例
-
使用 MemoryCache + 过期
-
明确最大容量
泄露点 ⑤:事件订阅未解除
SomeService.OnMessage += Handler; 但没有:
-= Handler; ❌ 本质
事件 = 强引用链:
static publisher → subscriber 泄露等级
⭐⭐⭐⭐
非常隐蔽,dump 才能查出来。
✅ 整改
-
禁止 Controller 订阅事件
-
所有事件必须可反注册
-
或使用 WeakEvent
泄露点 ⑥:XCode DAL / DbSession 未释放
var dal = DAL.Create(); dal.Query(sql); ❌ 问题
-
DbSession 持有连接
-
Session 被缓存
-
连接池异常增长
泄露等级
⭐⭐⭐
✅ 正确方式
using var dal = DAL.Create(); 或统一走 Entity API。
泄露点 ⑦:日志对象捕获大对象
你有:
Log.Info(JsonConvert.SerializeObject(obj)); obj 里包含:
-
大 List
-
用户集合
-
房间状态
❌ 问题
-
Logger 内部异步队列
-
消息未刷盘前一直在内存
-
高并发下堆积
泄露等级
⭐⭐⭐
✅ 建议
-
日志不要打完整对象
-
大对象只打 ID / Count
✅ 总体风险等级评估
| 模块 | 风险 |
|---|---|
| XCode 实体缓存 | 🔥🔥🔥🔥🔥 |
| TimerX | 🔥🔥🔥🔥🔥 |
| 后台线程 | 🔥🔥🔥🔥🔥 |
| Static Manager | 🔥🔥🔥🔥 |
| 事件 | 🔥🔥🔥🔥 |
| DAL | 🔥🔥🔥 |
| 日志 |
🔥🔥 |
结论:上述给出的问题都是框架本身代码的问题,而不是应用层代码的问题。
而是:它是“服务端常驻进程框架”,不是“请求驱动框架”。
NewLife 的设计年代背景是:
-
.NET Framework
-
Windows Service
-
游戏服
-
Socket Server
-
IM Server
-
单体部署
-
一台机器跑几年
而不是:
-
ASP.NET Core
-
Kestrel
-
云原生
-
容器
-
横向扩展
-
无状态 API
-
高频伸缩
所以现在的“问题”,其实是范式冲突
| 维度 | NewLife | WebAPI |
|---|---|---|
| 生命周期 | 进程级 | 请求级 |
| 对象 | 长期存活 | 短生命周期 |
| 缓存 | 默认开启 | 明确声明 |
| 定时器 | 全局 | HostedService |
| 状态 | 常驻内存 | 无状态 |
| static | 合理 | 危险 |
Newlife不适合做webapi框架,搭配iis;只适合做基础框架,应用日志模块等。
3.同样是高并发,游戏服和WebApi的不同?
“高并发”不是一个维度。
真正决定架构是否合适的,从来不是 QPS,
而是👇
连接模型 + 生命周期模型 + 状态归属模型
游戏服是“状态驱动并发”
WebAPI 是“请求驱动并发”
它们看起来都“并发高”,
但底层运行模型完全不是一回事。
一、游戏服的并发是什么并发?
游戏服的并发是:
大量长连接,同时在线
特点:
-
一个连接 = 一个玩家
-
连接持续几小时
-
状态长期存在内存
-
心跳维持
-
数据频繁复用
模型是:

这是“对象常驻模型”
Player
{
UserId
Socket
Room
Position
HP
Buffs
}
这个对象:
-
创建一次
-
用几小时
-
最后断线销毁
所以游戏服的核心优化目标是:
| 目标 | 原因 |
|---|---|
| 少 GC | GC 会卡顿 |
| 多缓存 | 减少 IO |
| 多 static | 快 |
| 内存换 CPU | 合理 |
这正是 NewLife 的设计哲学。
所以在游戏服里:
-
static 是优点
-
Cache 是刚需
-
TimerX 非常自然
-
全局对象完全合理
二、WebAPI 的并发是什么并发?
WebAPI 的并发是:
大量短请求瞬间涌入
特点:
-
一个请求 5–50ms
-
请求结束立刻销毁
-
无状态
-
横向扩容
-
随时重启
模型是:

这是“瞬时对象模型”
对象的理想寿命是:
毫秒级
这两种模型的根本冲突
| 维度 | 游戏服 | WebAPI |
|---|---|---|
| 对象 | 常驻 | 瞬态 |
| 内存 | 世界状态 | 临时计算 |
| 生命周期 | 小时 | 毫秒 |
| GC | 尽量少 | 频繁可控 |
| static | 合理 | 危险 |
| Cache | 默认 | 明确 |
所以同样是 10 万并发:
-
游戏服:10 万 连接
-
WebAPI:10 万 瞬时请求
这是完全不同的东西。
三、为什么Newlife框架适合管理后台?
管理后台本质是什么?
低并发 + 高复用 + 高读取
特点:
-
用户少(几十 / 几百)
-
数据结构固定
-
查询重复率高
-
操作节奏慢
-
不追求极限延迟
典型场景:
-
用户列表
-
配置表
-
权限表
-
字典表
-
菜单表
这些数据:
-
非常适合缓存
-
非常适合 static
-
几乎不增长
-
变更极少
这正是 XCode EntityCache 的“完美场景”。
对比一下三个场景
| 场景 | 是否适合 NewLife |
|---|---|
| 游戏服 | ✅ 非常合适 |
| 管理后台 | ✅ 合适 |
| WebAPI | ❌ 默认不适合 |
不是并发问题。
是生命周期模型问题。
四、为什么“游戏服高并发”不会内存涨?
因为游戏服的内存模型是:
在线人数 = 内存上限 -
10 万玩家
-
就 10 万 Player 对象
-
玩家下线 → 对象释放
这是:
有上限的并发
而 WebAPI 是:
-
用户 ID 无限
-
查询参数无限
-
URL 无限
-
数据组合无限
你今天查:
/room?id=1001 明天查:
/room?id=1002 后天查:
/room?id=999999 EntityCache 会把它们全记住。
于是:
没有自然上限
这才是内存灾难。
所以会看到这个现象
| 系统 | 内存走势 |
|---|---|
| 游戏服 | 稳定平台 |
| 管理后台 | 小平台 |
| WebAPI | 持续上涨 |
不是 GC 不行。
是对象从来没被允许死亡。
这就是一句最重要的话
游戏服并发是“状态并发”
WebAPI 并发是“请求并发”
这两者之间,没有任何“并发数”可以类比。
不同应用场景 → 生命周期模型不同 → 不能用同一套通用框架
后端系统的本质不是语言,也不是框架,而是:生命周期模型。
五、后端常见三大场景,本质上是三种“生物形态”
这三类:
-
WebAPI
-
管理后台
-
小游戏服务器
表面都是服务端
本质是三种完全不同的“生命体”
(1)WebAPI ——「瞬时型系统」
生命周期模型
Request → Process → Response → Destroy
特征
-
对象活几毫秒
-
无状态
-
可横向扩展
-
可随时重启
-
高 QPS
-
弱一致性
适合的技术特征
| 项目 | 需求 |
|---|---|
| static | ❌ |
| 缓存 | 明确、有限 |
| 定时器 | 极少 |
| 内存状态 | ❌ |
| 并发 | 请求并发 |
这是 “函数式服务模型”
典型框架:
-
ASP.NET Core
-
Spring Boot
-
FastAPI
-
Gin
(2)管理后台 ——「长寿型系统」
生命周期模型
用户少
操作慢
数据稳定
特征
-
并发低
-
数据复用高
-
查询重复
-
强一致
-
稳定优先
适合的技术特征
| 项目 | 需求 |
|---|---|
| static | ✅ |
| EntityCache | ✅ |
| 单例 | ✅ |
| 内存缓存 | ✅ |
| 重启 | 少 |
👉 本质是:
“业务系统”
典型框架:
-
NewLife XCode
-
EF Core + Admin
-
Django Admin
-
若依
(3)小游戏服务器 ——「常驻世界型系统」
生命周期模型

特征
-
长连接
-
内存即世界
-
状态同步
-
实时性
-
延迟敏感
-
GC 敏感
适合的技术特征
| 项目 | 需求 |
|---|---|
| static | ✅ |
| 全局对象 | ✅ |
| 内存状态 | ✅ |
| 定时器 | ✅ |
| HTTP | ❌ |
| IIS | ❌ |
本质是:
“实时系统 / 状态机”
三种模型根本无法统一
| 系统 | 类比 |
|---|---|
| WebAPI | 函数 |
| 管理后台 | 应用程序 |
| 游戏服 | 操作系统 |
不能拿操作系统内核去写Web 接口
同理:
你也不可能用 WebAPI 框架去承载游戏世界。
六、为什么“通用框架”几乎都失败?
因为它们试图同时满足:
-
短生命周期
-
长生命周期
-
状态型
-
无状态型
这是物理上冲突的。
所以会看到:
-
游戏框架不能优雅写 WebAPI
-
Web 框架写不好游戏服
-
管理后台框架性能一塌糊涂
所以真正成熟后端的思维是:
先选生命周期模型,再选框架。
而不是:
“我用 .NET / Java,应该选哪个框架?”
技术模型对照表
| 场景 | 生命周期 | 正确模型 |
|---|---|---|
| REST API | 毫秒 | 请求模型 |
| 管理后台 | 分钟~小时 | 应用模型 |
| 游戏服务器 | 小时~天 | 世界模型 |
| IM | 长连接 | 会话模型 |
| 网关 | 常驻 | 流量模型 |
| 任务调度 | 常驻 | 调度模型 |
“后端不是写接口,是管理生命周期。”
七、后端系统生命周期模型总图
后端系统的本质分类(不是按语言)

(1)瞬时系统(WebAPI / 微服务)
生命周期模型

特点关键词
-
无状态
-
横向扩展
-
随时重启
-
高 QPS
-
对 GC 友好
-
对 static 极度敏感
典型系统
-
WebAPI
-
微服务
-
BFF
-
接口网关(非连接型)
技术特征
| 项目 | 是否 |
|---|---|
| static | ❌ |
| 全局缓存 | ❌ |
| EntityCache | ❌ |
| 定时器 | ⚠️ 极少 |
| 长连接 | ❌ |
| IIS | ✅ |
| Kestrel | ✅ |
正确框架
-
ASP.NET Core
-
Spring Boot
-
FastAPI
-
Gin
函数模型
(2)应用系统(管理后台 / 业务系统)
生命周期模型

特点关键词
-
并发低
-
数据稳定
-
查询复用高
-
内存缓存非常有价值
-
重启成本高
典型系统
-
管理后台
-
ERP
-
CMS
-
运维平台
-
内部系统
技术特征
| 项目 | 是否 |
|---|---|
| static | ✅ |
| EntityCache | ✅ |
| 单例 | ✅ |
| 全局缓存 | ✅ |
| 定时任务 | ✅ |
| 长连接 | ❌ |
正确框架
-
NewLife + XCode
-
Django Admin
-
若依
-
ABP
应用模型
(3)常驻系统(游戏服 / IM / 实时系统)
生命周期模型

特点关键词
-
长连接
-
状态即内存
-
世界常驻
-
延迟敏感
-
GC 敏感
-
极少重启
典型系统
-
小游戏服务器
-
IM
-
推送系统
-
网关
-
房间系统
-
实时对战
技术特征
| 项目 | 是否 |
|---|---|
| static | ✅ |
| 全局状态 | ✅ |
| Timer | ✅ |
| HTTP | ❌ |
| IIS | ❌ |
| Socket | ✅ |
| WebSocket | ✅ |
正确框架
-
NewLife.Net
-
自研 SocketServer
-
Netty
-
Skynet
-
Actor 模型
👉 世界模型
(4)三种模型的“绝对禁区”
这是最重要的一部分。
WebAPI 禁区
-
static Dictionary
-
EntityCache
-
TimerX
-
后台线程
-
常驻对象
-
长连接状态
一句话:
请求结束,一切都应该消失。
游戏服禁区
-
IIS
-
Controller
-
HTTP 心跳
-
WebAPI 同步
-
每次查数据库
一句话:
世界不能被请求打断。
管理后台禁区
-
极致高并发
-
每请求建世界
-
重复计算
-
禁缓存
一句话:
稳定优先于性能。
选型只要问 3 个问题
问题 1:
这个系统的“核心对象”要活多久?
-
毫秒 → WebAPI
-
分钟 / 小时 → 管理后台
-
小时 / 天 → 游戏服
问题 2:
状态主要存在在哪里?
-
数据库 → WebAPI
-
内存缓存 → 管理后台
-
内存世界 → 游戏服
问题 3:
系统能不能随时重启?
-
能 → WebAPI
-
勉强 → 管理后台
-
不能 → 游戏服
七、“有登录 / 下线” ≠ “是常驻系统”
这是整个后端领域最容易混淆的点之一。
关键区分点只有一个
登录后的“状态”活在哪里?
这才是本质。
Web 系统的“登录”
登录后发生什么?

服务器这边:
-
❌ 不保存“你在线”
-
❌ 不维护“你当前状态”
-
❌ 不知道你是否在线
服务器只做一件事:
验证 token 是否有效
用户“下线”是什么意思?
在 Web 系统中:
-
只是客户端删 token
-
或 token 过期
-
或 refresh token 失效
服务器并没有一个“用户对象”。
Web 登录本质是:
身份校验机制,不是会话生命周期。
游戏服的“登录 / 下线”
对比一下

服务器内存中真实存在:
Dictionary<long, Player> -
Player 有血量
-
有位置
-
有状态
-
有技能 CD
-
有房间引用
👉 这才叫“在线”。
所以真正的分水岭是这条线
| 问题 | WebAPI | 游戏服 |
|---|---|---|
| 是否有 token | ✅ | ❌ |
| 是否有在线表 | ❌ | ✅ |
| 是否有 Player 对象 | ❌ | ✅ |
| 是否有世界状态 | ❌ | ✅ |
| 服务器是否感知在线 | ❌ | ✅ |
Web 系统“不知道你在不在”。
游戏服“必须知道你在不在”。
八.是否维护“业务推进所必须的连续状态”
是是否需要常驻对象的分界线。
什么叫“维护对象”?
“维护对象”不是指:
-
有 UserId
-
有 nickname
-
有 onlineTime
-
有统计字段
这些都不是对象生命周期。
以下都不算“对象状态”
这些只是属性数据:
| 类型 | 举例 |
|---|---|
| 用户资料 | 昵称、头像 |
| 统计 | 在线时长 |
| 累计 | 登录次数 |
| 行为记录 | 最近一次访问 |
它们有一个共同点👇
你随时可以从数据库算出来。
即使服务重启:
-
不影响业务正确性
-
不影响流程继续
真正的“对象状态”指什么?
我们来看真正需要对象的东西。
对象状态 = 推进业务所必需的“过程状态”
例如:
游戏服里的 Player
当前房间 当前位置 血量 技能 CD BUFF 战斗目标 当前操作 这些状态:
-
强关联
-
实时变化
-
必须连续
-
一旦丢失 → 游戏崩
👉 这些不能只靠数据库恢复。
这才叫:
对象状态(Process State)
一句话区分(非常重要)
如果状态丢了,业务还能不能继续?
-
能 → 数据状态
-
不能 → 对象状态
Web 系统里的“状态”
我们来看你关心的那些。
在线时长
-
累计数
-
可补算
-
可延迟写库
❌ 不需要对象
是否在线
-
最近 5 分钟是否活跃
-
TTL 机制
❌ 不需要对象
是否正在生成图片
-
task.status = processing
-
可重试
-
可恢复
❌ 不需要对象
用户当前在哪一步
例如:
已上传照片 → 已下单 → 已支付 → 已生成 这些都是:
-
状态机
-
离散状态
-
可落库
❌ 不需要对象
那什么时候“必须有对象”?
判断公式
状态是否依赖“上一次内存中的状态”才能继续?
如果:
-
当前逻辑 = f(上一次内存状态)
-
而不是 = f(数据库状态)
那你就进入了常驻对象领域。
举例对比
Web 业务:
当前状态 = 查数据库 游戏逻辑:
当前状态 = 上一帧内存状态 + 本次操作 这是本质区别。
再换一个角度(非常清晰)
能不能“幂等重放”?
-
Web 请求:可以
-
游戏帧:不可以
为什么?
因为游戏的状态是:
连续不可重放的。
状态分界表
| 状态类型 | 是否需要对象 |
|---|---|
| 用户资料 | ❌ |
| 统计字段 | ❌ |
| 登录态 | ❌ |
| 活跃时间 | ❌ |
| 任务状态 | ❌ |
| 流程步骤 | ❌ |
| 房间 | ✅ |
| 战斗 | ✅ |
| 实时操作 | ✅ |
| 帧同步 | ✅ |
| 技能 CD | ✅ |
| Buff | ✅ |
因为你看到的都是“状态”两个字。
但其实有两种状态:
数据状态(Data State)
-
可存储
-
可恢复
-
可延迟
-
可重复
Web 世界
过程状态(Process State)
-
实时
-
连续
-
不可重建
-
强顺序
👉 游戏世界
凡是可以“通过查库恢复”的状态,都不该存在内存对象中。
如果服务重启后还能继续业务,那就不该有常驻对象。
凡是依赖“在线对象 + 实时事件”的系统,都是常驻对象模型。
九、所有后端系统的四大运行模型全景图

第四个模型(很多人不知道它存在)
👇👇👇
┌──────────────────────────┐ │ 计算 / 推理模型 │ │ Compute-based │ └──────────────────────────┘ 请求进入 ↓ 独占计算资源 ↓ 长时间运行 ↓ 返回结果 ✅ 四大模型完整如下:
| 模型 | 驱动核心 | 是否常驻连接 | 是否有世界 | 典型系统 |
|---|---|---|---|---|
| WebAPI | 请求 | ❌ | ❌ | 管理后台 |
| 消息系统 | 消息 | ❌ | ❌ | 邮件 / MQ |
| 常驻对象 | 世界 | ✅ | ✅ | 游戏 / IM |
| 计算模型 | 任务 | ❌ | ❌ | AI / 视频 |
🧱 一、WebAPI 模型(请求驱动)
Request ↓ 处理 ↓ Response 特点
-
完全无状态(或伪状态)
-
不关心用户是否在线
-
不保存连接
-
扩容极其简单
适合
-
管理后台
-
CRUD
-
登录
-
支付回调
-
查询接口
❌ 不适合
-
长连接
-
心跳
-
在线人数
-
房间
✉️ 二、消息驱动模型(Message-based)
Producer → Queue → Consumer 特点
-
强可靠
-
可重试
-
延迟允许
-
无实时要求
适合
-
邮件
-
短信
-
异步通知
-
积分发放
-
订单状态同步
本质
系统维护的是“消息”,不是用户。
🎮 三、常驻对象模型(World-based)
Server Start ↓ World Init ↓ while(running) { Tick() ProcessEvents() } 特点
-
内存即世界
-
有生命周期
-
有连接
-
有 Tick
-
有实时性
适合
-
游戏服务器
-
聊天室
-
实时协作
-
撮合系统
-
房间系统
危险点
-
内存泄露
-
锁竞争
-
GC 压力
-
框架不当直接卡死
(你踩过的坑就在这里)
🤖 四、计算 / 推理模型(Compute-based)
接任务 ↓ 占 GPU / CPU ↓ 计算 30s ~ 5min ↓ 输出结果 特点
-
单任务重
-
资源昂贵
-
并发低
-
延迟可接受
适合
-
AI 换脸
-
视频转码
-
OCR
-
推理服务
通常配合
-
队列
-
回调
-
状态轮询
🔥 真正的现实系统:混合模型
你现在看到的所有大型系统👇
微信 抖音 王者荣耀 Steam 支付宝 全部是:
WebAPI + 消息系统 + 常驻对象 + 计算模型 只是:
-
哪个是核心
-
哪个是外围
不同。
不是并发高就难,
而是模型选错一定死。

浙公网安备 33010602011771号