在ASP.NET Core应用程序中使用多个HttpClient对象

huangapple go评论130阅读模式
英文:

Using multiple HttpClient objects in an ASP.NET Core application

问题

以下是翻译好的部分:

在ASP.NET Core应用程序中,当有多个客户(公司)与之合作,并且必须为每个公司在ASP.NET应用程序中维护一个单独的具有相应客户端证书的HttpClient实例时,最佳应用程序设计将是什么?

首先,关于应用程序的一些重要事实:

  • 这是一个ASP.NET Core 3.1应用程序。
  • 使用此ASP.NET Core应用程序的每个公司需要在注册过程中首先上传自己的客户端证书。证书和相应的密码存储在数据库中。证书对于与某些外部Web服务进行通信是必要的,必须代表公司通知某些操作已由公司在所述ASP.NET Core应用程序中执行。换句话说,对于公司在ASP.NET Core应用程序中执行的某些操作,必须使用POST方法通知某个外部Web服务,并且必须处理并返回其响应。
  • 注册后,公司可以开始向所述ASP.NET Core应用程序发送请求。此ASP.NET Core应用程序应从数据库中获取相应的公司证书,创建HttpClient对象并添加证书,准备请求数据并调用外部Web服务以进行通知。然后,应处理并将来自此外部Web服务的响应作为对原始公司请求的响应返回。
  • 当然,可能会同时来自不同公司的请求,但也可能会同时来自同一公司不同办公室的请求 - 所有来自同一公司的请求都使用相同的公司客户端证书,但通知外部Web服务的请求数据(正文)将始终不同。

据我所知,为每次对外部Web服务的调用创建新的HttpClient对象并不是最佳实践。我已考虑使用HttpClientFactory,但在我的情况下,每个HttpClient对象必须包含公司的相应客户端证书。我知道ASP.NET Core支持命名HttpClient以维护每个公司的单独HttpClient,但据我所知,这些命名客户端只能在Startup类中创建。这不足以应对ASP.NET应用程序正在运行时随时可能注册新公司、公司可能在应用程序运行时上传新证书等情况。

我考虑维护一个静态列表,其中包含每个公司的一个HttpClient对象。当首次启动公司的请求时,将使用相应的客户端证书创建新的HttpClient,并将其添加到此列表中。对于来自同一公司(或办公室)的所有后续请求,将从上述静态列表中获取相应的HttpClient并重复使用它。当然,我将不得不在公司的HttpClient实例上建立一些锁定,以防止在来自同一公司的同时请求时发生故障。我对这个设计的一项担忧是,可能会有几百家公司,这个Http客户端列表可能会相当长。

您有其他的想法或建议吗?

英文:

What would be the best application design when there are multiple clients (companies) working with ASP.NET Core application and a separate instance of HttpClient with the corresponding client certificate must be maintained in ASP.NET application for each of these companies?

First, some important facts about the application:

  • It's an ASP.NET Core 3.1 application.
  • Each company using this ASP.NET Core application needs first to upload its own client certificate
    during the registration. Certificate and corresponding password are
    stored in the database. Certificate is necessary for communication
    with some external web service which must be notified on behalf of
    the company that some action was performed by the company in the
    mentioned ASP.NET Core application. In other words, for some actions
    company performs in the ASP.NET Core application, some external web
    service must be notified with POST method and corresponding body and
    its response must be processed and returned.
  • After registration,
    company can start sending requests to the mentioned ASP.NET Core
    application. This ASP.NET Core application should obtain
    corresponding company certificate from the database, create
    HttpClient object and add certificate to it, prepare request data and call external web service for
    notification. Then response from this external web service should be
    processed and returned as response to the original company request.
  • Of course, there can be simultaneous requests coming from different companies, but there can also be simultaneous requests coming for the same company from different company offices - all requests from the same company use the same company client certificate, but request data (body) to notify external web service will always be different.

