c# - 如何从 Npgsql 异常判断调用是否值得重试( transient 故障策略)

标签 c# postgresql microservices npgsql polly

我正在编写一个将连接到远程 postgres 服务器的服务。 我正在寻找一种好方法来确定哪些异常应该被视为暂时的(值得重试),以及如何定义连接到远程数据库的适当策略。

该服务正在使用 Npgsql 进行数据访问。 文档说 Npgsql 将针对 sql 错误抛出 PostgresException,并针对“服务器相关问题”抛出 NpgsqlException。

到目前为止,我能想到的最好的办法是假设所有不是 PostgresExceptions 的异常都应该被视为可能是暂时的,值得重试,但是 PostgresException 意味着查询有问题并且重试不会帮助。我的这个假设是否正确?

我正在使用 Polly 创建重试和断路器策略。 因此,我的策略如下所示:

Policy.Handle<Exception>( AllButPotgresExceptions()) // if its a postgres exception we know its not going to work even with a retry, so don't
                       .WaitAndRetryAsync(new[]
                       {
                           TimeSpan.FromSeconds(1),
                           TimeSpan.FromSeconds(2),
                           TimeSpan.FromSeconds(4)
                       }, onRetry: (exception, span) => Log.Warning(exception, "Postgres Retry Failure: "))
                    .WrapAsync(
                           Policy.Handle<Exception>( AllButPotgresExceptions())
                               .AdvancedCircuitBreakerAsync(
                                   failureThreshold:.7, 
                                   samplingDuration: TimeSpan.FromSeconds(30), 
                                   minimumThroughput: 20, 
                                   durationOfBreak: TimeSpan.FromSeconds(30), 
                                   onBreak: (ex, timeSpan, context) => Log.Warning(ex, "Postres Circuit Breaker Broken: "), 
                                   onReset: (context) => Log.Warning("Postres Circuit Breaker Reset: "), 
                                   onHalfOpen: () => Log.Warning("Postres Circuit Breaker Half Open: ")
                               )));
        }
    }

    private static Func<Exception, bool> AllButPotgresExceptions()
    {
        return ex => ex.GetType() != typeof(PostgresException);
    }

是否有更好的方法来确定哪些错误可能是暂时的?

更新:

按照 Shay 的建议,我在 Npgsql 中打开了一个新问题并将我的策略更新为如下所示:

public static Policy PostresTransientFaultPolicy
    {
        get
        {
            return postgresTransientPolicy ?? (postgresTransientPolicy = Policy.Handle<Exception>( PostgresDatabaseTransientErrorDetectionStrategy())
                       .WaitAndRetryAsync(
                            retryCount: 10, 
                            sleepDurationProvider: retryAttempt => ExponentialBackoff(retryAttempt, 1.4), 
                            onRetry: (exception, span) => Log.Warning(exception, "Postgres Retry Failure: "))
                    .WrapAsync(
                           Policy.Handle<Exception>( PostgresDatabaseTransientErrorDetectionStrategy())
                               .AdvancedCircuitBreakerAsync(
                                   failureThreshold:.4, 
                                   samplingDuration: TimeSpan.FromSeconds(30), 
                                   minimumThroughput: 20, 
                                   durationOfBreak: TimeSpan.FromSeconds(30), 
                                   onBreak: (ex, timeSpan, context) => Log.Warning(ex, "Postres Circuit Breaker Broken: "), 
                                   onReset: (context) => Log.Warning("Postres Circuit Breaker Reset: "), 
                                   onHalfOpen: () => Log.Warning("Postres Circuit Breaker Half Open: ")
                               )));
        }
    }

    private static TimeSpan ExponentialBackoff(int retryAttempt, double exponent)
    {
        //TODO add random %20 variance on the exponent
        return TimeSpan.FromSeconds(Math.Pow(retryAttempt, exponent));
    }

    private static Func<Exception, bool> PostgresDatabaseTransientErrorDetectionStrategy()
    {
        return (ex) =>
        {                
            //if it is not a postgres exception we must assume it will be transient
            if (ex.GetType() != typeof(PostgresException))
                return true;

            var pgex = ex as PostgresException;
            switch (pgex.SqlState)
            {
                case "53000":   //insufficient_resources
                case "53100":   //disk_full
                case "53200":   //out_of_memory
                case "53300":   //too_many_connections
                case "53400":   //configuration_limit_exceeded
                case "57P03":   //cannot_connect_now
                case "58000":   //system_error
                case "58030":   //io_error

                //These next few I am not sure whether they should be treated as transient or not, but I am guessing so

                case "55P03":   //lock_not_available
                case "55006":   //object_in_use
                case "55000":   //object_not_in_prerequisite_state
                case "08000":   //connection_exception
                case "08003":   //connection_does_not_exist
                case "08006":   //connection_failure
                case "08001":   //sqlclient_unable_to_establish_sqlconnection
                case "08004":   //sqlserver_rejected_establishment_of_sqlconnection
                case "08007":   //transaction_resolution_unknown
                    return true;
            }

            return false;
        };
    }

最佳答案

你的方法很好。 NpgsqlException 通常意味着网络/IO 错误,尽管您可以检查内部异常并检查 IOException 以确定。

PostgreSQL报错时会抛出PostgresException,大多数情况下是查询有问题。但是,可能存在一些暂时的服务器端问题(例如连接过多),您可以检查 SQL 错误代码 - 请参阅 the PG docs .

向这些异常添加一个 IsTransient 属性可能是个好主意,在 PostgreSQL 本身内部对这些检查进行编码 - 欢迎您在 Npgsql 存储库上为此打开一个问题。

关于c# - 如何从 Npgsql 异常判断调用是否值得重试( transient 故障策略),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42822348/

相关文章:

c# - asp.net 中的用户登录页面?

postgresql - 列出与 PostgreSQL 中的模式和列的所有关系

python - Amazon Ubuntu Postgres - 无法连接到本地主机

microservices - 从多个节点订阅事件流

java - 我们可以通过 RestTemplate 调用支持 Zuul 的服务器吗

c# - Asp.Net Global.asax获取当前请求的Page对象

c# - TFS 错误 : item has pending changes but does not exist locally

javascript - 单击按钮时防止默认值不随机工作

sql - 向现有 PostgreSQL 索引添加唯一约束

architecture - 单一数据库支持的微服务器架构?