使用 EF Core 的 EnableRetryOnFailure 解决短暂的数据库连接失败问题
阿里云服务器有时会出现短暂的连接不上数据库服务器(RDS)的问题,之前由于没有启用 Entity Framework Core 的失败重试功能(默认是禁用的),短暂的连接失败立马会引发下面的异常从而出现500错误。
System.Data.SqlClient.SqlException (0x80131904): A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: TCP Provider, error: 40 - Could not open a connection to SQL Server)
为了解决这个问题,在 Startup 中添加如下的代码启用 RetryOnFailure 。
services.AddDbContext<CnblogsDbContext>(options => { options.UseSqlServer(Configuration.GetConnectionString("cnblogs"), builder => { builder.EnableRetryOnFailure( maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); }); });
但是测试发现不起作用。
期望的测试结果是这样的:启动 asp.net core 站点 -> curl 发请求 -> 正常响应 -> 停止 SQL Server 服务器 -> curl 发请求 -> 等待 -> 30秒之后启动 SQL Server -> 正常响应。
实际的测试结果却是这样:启动 asp.net core 站点 -> curl 发请求 -> 正常响应 -> 停止 SQL Server 服务器 -> curl 发请求 -> 15秒左右出现500错误,报上面的异常。
难道这个异常不在 RetryOnFailure 的默认范围?
于是通过 errorNumbersToAdd 添加 0x80131904 错误码:
var errorNumer = 0x80131904; builder.EnableRetryOnFailure( maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: new int[] { (int)errorNumer });
却依然不起作用。
别无他法,只能硬啃 EFCore 的源代码找线索了,于是找到 SqlServerTransientExceptionDetector
public class SqlServerTransientExceptionDetector { public static bool ShouldRetryOn([NotNull] Exception ex) { if (ex is SqlException sqlException) { foreach (SqlError err in sqlException.Errors) { switch (err.Number) { case 49920: case 49919: case 49918: case 41839: case 41325: case 41305: case 41302: case 41301: case 40613: case 40501: case 40197: case 10929: case 10928: case 64: case 20: } } return false; } if (ex is TimeoutException) { return true; } return false; } }
原来是根据 SqlError.Number 来判断的, 上面的数字都这么小,看来 0x80131904 不是 SqlError.Number ,再次查看错误日志发现在 System.Data.SqlClient.SqlException (0x80131904) 之前有下面一行日志:
Error Number:2,State:0,Class:20
原来是 2 ,于是在 errorNumbersToAdd 中添加这个 error number ,问题就解决了。
services.AddDbContext<CnblogsDbContext>(options => { options.UseSqlServer(Configuration.GetConnectionString("cnblogs"), builder => { builder.EnableRetryOnFailure( maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: new int[] { 2 }); }); });