【问题标题】:Implementing retry logic for deadlock exceptions实现死锁异常的重试逻辑
【发布时间】:2012-10-20 23:49:30
【问题描述】:

我已经实现了一个通用存储库,想知道是否有一种聪明的方法可以在死锁异常的情况下实现重试逻辑?

所有存储库方法的方法应该相同。那么,无论如何我可以避免在每个方法中都写“try/catch - call method again with retry-count”吗?

欢迎提出任何建议。

一些我的存储库代码:

public class GenericRepository : IRepository
{
    private ObjectContext _context;

    public List<TEntity> ExecuteStoreQuery<TEntity>(string commandText, params object[] parameters) where TEntity : class
    {
        List<TEntity> myList = new List<TEntity>();

        var groupData = _context.ExecuteStoreQuery<TEntity>(commandText, parameters);

        return myList;
    }


    public IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class
    {          
        var entityName = GetEntityName<TEntity>();
        return _context.CreateQuery<TEntity>(entityName);
    }

    public IEnumerable<TEntity> GetAll<TEntity>() where TEntity : class
    {
        return GetQuery<TEntity>().AsEnumerable();
    }

编辑:

1.解决方法:

稍微修改 chris.house.00解决方案

 public static T DeadlockRetryHelper<T>(Func<T> repositoryMethod, int maxRetries)
    {
        var retryCount = 0;

        while (retryCount < maxRetries)
        {
            try
            {
                return repositoryMethod();
            }
            catch (System.Data.SqlClient.SqlException ex)
            {
                if (ex.Number == 1205)// Deadlock                         
                    retryCount++;
                else
                    throw;                   
            }
        }
        return default(T);
    }

你这样称呼它:

    public TEntity FirstOrDefault<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class
    {
        return RetryUtility.DeadlockRetryHelper<TEntity>( () =>p_FirstOrDefault<TEntity>(predicate), 3);
    }

    protected TEntity p_FirstOrDefault<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : class
    {
        return GetQuery<TEntity>().FirstOrDefault<TEntity>(predicate);
    }

【问题讨论】:

  • try 语句有什么问题?
  • 我认为 OP 希望避免在每个 repo 方法中重复 try...catch 块。
  • 确保您的存储库可以处理死锁重试可能很困难。即使您将调用存储库合并,每个存储库功能仍需要进行测试。

标签: c# entity-framework try-catch repository-pattern database-deadlocks


【解决方案1】:

这样的事情怎么样:

public T DeadlockRetryHelper<T>(Func<T> repositoryMethod, int maxRetries)
{
  int retryCount = 0;

  while (retryCount < maxRetries)
  {
    try
    {
      return repositoryMethod();
    }
    catch (SqlException e) // This example is for SQL Server, change the exception type/logic if you're using another DBMS
    {
      if (e.Number == 1205)  // SQL Server error code for deadlock
      {
        retryCount++;
      }
      else
      {
        throw;  // Not a deadlock so throw the exception
      }
      // Add some code to do whatever you want with the exception once you've exceeded the max. retries
    }
  }
}

使用上面的代码,你的重试逻辑都在这个方法中,你可以将你的存储库方法作为委托传递。

【讨论】:

  • 注意只捕获死锁异常。否则,您可能只是在重复错误,或者更糟糕的是,多次执行具有相同副作用的相同功能。
  • 如果重试次数用尽,则函数将返回而不抛出异常。
  • 您的代码很脆弱,因为 repositoryMethod() 不仅可以在自己的 sql-transaction 中运行,还可以在闭包中使用 transaction 作为参数。在这种情况下,您应该重试整个调用堆栈。
  • 您应该在重试之间稍作停顿,让锁持有者有时间释放其锁。您还应该暂停一个随机间隔,以防止相互死锁同时重试和死锁再次
  • 使用 C# 6 catch (SqlException ex) when (ex.Number == 1205)
【解决方案2】:

您是否考虑过某种形式的策略注入?作为示例,您可以使用 Unity 拦截来捕获所有存储库调用。然后你只需在拦截器中编写一次重试逻辑,而不是在每个方法中重复多次。

【讨论】:

  • 听起来就像我正在寻找的一样。你有一个例子,或链接到模拟使用?
【解决方案3】:

我知道这是一篇旧帖子,但想分享一个更新的答案。

EF 6 现在有一个内置的解决方案,您可以设置一次性执行的执行策略。您创建一个继承自 DbExectutionStrategy 并覆盖 ShouldRetryOn 虚拟方法的类。您可以创建一个包含常量字段值的异常的静态类,这些字段值是重试合格代码,并循环遍历每个异常以确定当前抛出的 sql 异常是否与合格重试代码列表匹配...

 public static class SqlRetryErrorCodes
{
    public const int TimeoutExpired = -2;
    public const int Deadlock = 1205;
    public const int CouldNotOpenConnection = 53;
    public const int TransportFail = 121;
}

public class MyCustomExecutionStrategy : DbExecutionStrategy
{
    public MyCustomExecutionStrategy(int maxRetryCount, TimeSpan maxDelay) : base(maxRetryCount, maxDelay) { }

