繁体   English   中英

ASP.NET Core 中间件处理 SQL 序列化失败重试?

[英]ASP.NET Core middleware handling SQL serialization failure retries?

我目前正在尝试编写一个 ASP.NET Core API 中间件,该中间件在执行底层 MVC 操作之前打开一个 SQL 事务。 该事务使用 Serializable 隔离级别,并由底层 MVC 操作中的所有 SQL 请求使用。 然后,当 MVC 操作退出时:

  • 如果成功,中间件应该提交事务;
  • 如果它因序列化错误而失败,则中间件应重置所有内容并重试 MVC 操作(最多 N 次);
  • 否则,中间件应该回滚事务并重新抛出错误。

我最终得到的是:

public async Task InvokeAsync(HttpContext context, IDatabaseDriver databaseDriver)
{
    context.Request.EnableBuffering(REQUEST_BUFFER_THRESHOLD);

    int attempt = 0;
    while (true)
    {
        attempt++;
        try
        {
            await this._next(context);
            await databaseDriver.Commit();
            break;
        }
        catch (PostgresException ex)
        when (ex.SqlState == PostgresErrorCodes.SerializationFailure &&
              attempt <= MAX_RETRIES)
        {
            // SQL serialization failure: rollback and retry
            await databaseDriver.Rollback();
            context.Request.Body.Seek(0, SeekOrigin.Begin);
        }
        catch
        {
            // Unhandled error: rollback and throw
            await databaseDriver.Rollback();
            throw;
        }
    }
}

不幸的是,这不能正常工作,因为 SQL 序列化异常有时发生在await databaseDriver.Commit()步骤,该步骤在操作成功返回并开始写入 HTTP 响应流后执行。 这会导致响应正文中出现重复的 JSON 数据。

解决这个问题的最佳方法是什么?

  • 让 API 客户端重新执行查询(使用专用的错误代码,例如 HTTP 419),并且永远不要从中间件重新执行 ASP.NET 操作。 无论如何,使用请求缓冲是一件坏事,并且在重新运行 MVC 管道时可能会有其他不良副作用。
  • 在每个 MVC 操作返回之前提交请求事务,而不是从外部中间件提交。
  • 在全局操作过滤器中提交事务(仅在没有抛出异常的情况下),该过滤器在响应流被触摸之前运行,从而避免了之前方法中每个操作中重复的“提交”指令。
  • 以某种方式延迟 ASP.NET MVC 管道从写入响应流直到事务提交(这甚至可能吗?)。
  • 还要别的吗。

我最终通过在每次重试之前重置响应流来解决这个问题。 这通常是不可能的,因为响应流不可搜索,但您可以在中间件运行时使用临时MemoryStream替换响应流:

public async Task InvokeAsync(HttpContext context, IDatabaseDriver databaseDriver)
{
    context.Request.EnableBuffering(REQUEST_BUFFER_THRESHOLD);

    Stream originalResponseBodyStream = context.Response.Body;
    using var buffer = new MemoryStream();
    context.Response.Body = buffer;

    try
    {
        int attempt = 0;
        while (true)
        {
            attempt++;
            try
            {
                // Process request then commit transaction
                await this._next(context);
                await databaseDriver.Commit();
                break;
            }
            catch (PostgresException ex)
            when (ex.SqlState == PostgresErrorCodes.SerializationFailure &&
                  attempt <= MAX_RETRIES)
            {
                // SQL serialization failure: rollback and retry
                await databaseDriver.Rollback();
                context.Request.Body.Seek(0, SeekOrigin.Begin);
                context.Response.Body.SetLength(0);
            }
            catch
            {
                // Unhandled error: rollback and throw
                await databaseDriver.Rollback();
                throw;
            }
        }
    }
    finally
    {
        context.Response.Body = originalResponseBodyStream;
        buffer.Seek(0, SeekOrigin.Begin);
        await buffer.CopyToAsync(context.Response.Body);
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM