阿里雲服務器有時會出現短暫的連接不上數據庫服務器(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 }); }); });
