Azure函数应用使用托管身份实现SQL认证的性能问题及替代方案咨询
性能问题根因与优化方案
你当前代码的性能瓶颈非常明确:每次QuoteContext实例化时都会同步请求Azure AD访问令牌,单次令牌请求的网络开销就是你观察到的3秒额外耗时。同时你每次都新建DefaultAzureCredential实例,也浪费了类库本身内置的令牌缓存能力。
可行优化方案
方案1:官方推荐最简方案,用SQL驱动内置认证
只要你使用的Microsoft.Data.SqlClient版本≥2.1,直接修改连接字符串,添加Authentication=Active Directory Default参数即可,驱动会自动处理令牌的获取、缓存、刷新逻辑,完全不需要自己写代码处理AccessToken,性能最优。
示例连接字符串:
Server=tcp:<你的SQL服务器名>.database.windows.net,1433;Database=<你的数据库名>;Authentication=Active Directory Default;Connect Timeout=30;
修改后你原有的QuoteContext构造函数可以直接删除所有令牌相关逻辑,恢复为默认实现即可:
public QuoteContext(DbContextOptions options) : base(options) { // 无需额外逻辑 }
方案2:自定义实现令牌缓存+连接拦截器
如果你因为版本限制不能用驱动内置认证,就用EF Core的连接拦截器+静态令牌缓存的方式实现,避免每次实例化DbContext都请求令牌:
首先实现自定义连接拦截器:
using Microsoft.EntityFrameworkCore.Diagnostics; using Azure.Core; using Azure.Identity; using Microsoft.Data.SqlClient; public class AzureSqlTokenInterceptor : DbConnectionInterceptor { // 静态复用Credential,复用其内部缓存能力 private static readonly DefaultAzureCredential _azureCredential = new DefaultAzureCredential(); private static readonly TokenRequestContext _tokenRequestContext = new TokenRequestContext( new[] { "https://database.windows.net/.default" } ); // 自定义缓存令牌,提前5分钟刷新避免过期 private static AccessToken _cachedAccessToken; // 异步连接场景处理(绝大多数Web场景都是异步) public override async Task<InterceptionResult> ConnectionOpeningAsync( DbConnection connection, ConnectionEventData eventData, InterceptionResult result, CancellationToken cancellationToken = default) { if (connection is SqlConnection sqlConn && string.IsNullOrEmpty(sqlConn.AccessToken)) { if (_cachedAccessToken.ExpiresOn <= DateTimeOffset.UtcNow.AddMinutes(5)) { _cachedAccessToken = await _azureCredential.GetTokenAsync(_tokenRequestContext, cancellationToken); } sqlConn.AccessToken = _cachedAccessToken.Token; } return await base.ConnectionOpeningAsync(connection, eventData, result, cancellationToken); } // 同步连接场景处理,无同步需求可以省略 public override InterceptionResult ConnectionOpening( DbConnection connection, ConnectionEventData eventData, InterceptionResult result) { if (connection is SqlConnection sqlConn && string.IsNullOrEmpty(sqlConn.AccessToken)) { if (_cachedAccessToken.ExpiresOn <= DateTimeOffset.UtcNow.AddMinutes(5)) { _cachedAccessToken = _azureCredential.GetToken(_tokenRequestContext, CancellationToken.None); } sqlConn.AccessToken = _cachedAccessToken.Token; } return base.ConnectionOpening(connection, eventData, result); } }
然后在注册DbContext的时候注入拦截器即可:
services.AddDbContext<QuoteContext>(options => { options.UseSqlServer("你的连接字符串") .AddInterceptors(new AzureSqlTokenInterceptor()); });
额外注意:不要在DbContext的构造函数中执行任何IO操作,构造函数应当只处理简单的初始化逻辑,同步调用IO接口不仅会带来不必要的阻塞开销,高并发场景下还可能引发线程池耗尽问题。
内容的提问来源于stack exchange,提问作者Santosh Jindal
相关产品推荐
相关产品推荐