As far as I know, it's not the best practice to create new HttpClient object for every call to external web service. I have considered HttpClientFactory, but in my case each HttpClient object must contain corresponding client certificate for the company. I know ASP.NET Core supports named HttpClient to maintain separate HttpClient for each company, but AFAIK this named clients can only be created in the Startup class. This won't be sufficient because new company can be registered anytime while ASP.NET app is running, company can upload new certificate while app is running rendering existing named client invalid, etc.

I am considering maintaining a static list containing one HttpClient object for each company. When first request for the company is initiated, new HttpClient is created with the corresponding client certificate and added to this list. For all subsequent requests from the same company (or office) the corresponding HttpClient is obtained from the mentioned static list and reused. Of course, I would have to establish some locking on the company's HttpClient instance so that it does not break in case of simultaneous requests from the same company. One concern I have with this design is that there might be couple of hundred companies and this list of http clients can be quite long.

Do you have any other idea or proposal?

答案1

得分: 1

> 我正在考虑维护一个包含每家公司一个 HttpClient 对象的静态列表。

在高层次上,我认为每家公司一个实例是正确的方法,但魔鬼在于(实现)细节。

> 当然,我会需要建立一些锁定。。

如果你使用了为这种用法构建的类型,就不需要了。ConcurrentDictionary,特别是它的GetOrAdd 方法,会为你提供一个按键存储的机制,其中项目是惰性创建的,而且是线程安全且无锁的。

> 我对这种设计的一个担忧是可能会有几百家公司,而这个 HttpClient 列表可能会非常长。

我不知道是否有一个具体的数量需要担心(我认为这取决于操作系统和硬件),但至少根据使 HttpClient 单例建议著名的文章,数量应该在千位数而不是百位数。

> "在生产场景中,我发现正在使用的套接字数量平均约为 4000,在峰值时会超过 5000,从而在服务器上消耗了可用资源,导致服务崩溃。在实施了此更改后,正在使用的套接字从平均超过 4000 个减少到了始终少于 400 个,通常约为 100 个。"

尽管如此,如果这是一个问题,你可以做一些事情来减轻它,就是偶尔允许这些缓存的 HttpClient 实例过期。(这也应该减轻另一个著名的问题。)不幸的是,ConcurrentDictionary 不会直接提供这个功能。MemoryCache 提供了这个功能,但不直接支持懒惰的 GetOrAdd 语义,就像 ConcurrentDictionary 那样。为了兼顾两者的最佳情况,可以查看LazyCache

英文:

> I am considering maintaining a static list containing one HttpClient object for each company.

At a high level I think single-instance-per-company is the right approach, but the devil's in the (implementation) details.

> Of course, I would have to establish some locking..

Not if you're using a type that was built for this kind of usage. ConcurrentDictionary, and specifically its GetOrAdd method, will give you a keyed storage mechanism where items are created lazily and is thread-safe and lock-free.

> One concern I have with this design is that there might be couple of hundred companies and this list of http clients can be quite long.

I don't know if there's a specific number where you need to start worrying (depends on the OS and hardware, I believe), but at least according to the article that made the HttpClient singleton advice famous, it's in the thousands, not hundreds.

> "In the production scenario I had the number of sockets was averaging around 4000, and at peak would exceed 5000, effectively crushing the available resources on the server, which then caused services to fall over. After implementing the change, the sockets in use dropped from an average of more than 4000 to being consistently less than 400, and usually around 100."

Still, if this is a concern, one thing you could do to mitigate it is allow those cached HttpClient instances to expire occasionally. (This should also mitigate that other famous problem.) Unfortunately, ConcurrentDictionary doesn't provide that capability out of the box. MemoryCache does, but doesn't directly support the lazy GetOrAdd semantics like ConcurrentDictionary. For the best of both worlds, have a look at LazyCache.

huangapple
  • 本文由 发表于 2020年1月3日 18:09:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/59576639.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定