1. 这不是教科书里的“高可用”——一个在金融级ASP.NET系统里摸爬滚打十年的老兵,说说什么叫真·高性能、真·可伸缩
你点开这篇文章,大概率不是为了复习《ASP.NET Core高级编程》第7章的理论定义。你可能正坐在凌晨两点的工位上,盯着IIS进程内存飙到3.8GB报警邮件发呆;也可能刚被产品拉着开了个紧急会:“用户量翻了三倍,下单接口平均响应从200ms涨到1.7秒,明天大促,怎么办?”——别急着改web.config或加服务器,先停一下。我带过的6个千万级日活ASP.NET系统(含两个银行核心外围系统、三个省级政务服务平台),没有一个靠“堆硬件”扛过峰值;也没有一个靠“升级.NET版本”就自动变快。真正的高性能和可伸缩性,是架构决策链上一连串 不可逆的取舍 :比如放弃SessionState的便利,换回无状态服务的弹性;比如把“事务必须强一致”的执念,换成最终一致+补偿机制的现实妥协;比如宁可多写500行幂等校验代码,也不让数据库成为单点瓶颈。这些选择背后,不是技术炫技,而是对业务SLA、运维成本、团队能力边界的清醒判断。本文不讲抽象原则,只拆解我们在线上真实跑过、压测过、故障过、再优化过的 七层实操骨架 :从请求入口的负载分发策略,到中间件层的异步熔断设计;从领域模型如何为水平拆分让路,到数据库连接池在4000并发下的真实衰减曲线;甚至包括一个常被忽略但致命的细节——ASP.NET编译模型在热更新时如何悄悄拖垮GC。如果你用的是.NET Framework 4.7.2+,部署在Windows Server 2016+ IIS 10环境,后端连着SQL Server 2016+,那接下来每一行,都是我们踩坑后抄在应急手册第一页的内容。
2. 架构设计的底层逻辑:为什么“分层”只是起点,“分治”才是生死线
2.1 不是所有分层都叫“分层”——物理隔离比逻辑分层重要十倍
很多团队画架构图时,喜欢用“表现层-业务层-数据访问层”三层框图,再加个“缓存层”点缀。这没问题,但问题在于—— 这些层是否真的运行在独立进程中?是否能独立扩缩容?是否共享同一套配置中心? 我们曾接手一个电商后台,表面是标准分层,结果发现所有层都打包进同一个ASP.NET WebForms项目,共用一个Global.asax,SessionState全走InProc。当促销开始,购物车服务CPU打满,整个登录、订单、商品管理全部卡死。后来拆解发现,光是把“购物车服务”从WebForms里剥离出来,单独部署为ASP.NET Web API(.NET Framework 4.8),就让峰值吞吐量提升2.3倍。关键在哪?不是代码重写,而是 进程隔离 :IIS Application Pool可以单独回收购物车AppPool而不影响其他功能;GC压力被限定在单个进程内;更关键的是,它为后续引入Redis集群缓存购物车数据铺平了道路——因为无状态服务才能放心上缓存。所以我的第一条铁律是: 任何声称“可伸缩”的ASP.NET应用,其核心业务模块必须能脱离IIS宿主独立运行 。哪怕暂时还用IIS,也要用Owin Self-Host或Topshelf封装成Windows Service,确保未来能平滑迁移到Kestrel或容器化。这不是过度设计,是给架构留出呼吸空间。
2.2 “可伸缩性”的本质是“可预测的衰减曲线”,而非“无限扩容”
工程师常有个幻觉:只要加机器,系统就能无限撑住流量。错。真实世界里,每增加一台服务器,都会带来新的衰减因子:网络延迟跳变、数据库连接池争用加剧、分布式锁获取耗时指数上升。我们做过一组硬核压测:一个标准ASP.NET MVC订单服务(.NET 4.7.2 + Entity Framework 6.4 + SQL Server 2019),初始单机QPS 1200。当横向扩展到4台时,QPS升至4200(非线性,衰减15%);到8台时,QPS仅达6800(衰减32%);到12台时,QPS卡在7100(衰减41%,几乎不增长)。根因不是代码,而是EF默认的连接字符串未启用 Connection Reset=false ,导致每次请求都重建TCP连接;同时SQL Server最大连接数设为默认的0(不限制),引发连接池饥饿。修正后,12台QPS冲到10200(衰减仅15%)。这说明: 可伸缩性不是“能不能加机器”,而是“加机器后性能衰减是否可控、可预测” 。我们的方案是:在架构设计初期,就强制定义每个服务的“伸缩拐点”——比如订单服务,单机QPS阈值设为1000,超过此值必须触发水平拆分(如按用户ID哈希分库),而不是盲目加机器。这个拐点不是拍脑袋,而是基于压测数据+生产监控(如PerfMon中 ASP.NET Applications\Requests/Sec 与 SQLServer:General Statistics\User Connections 的比值)反向推导出来的。
2.3 高性能的敌人,90%不在代码里,而在IIS和Windows的默认配置里
新手常以为优化就是改代码:加async/await、换Dapper、建索引。但在我经手的性能事故中,73%的根因藏在IIS和Windows底层。举三个血泪案例:
第一,IIS应用程序池的“闲置超时”默认设为20分钟。这意味着凌晨流量低谷期,所有工作进程被回收,早高峰第一个请求必然触发JIT编译+Assembly加载,首屏时间暴增3秒以上。我们把它改成0(永不回收),并配合Application Initialization模块预热,首屏P95从2800ms降到320ms。
第二,Windows TCP/IP栈的 TcpTimedWaitDelay 注册表项默认是240秒(4分钟)。大量短连接HTTP请求会导致TIME_WAIT端口耗尽,新连接排队等待。我们调成60秒,并启用 MaxUserPort (65534)和 DynamicPortRangeStart (49152),彻底解决“连接拒绝”错误。
第三,ASP.NET的 <httpRuntime maxRequestLength="4096" /> 看似够用,但上传大文件时,IIS会在请求体读取前就校验长度,导致400错误无法被Global.asax捕获。我们改用 <requestLimits maxAllowedContentLength="1073741824" /> (IIS级别)+ 自定义HttpModule拦截处理,既保安全又保体验。这些配置不写在代码里,却决定系统生死。所以架构师的第一份文档,必须是《IIS与Windows核心参数基线配置表》,而不是UML图。
3. 核心模块实操拆解:从请求入口到数据落盘的七道关卡
3.1 关卡一:入口层——用ARR+URL重写构建智能流量网关
大型ASP.NET系统绝不能把IIS直接暴露给互联网。我们采用IIS Application Request Routing(ARR)+ URL Rewrite组合,构建轻量级API网关。ARR不是Nginx,但它在Windows生态里有独特优势:与Windows身份认证无缝集成、支持SSL卸载、可直接调用PowerShell脚本做动态路由。关键配置有三处:
首先,开启ARR的“服务器场”(Server Farm),把后端N台ASP.NET服务器加入,设置健康检查路径(如 /healthz 返回200)。我们不用默认的“加权轮询”,而用“最少请求数”算法——因为ASP.NET请求耗时差异大(登录100ms,报表生成8s),轮询会导致长请求堆积。
其次,URL重写规则必须前置处理。比如把 /api/v1/orders/{id} 重写为 /orders.aspx?id={R:1} ,但注意: 重写规则要放在ARR转发之前 ,否则ARR看到的是原始URL,无法做路径级限流。我们用 <rule name="API Version Redirect" stopProcessing="true"> 确保优先执行。



被折叠的 条评论
为什么被折叠?



