[英]Controlling the concurrency of HTTP requests using Python's asyncio.Semaphore
[英]What is equivalent of using Python's Asyncio for generating HTTP requests in Scala?
我目前正在尝试使用Scala构建异步客户端数据获取器(通过 HTTP)。 获取的数据将被解析,并插入到数据库中,但这超出了这个问题的 scope。
主要目标是:
为了更好地了解我正在尝试做的事情,我在 Python 中编写了一些代码来帮助更好地表达我的问题。
我首先定义一个 function 来创建异步 http 请求(我使用aiohttp因为我发现它表达了我的意图):
from aiohttp import ClientResponse, ClientSession, ClientError, http_exceptions
async def fetch_data(url: str, method: str, session: ClientSession, **kwargs) -> dict:
"""
Request wrapper that fetches data. Passes kwargs to session.
Args:
:param url: api endpoint used (eg. https://foo.bar.com/api/v3/baz)
:param method: HTTP method used (eg. GET, POST...etc.)
:param session: aiohttp.ClientSession object. The main entry point for all client API operations.
Kwargs:
___ : Includes other KWARGS that may be passed to a session (eg. Headers, SSL...etc.)
Returns:
Dict
"""
response: ClientResponse = await session.request(url=url, method=method, **kwargs)
response.raise_for_status() # Raises an aiohttp.ClientResponseError if the response status is 400 or higher
logger.info("Got response [%s] for request: %s", response.status, url)
data: dict = await response.json()
return data
稍后我会使用这个 function 来检索数据并使用它进行实际工作(注意 TCPConnector 的限制,它限制了同时连接的总数):
H = {"Authorization": "orgId={org_id}".format(org_id=config.account_id())}
C = config.cert_dir()
async def main():
sslcontext = ssl.create_default_context(cafile=None)
sslcontext.load_cert_chain(certfile=C + "Admin_API_access.pem", keyfile=C + "Admin_API_access.key")
connector = aiohttp.TCPConnector(limit=0, ssl=sslcontext) # limit=0 can be used to reduce the number of async requests
async with aiohttp.ClientSession(connector=connector) as context:
# Call API and do something with the data (eg. write to a file)
根据我的理解(也让我感到困惑),有几种方法可以在 Scala 中完成此操作。什么是上述 Scala 中的 Python 代码的等价物,可以在不阻塞的情况下生成 HTTP 请求。 由于 Asyncio 是单线程的,我并不是特别在寻找单线程解决方案,而是寻找异步解决方案。
最后,我不是在找人为我编写代码,而是在寻求对我可以 go 提供相同功能的潜在解决方案的概念性理解。
从根本上说,在 Scala 中,异步由Future
表示,可以将其视为单个 object 的包装器:
可以注册回调函数,它会在Future
完成后的某个时刻对它进行操作; 也可以以一种非常类似于集合的方式处理Future
,使用map
、 flatMap
等来转换它(在幕后,这些操作注册一个回调并返回一个由回调完成的新Future
:使用map
、 flatMap
, recover
and friends 通常比手动回调注册更可取)。
这是一个非常通用的 API 并且有很多很多方法可以实现它。 一般来说,如果一个 Scala 库返回Future
s,它支持开箱即用的异步操作。 不乏会产生 HTTP 请求的Future
的库,包括但绝对不限于:
选择这样一个库基本上是一个品味问题:正在使用的其他库可能会引导您找到一个特定的库(例如,如果您正在使用其他 Akka 库,使用 Akka HTTP 可能是有意义的),您可能会找到一个库或另一个更好地支持特定的用例。
另外,如果一个特定的库没有给你一个Future
,你可以很容易地包装它:
import some.blocking.http.request.library
import scala.concurrent.ExecutionContext
def asyncRequestUrl(url: String)(implicit ectx: ExecutionContext): Future[Response] =
Future {
library.request(url)
}
scala.concurrent.ExecutionContext
表示一个线程池,其中积压了要在池中执行的任务。 Future { code... }
(技术上Future.apply
)添加一个任务来执行code...
并使用执行结果完成未来(立即返回)。 请注意,由于线程池中的线程数几乎总是有上限,因此只要池中的所有线程都忙于其他任务,任务就会一直处于“已调度但未执行”状态 state。
如果调用者不提供ExecutionContext
,编译器会将此标记为错误,并可能建议使用scala.concurrent.ExecutionContext.Implicits.global
。 在开发/测试环境中使用此ExecutionContext
可能没问题,但几乎肯定会希望创建一个线程数多于默认全局上下文的自定义ExecutionContext
,至少要发出 HTTP 次请求。 使用像我上面列出的异步设计库在选择合适的线程池方面可能会“正常工作”(如果发现它不“正常工作”,则可能是可配置的)。
如果一个人在面向 FP 的代码库中,可能会想要使用类似http4s的东西,它使用与“vanilla”Scala 略有不同的 model 来实现异步(例如 Cats Effect 的ConcurrentEffect
)。 如果愿意,有多种方法可以使其与 vanilla Scala 一起使用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.