HttpClient w/HttpMessageHandler 数据并发问题

问题描述 投票:0回答:1

我有一个通过 API 调用与第三方服务集成的微服务。为了处理集成,我创建了一个类型化的

HttpClient
,其中
HttpMessageHandler
注册为
transient
服务,以根据传入请求设置标头(因为标头特定于微服务的调用者)。

流程如下:

  • 微服务在控制器中接收请求,其中传递标头(标识帐户 - 系统和租户)
  • 控制器调用类型化的 HttpClient
  • HttpClient向第三方服务发送请求。
  • HttpMessageHandler 根据接收到的请求确定帐户信息,并为传出请求设置标头。
  • 所有请求和响应,包括帐户信息,都记录在微服务的数据库中。

问题: 当我们将微服务部署到生产环境并开始处理多个客户端时,我们遇到了并发问题:记录了账户 X 的数据,但对第三方的请求是在账户 Y 下处理的。

为了调查这一点,我尝试了:

  1. 我在发送请求之前和之后立即在 HttpMessageHandler 中记录了标头信息(帐户)

  2. 我使用 Serilog 作为记录器,因此我安装了 NuGet 包 serilog-httpclient 并记录了所有内容。

  3. 我直接从控制器使用了 HttpClient 的单例实例,并在其中一个 API 中使用了它。

第一种和第二种方式总是产生相同的结果,即在向第三方发送请求之前和之后,标头是正确的,但第三方响应帐户中不存在数据的错误,而它事实上确实如此。但是,当我采用第三种解决方案时,该特定 API 中不再发生该问题,但其他 API 仍然遇到相同的问题。

我不确定问题是由于我的代码中的并发性还是其他原因造成的。日志始终显示正确的标头,但不知何故,请求到达第三方服务时帐户信息不正确。这是否仍然是我的服务中的并发问题,或者问题是否可能出在第三方服务上?

这是我的代码:

Startup.cs

        services.AddScoped<IMySession, MySession>();

        services.AddScoped<IAccountService, AccountService>();

        services.AddTransient<MyAuthenticationMessageHandler>();

        services.AddHttpClient<MyAPI>(c =>
        {
            c.BaseAddress = new Uri(configuration["MyAPIUrl"]);
        })
        .LogRequestResponse(p =>
        {
            p.LogMode = LogMode.LogAll;
            p.RequestHeaderLogMode = LogMode.LogAll;
            p.RequestBodyLogMode = LogMode.LogAll;
            p.ResponseHeaderLogMode = LogMode.LogAll;
            p.ResponseBodyLogMode = LogMode.LogAll;
            p.MaskedProperties.Clear();
        })           
        .AddHttpMessageHandler<MyAuthenticationMessageHandler>();

MySession.cs

    public class MySession : IMySession
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public MySession(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public int TenantId
    {
        get
        {
            return int.Parse(_httpContextAccessor.HttpContext.User.FindFirst(Consts.ClaimTenantId).Value);
        }
    }

    public int SystemId
    {
        get
        {
            return int.Parse(_httpContextAccessor.HttpContext.User.FindFirst(Consts.ClaimSystemId).Value);
        }
    }
}

MyController.cs

    public class MyController : MyControllerBase
{
    private readonly MyAPI _myAPI;

    public MyController(MyAPI myAPI)
        : base(serviceProvider)
    {
        _myAPI = myAPI;
    }

    [HttpGet("Status")]
    public async Task<CallResult<int?>> GetStatusAsync(int myId)
    {
        try
        {
            var response = await _myAPI.GetContractAsync();

            //rest of the code
        }
        catch (Exception ex)
        {
            return PrepareFailedCallResult<int?>(ex);
        }
    }
}

MyAuthenticationMessagehandler.cs

    public class MyAuthenticationMessageHandler : DelegatingHandler
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public MyAuthenticationMessageHandler(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var accountService = _httpContextAccessor.HttpContext.RequestServices.GetRequiredService<IAccountService>();
        var accountInfo = await accountService.GetAccountInfo();

        request.Headers.Add(APIKeys.APIId, accountInfo.APIId);
        request.Headers.Add(APIKeys.APIKey, accountInfo.APIKey);
        request.Headers.Add(APIKeys.APIAuthorizationToken, accountInfo.APIAuthorizationToken);

        return await base.SendAsync(request, cancellationToken);
    }
}

ApplicationServiceBase.cs

public abstract class ApplicationServiceBase
{
    protected readonly IMySession Session;

    public ApplicationServiceBase(IMapper mapper, IServiceProvider serviceProvider)
    {
        Session = serviceProvider.GetService<ITajeerSession>();
    }
}

AccountService.cs

    public class AccountService : ApplicationServiceBase, IAccountService
{
    private readonly IRepository<Account> _accountRepository;

    public AccountService(IRepository<Account> accountRepository, IMapper mapper, IServiceProvider serviceProvider) : base(mapper, serviceProvider)
    {
        _accountRepository = accountRepository;
    }

    public async Task<AccountInfoDto> GetAccountInfo()
    {
        Expression<Func<Account, bool>> predicate =
            account =>
                account.TenantId == Session.TenantId &&
                account.SystemId == Session.SystemId;

        var account = (await _accountRepository.All(predicate)).Select(a => _mapper.Map<AccountInfoDto>(a)).FirstOrDefault();
        if (account == null)
            throw new Exception($"Account with TenantId:{Session.TenantId}, SystemId:{Session.SystemId} is invalid");

        return account;
    }
}
c# concurrency dotnet-httpclient message-handlers
1个回答
0
投票

我编写的代码结果是没有错误的,并且永远不会导致数据并发问题。问题来自于我正在集成的服务提供商。他们承认他们遇到了并发问题,过了一段时间,他们就解决了这个问题。问题解决了。

© www.soinside.com 2019 - 2024. All rights reserved.