     private readonly List<int> _errorCodesToRetry = new List<int>
    {
        SqlRetryErrorCodes.Deadlock,
        SqlRetryErrorCodes.TimeoutExpired,
        SqlRetryErrorCodes.CouldNotOpenConnection,
        SqlRetryErrorCodes.TransportFail
    };
    protected override bool ShouldRetryOn(Exception exception)
    {
        var sqlException = exception as SqlException;
        if (sqlException != null)
        {
            foreach (SqlError err in sqlException.Errors)
            {
                // Enumerate through all errors found in the exception.
                if (_errorCodesToRetry.Contains(err.Number))
                {
                    return true;
                }
            }
        }
        return false;
    }
}

最后一次,您已经设置了自定义执行策略,您只需使用设置执行策略的公共构造函数创建另一个继承自 DbConfiguration 的类:

 public class MyEfConfigurations : DbConfiguration
    {
        public MyEfConfigurations()
        {
            SetExecutionStrategy("System.Data.SqlClient",() => new MyCustomExecutionStrategy(5,TimeSpan.FromSeconds(10)));
        }
    }

【讨论】:

    【解决方案4】:

    虽然我不想担心ActionFunc 的参数数量将被淘汰,但该解决方案仍然有效。如果您使用泛型 Action 创建单个重试方法,则可以处理要在 lambda 中调用的方法的所有可变性:

    public static class RetryHelper
    {
    
        public static void DeadlockRetryHelper(Action method, int maxRetries = 3)
        {
            var retryCount = 0;
    
            while (retryCount < maxRetries)
            {
                try
                {
                    method();
                    return;
                }
                catch (System.Data.SqlClient.SqlException ex)
                {
                    if (ex.Number == 1205)// Deadlock           
                    {
                        retryCount++;
                        if (retryCount >= maxRetries)
                            throw;
                        // Wait between 1 and 5 seconds
                        Thread.Sleep(new Random().Next(1000, 5000));
                    }
                    else
                        throw;
                }
            }
    
        }
    }
    

    然后像这样使用它:

    RetryHelper.DeadlockRetryHelper(() => CopyAndInsertFile(fileModel));
    

    【讨论】:

      【解决方案5】:

      EntityFramework 6 添加ExecutionStrategy 功能。所需要的只是正确设置策略。

      我的重试策略:

      public class EFRetryPolicy : DbExecutionStrategy
      {
          public EFRetryPolicy() : base()
          {
          }
          //Keep this constructor public too in case it is needed to change defaults of exponential back off algorithm.
          public EFRetryPolicy(int maxRetryCount, TimeSpan maxDelay): base(maxRetryCount, maxDelay)
          {
          }
          protected override bool ShouldRetryOn(Exception ex)
          {
      
              bool retry = false;
      
              SqlException sqlException = ex as SqlException;
              if (sqlException != null)
              {
                  int[] errorsToRetry =
                  {
                      1205,  //Deadlock
                      -2,    //Timeout
                  };
                  if (sqlException.Errors.Cast<SqlError>().Any(x => errorsToRetry.Contains(x.Number)))
                  {
                      retry = true;
                  }
      
              }          
              return retry;
          }
      }
      

      告诉 EF 应用我的策略:

      public class EFPolicy: DbConfiguration
      {
          public EFPolicy()
          {
              SetExecutionStrategy(
                  "System.Data.SqlClient",
                  () => new EFRetryPolicy());
          }
      }
      

      来源:

      重试策略不适用于用户发起的事务(使用TransactionScope 创建的事务),如here 所述。如果使用,您将收到错误The configured execution strategy does not support user initiated transactions

      【讨论】:

        【解决方案6】:

        我在上面的帖子中使用了 MiguelSlv 提供的以下解决方案,它按预期对我有用。它简单易行。

        EntityFramework 6 添加 ExecutionStrategy 功能。所需要的只是正确设置策略。

        我的重试策略:

        public class EFRetryPolicy : DbExecutionStrategy
        {
            public EFRetryPolicy() : base()
            {
            }
            //Keep this constructor public too in case it is needed to change defaults of exponential back off algorithm.
            public EFRetryPolicy(int maxRetryCount, TimeSpan maxDelay): base(maxRetryCount, maxDelay)
            {
            }
            protected override bool ShouldRetryOn(Exception ex)
            {
        
                bool retry = false;
        
                SqlException sqlException = ex as SqlException;
                if (sqlException != null)
                {
                    int[] errorsToRetry =
                    {
                        1205,  //Deadlock
                        -2,    //Timeout
                    };
                    if (sqlException.Errors.Cast<SqlError>().Any(x => errorsToRetry.Contains(x.Number)))
                    {
                        retry = true;
                    }
                }          
                return retry;
            }
        }
        

        告诉 EF 应用此政策

        public class EFPolicy: DbConfiguration
        {
            public EFPolicy()
            {
                SetExecutionStrategy(
                    "System.Data.SqlClient",
                        () => new EFRetryPolicy());
            }
        }
        

        来源:

        使用 Entity Framework 6 实现连接弹性 微软文档 重试策略不适用于用户发起的事务(使用 TransactionScope 创建的事务),如此处所述。如果使用你会得到错误配置的执行策略不支持用户发起的事务

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-09-25
          • 2016-11-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-02-25
          相关资源
          最近更新 更多