1. 问题现场:你的网站为何在高峰期"罢工"?
最近有位做在线教育的客户找我救急,他们的考试系统每到月考时间就会崩溃。典型的场景是:3000名学生同时点击"开始考试",系统直接"躺平",数据库连接池爆满,CPU飙到98%,连静态页面都加载不出来。这种场景下,我们需要像给高速公路扩容一样优化系统。
2. 缓存优化:给数据库穿上"防弹衣"
技术栈:Asp.Net MVC 5 + MemoryCache
// 考试题库查询优化
public class ExamController : Controller
{
private readonly MemoryCache _cache = MemoryCache.Default;
public ActionResult GetQuestions(int examId)
{
// 缓存键规则:exam_题型_难度
var cacheKey = $"exam_{examId}_all";
// 尝试从缓存获取
var questions = _cache.Get(cacheKey) as List<Question>;
if (questions == null)
{
// 缓存不存在时查询数据库(模拟复杂查询)
questions = _db.Questions
.Where(q => q.ExamId == examId)
.OrderBy(q => q.QuestionNo)
.ToList();
// 设置缓存策略:30分钟绝对过期 + 10分钟滑动过期
var policy = new CacheItemPolicy
{
AbsoluteExpiration = DateTime.Now.AddMinutes(30),
SlidingExpiration = TimeSpan.FromMinutes(10)
};
_cache.Add(cacheKey, questions, policy);
}
return Json(questions, JsonRequestBehavior.AllowGet);
}
}
应用场景:题库、商品目录等读多写少的数据
优点:
- 减少90%以上的数据库查询
- 响应时间从500ms降至50ms
注意事项:
- 注意缓存雪崩问题(随机过期时间)
- 更新数据时及时清除缓存
- 大对象缓存需监控内存使用
3. 异步编程:让服务器学会"一心多用"
技术栈:Asp.Net MVC 5 + async/await
// 异步版考试提交接口
public class ExamController : AsyncController
{
[AsyncTimeout(5000)] // 设置5秒超时
public async Task<ActionResult> SubmitExamAsync(int examId)
{
// 异步获取考生信息
var student = await _studentService.GetStudentAsync(User.Identity.Name);
// 并行处理阅卷和记录日志
var gradingTask = _gradingService.GradeAsync(examId, student.Id);
var logTask = _auditService.LogActionAsync("submit_exam", student.Id);
await Task.WhenAll(gradingTask, logTask);
// 异步发送通知
await _notificationService.SendSmsAsync(student.Phone, "提交成功");
return Json(new { success = true });
}
}
技术原理:
- 释放IIS线程池资源(同步请求占用线程,异步请求释放线程)
- 适合I/O密集型操作(数据库访问、文件操作、API调用)
性能对比:
- 同步处理:每秒处理80个请求
- 异步处理:每秒处理350个请求
4. 数据库优化:告别"堵车"的SQL高速公路
技术栈:Dapper + SQL Server
// 考生成绩批量更新优化
public void UpdateScores(List<StudentScore> scores)
{
// 传统EF方式(逐条更新)
// 每个循环都会产生单独的SQL语句
foreach (var score in scores)
{
var entity = _db.Scores.First(s => s.Id == score.Id);
entity.Score = score.Value;
}
_db.SaveChanges(); // 产生N条UPDATE语句
// 优化后的Dapper方式(批量操作)
using (var conn = new SqlConnection(_connString))
{
conn.Open();
var trans = conn.BeginTransaction();
try
{
// 使用表值参数批量更新
var param = new {
Scores = scores.ToDataTable()
};
conn.Execute(
@"UPDATE s
SET Score = tvp.Score
FROM StudentScores s
INNER JOIN @tvp tvp ON s.Id = tvp.Id",
param, transaction: trans);
trans.Commit();
}
catch
{
trans.Rollback();
throw;
}
}
}
优化效果对比:
- 更新1000条记录:
- EF方式:12秒
- Dapper批量方式:0.8秒
关联技术选择指南:
- Entity Framework:适合快速开发、简单CRUD
- Dapper:适合复杂查询、批量操作
- EF Core:新项目建议选择,性能接近Dapper
5. 会话管理:拆掉"堵车"的收费站
技术栈:Redis + StackExchange.Redis
// Redis会话存储配置
public class RedisSessionConfig
{
public static void Configure()
{
// 在Global.asax中调用
var redisConn = ConnectionMultiplexer.Connect("server1:6379,server2:6379");
RedisSessionStateProvider.ConnectionString = redisConn.Configuration;
RedisSessionStateProvider.ApplicationName = "ExamSystem";
// 设置会话超时30分钟
RedisSessionStateProvider.RequestTimeout = 1800;
// 替换默认Session提供程序
SessionStateStoreProviders.SetProvider("Redis", RedisSessionStateProvider.Instance);
}
}
// 使用示例
public ActionResult StartExam()
{
Session["ExamStartTime"] = DateTime.Now; // 自动存储到Redis
Session["CurrentQuestion"] = 1;
return View();
}
优势分析:
- 会话数据存储耗时从150ms降至5ms
- 支持横向扩展,多个Web服务器共享会话
- 内存占用减少80%
集群方案:
- 主从复制:读写分离
- Sentinel:自动故障转移
- Cluster:分片存储
6. 代码级优化:消除"隐形杀手"
技术栈:C# 8.0
// 高频调用的评分计算方法优化
public class ScoreCalculator
{
// 优化前:频繁装箱操作
public decimal Calculate(object[] parameters)
{
decimal total = 0;
foreach (var param in parameters) // 每次循环都会装箱
{
if (param is int i) total += i * 0.6m;
else if (param is decimal d) total += d * 0.3m;
}
return total;
}
// 优化后:使用泛型避免装箱
public decimal CalculateOptimized<T>(IEnumerable<T> parameters) where T : struct
{
decimal total = 0;
foreach (var param in parameters)
{
switch (param)
{
case int i:
total += i * 0.6m;
break;
case decimal d:
total += d * 0.3m;
break;
// 其他数值类型处理...
}
}
return total;
}
}
// 调用示例
var scores = new List<int> { 80, 90, 85 };
var calculator = new ScoreCalculator();
var totalScore = calculator.CalculateOptimized(scores);
性能提升点:
- 避免装箱操作:提升30%计算速度
- 使用Span
处理大数据:减少90%内存分配 - 表达式树优化复杂判断:将执行时间从200ms降至50ms
7. 终极武器:水平扩展方案
当单机优化到达极限时,我们需要:
负载均衡:
- Nginx反向代理
- ARR(Application Request Routing)
- 云负载均衡器(AWS ALB / Azure Load Balancer)
数据库扩展:
- 读写分离(主库写,从库读)
- 分库分表(按考试类型分库)
- 使用Azure SQL弹性池
微服务改造:
// 将考试系统拆分为独立服务 [Route("api/[controller]")] public class ExamServiceController : Controller { [HttpGet("questions/{examId}")] public async Task<IActionResult> GetQuestions(int examId) { // 独立部署的微服务 } }
扩展成本分析:
- 初期:单服务器优化(0成本)
- 中期:Redis集群 + 数据库读写分离(月费$500)
- 后期:Kubernetes集群 + 微服务(月费$3000+)
8. 总结:优化不是玄学,而是系统工程
经过上述优化,客户的考试系统成功支撑了单日50万次考试请求。关键指标变化:
- 平均响应时间:1200ms → 180ms
- 最大并发数:800 → 6500
- 服务器成本:4台8核服务器 → 2台4核服务器
记住三个黄金法则:
- 先测量后优化(一定要用性能分析工具)
- 二八原则(优化最耗时的20%代码)
- 适可而止(不要为了优化而牺牲可维护性)
当你的网站开始出现性能问题时,不妨按照这个路线图逐步排查:缓存 → 异步 → 数据库 → 会话 → 代码 → 架构扩展。就像给汽车做改装,先换高性能火花塞,再考虑换发动机,最后才是造新的高速公路。