一、高并发场景下的核心挑战

当你的网站突然被大量用户访问时,服务器可能会像早高峰的地铁站一样拥挤不堪。在.NET Core中处理高并发请求,首先要理解几个关键问题:

  1. 线程池饥饿:默认线程池可能无法快速响应突发流量,导致请求排队。
  2. 数据库连接瓶颈:频繁的数据库操作会让连接池迅速耗尽。
  3. 资源锁竞争:比如缓存或文件读写时的锁冲突。

举个实际例子:假设你有一个电商秒杀接口,代码如下(技术栈:.NET Core 6 + Dapper):

// 错误示例:同步阻塞导致线程池饥饿
public IActionResult KillProduct(int productId)
{
    var stock = _db.QueryFirst<int>("SELECT Stock FROM Products WHERE Id = @Id", new { Id = productId });
    if (stock <= 0) return BadRequest("已售罄");
    
    // 同步更新数据库(阻塞线程)
    _db.Execute("UPDATE Products SET Stock = Stock - 1 WHERE Id = @Id", new { Id = productId });
    return Ok("抢购成功");
}

注释:这段代码在并发请求下会出现超卖和线程阻塞,就像收银员一个一个处理顾客结账,队伍越排越长。

二、异步编程与性能优化

.NET Core的异步特性是解决高并发的利器。重构上面的代码:

// 正确示例:异步非阻塞处理
public async Task<IActionResult> KillProductAsync(int productId)
{
    // 异步查询(释放线程)
    var stock = await _db.QueryFirstAsync<int>(
        "SELECT Stock FROM Products WHERE Id = @Id", 
        new { Id = productId });
    
    if (stock <= 0) return BadRequest("已售罄");
    
    // 异步更新(使用乐观并发控制)
    var rows = await _db.ExecuteAsync(
        "UPDATE Products SET Stock = Stock - 1 WHERE Id = @Id AND Stock > 0", 
        new { Id = productId });
    
    return rows > 0 ? Ok("抢购成功") : BadRequest("库存不足");
}

注释:通过async/await避免线程阻塞,SQL语句添加Stock > 0条件防止超卖。

关联技术

  • 使用ValueTask替代Task减少内存分配
  • IAsyncEnumerable处理流式数据

三、分布式缓存与队列削峰

当单机性能达到瓶颈时,可以引入Redis和消息队列。以下是结合Redis的库存扣减方案:

// 使用Redis + StackExchange.Redis
public async Task<bool> DeductStockAsync(string productId)
{
    var db = _redis.GetDatabase();
    
    // Lua脚本保证原子性
    var script = @"local stock = tonumber(redis.call('GET', KEYS[1]))
                  if stock > 0 then
                      redis.call('DECR', KEYS[1])
                      return 1
                  end
                  return 0";
                  
    var result = (int)await db.ScriptEvaluateAsync(
        script, 
        new RedisKey[] { $"product:{productId}:stock" });
    
    return result == 1;
}

注释:Lua脚本在Redis中原子执行,避免竞态条件,就像把抢购请求变成排队叫号。

四、微服务与水平扩展

当流量持续增长时,需要考虑:

  1. 容器化部署:通过Docker+Kubernetes实现自动扩缩容
  2. 数据库分库分表:例如使用ShardingCore对SQL Server分片
  3. 负载均衡:Nginx轮询分发请求到多个服务实例
// 在Startup.cs中配置限流(使用AspNetCoreRateLimit)
services.AddRateLimiter(options => {
    options.AddPolicy("api", context => 
        RateLimitPartition.GetFixedWindowLimiter(
            partitionKey: context.User.Identity?.Name,
            factory: _ => new FixedWindowRateLimiterOptions
            {
                PermitLimit = 100,
                Window = TimeSpan.FromMinutes(1)
            }));
});

注释:这相当于给每个用户发一张"一分钟最多100次请求"的通行证。

五、实战注意事项

  1. 监控必不可少:集成Prometheus+Grafana监控QPS和延迟
  2. 熔断机制:Polly库实现服务降级
  3. 连接复用:如HttpClientFactory管理HTTP连接
// 使用Polly实现熔断
services.AddHttpClient<ProductService>()
    .AddTransientHttpErrorPolicy(policy => 
        policy.WaitAndRetryAsync(3, retryAttempt => 
            TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))))
    .AddCircuitBreakerAsync(
        exceptionsAllowedBeforeBreaking: 5,
        durationOfBreak: TimeSpan.FromSeconds(30));

注释:当连续失败5次后,系统会"休息"30秒,避免雪崩效应。

总结

处理高并发就像指挥交通,需要:

  • 异步化避免堵塞(像设置多车道)
  • 缓存和队列分流(像设置缓冲带)
  • 水平扩展增加容量(像增开收费站)

关键是要根据业务场景选择合适的技术组合,并始终通过压力测试验证方案。