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. 终极武器:水平扩展方案

当单机优化到达极限时,我们需要:

  1. 负载均衡

    • Nginx反向代理
    • ARR(Application Request Routing)
    • 云负载均衡器(AWS ALB / Azure Load Balancer)
  2. 数据库扩展

    • 读写分离(主库写,从库读)
    • 分库分表(按考试类型分库)
    • 使用Azure SQL弹性池
  3. 微服务改造

    // 将考试系统拆分为独立服务
    [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核服务器

记住三个黄金法则:

  1. 先测量后优化(一定要用性能分析工具)
  2. 二八原则(优化最耗时的20%代码)
  3. 适可而止(不要为了优化而牺牲可维护性)

当你的网站开始出现性能问题时,不妨按照这个路线图逐步排查:缓存 → 异步 → 数据库 → 会话 → 代码 → 架构扩展。就像给汽车做改装,先换高性能火花塞,再考虑换发动机,最后才是造新的高速公路。