阿里雲伺服器有時會出現短暫的連線不上資料庫伺服器(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 }); }); });