LiaoLiao.WebAPI项目存在的问题-扩展技术选型

1.实际问题

使用Newlife框架(Newlife.core还有Xcode)出现了内存泄露问题(表现是iis线程内存不停增长,请求进不来)

image

 在架构上就是天然冲突。

IIS 是为

  • 网站

  • 管理后台

  • 企业系统

  • 表单提交

  • REST API

设计的。

它的核心假设是:

请求进来 → 快速处理 → 马上结束

IIS 的真实内部模型(简化)

image

关键点

  • 队列是有限的

  • 线程池是有限的

  • 请求必须“尽快结束”

小游戏服务端

  • 心跳 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(Core + XCode)并不是“不能写 WebAPI”
而是:它是“服务端常驻进程框架”,不是“请求驱动框架”。

NewLife 的设计年代背景是:

  • .NET Framework

  • Windows Service

  • 游戏服

  • Socket Server

  • IM Server

  • 单体部署

  • 一台机器跑几年

而不是:

  • ASP.NET Core

  • Kestrel

  • 云原生

  • 容器

  • 横向扩展

  • 无状态 API

  • 高频伸缩


 所以现在的“问题”,其实是范式冲突

维度NewLifeWebAPI
生命周期 进程级 请求级
对象 长期存活 短生命周期
缓存 默认开启 明确声明
定时器 全局 HostedService
状态 常驻内存 无状态
static 合理 危险

Newlife不适合做webapi框架,搭配iis;只适合做基础框架,应用日志模块等。

3.同样是高并发,游戏服和WebApi的不同?

“高并发”不是一个维度。

真正决定架构是否合适的,从来不是 QPS,
而是👇

连接模型 + 生命周期模型 + 状态归属模型

游戏服是“状态驱动并发”
WebAPI 是“请求驱动并发”

它们看起来都“并发高”,
但底层运行模型完全不是一回事


一、游戏服的并发是什么并发?

游戏服的并发是:

大量长连接,同时在线

特点:

  • 一个连接 = 一个玩家

  • 连接持续几小时

  • 状态长期存在内存

  • 心跳维持

  • 数据频繁复用

模型是:

image

 这是“对象常驻模型”

Player
{
    UserId
    Socket
    Room
    Position
    HP
    Buffs
}

这个对象:

  • 创建一次

  • 用几小时

  • 最后断线销毁


所以游戏服的核心优化目标是:

目标原因
少 GC GC 会卡顿
多缓存 减少 IO
多 static
内存换 CPU 合理

 这正是 NewLife 的设计哲学。

所以在游戏服里:

  • static 是优点

  • Cache 是刚需

  • TimerX 非常自然

  • 全局对象完全合理


 二、WebAPI 的并发是什么并发?

WebAPI 的并发是:

大量短请求瞬间涌入

特点:

  • 一个请求 5–50ms

  • 请求结束立刻销毁

  • 无状态

  • 横向扩容

  • 随时重启

模型是:

image

这是“瞬时对象模型”

对象的理想寿命是:

毫秒级


 这两种模型的根本冲突

维度游戏服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)小游戏服务器 ——「常驻世界型系统」

生命周期模型

image

特征

  • 长连接

  • 内存即世界

  • 状态同步

  • 实时性

  • 延迟敏感

  • GC 敏感

适合的技术特征

项目需求
static
全局对象
内存状态
定时器
HTTP
IIS

 本质是:

“实时系统 / 状态机”


 三种模型根本无法统一

系统类比
WebAPI 函数
管理后台 应用程序
游戏服 操作系统

不能拿操作系统内核去写Web 接口

同理:

你也不可能用 WebAPI 框架去承载游戏世界。


六、为什么“通用框架”几乎都失败?

因为它们试图同时满足:

  • 短生命周期

  • 长生命周期

  • 状态型

  • 无状态型

这是物理上冲突的。

所以会看到:

  • 游戏框架不能优雅写 WebAPI

  • Web 框架写不好游戏服

  • 管理后台框架性能一塌糊涂


 所以真正成熟后端的思维是:

先选生命周期模型,再选框架。

而不是:

“我用 .NET / Java,应该选哪个框架?”

技术模型对照表

场景生命周期正确模型
REST API 毫秒 请求模型
管理后台 分钟~小时 应用模型
游戏服务器 小时~天 世界模型
IM 长连接 会话模型
网关 常驻 流量模型
任务调度 常驻 调度模型

 

 

“后端不是写接口,是管理生命周期。”

七、后端系统生命周期模型总图

后端系统的本质分类(不是按语言)

image

 

(1)瞬时系统(WebAPI / 微服务)

生命周期模型

 

image

特点关键词

  • 无状态

  • 横向扩展

  • 随时重启

  • 高 QPS

  • 对 GC 友好

  • 对 static 极度敏感

典型系统

  • WebAPI

  • 微服务

  • BFF

  • 接口网关(非连接型)

技术特征

项目是否
static
全局缓存
EntityCache
定时器 ⚠️ 极少
长连接
IIS
Kestrel

正确框架

  • ASP.NET Core

  • Spring Boot

  • FastAPI

  • Gin

函数模型


(2)应用系统(管理后台 / 业务系统)

生命周期模型

image

特点关键词

  • 并发低

  • 数据稳定

  • 查询复用高

  • 内存缓存非常有价值

  • 重启成本高

典型系统

  • 管理后台

  • ERP

  • CMS

  • 运维平台

  • 内部系统

技术特征

项目是否
static
EntityCache
单例
全局缓存
定时任务
长连接

正确框架

  • NewLife + XCode

  • Django Admin

  • 若依

  • ABP

应用模型


(3)常驻系统(游戏服 / IM / 实时系统)

生命周期模型

image

 

特点关键词

  • 长连接

  • 状态即内存

  • 世界常驻

  • 延迟敏感

  • 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 系统的“登录”

登录后发生什么?

image

服务器这边:

  • ❌ 不保存“你在线”

  • ❌ 不维护“你当前状态”

  • ❌ 不知道你是否在线

服务器只做一件事:

验证 token 是否有效


用户“下线”是什么意思?

在 Web 系统中:

  • 只是客户端删 token

  • 或 token 过期

  • 或 refresh token 失效

服务器并没有一个“用户对象”。

 Web 登录本质是:

身份校验机制,不是会话生命周期。


游戏服的“登录 / 下线”

对比一下

 

image

 

服务器内存中真实存在:

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)

  • 实时

  • 连续

  • 不可重建

  • 强顺序

👉 游戏世界

 

凡是可以“通过查库恢复”的状态,都不该存在内存对象中。

如果服务重启后还能继续业务,那就不该有常驻对象。

凡是依赖“在线对象 + 实时事件”的系统,都是常驻对象模型。

 

九、所有后端系统的四大运行模型全景图

image

第四个模型(很多人不知道它存在)

👇👇👇

 
┌──────────────────────────┐ │ 计算 / 推理模型 │ │ 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 + 消息系统 + 常驻对象 + 计算模型

只是:

  • 哪个是核心

  • 哪个是外围

不同。

不是并发高就难,
而是模型选错一定死。

posted @ 2026-01-19 15:50  头号程序媛  阅读(0)  评论(0)    收藏  举报