我正在使用第三方库提供解析服务(在我的情况下为用户代理解析),该服务不是线程安全的库,必须在单线程基础上运行。我想编写一个线程安全的API,该线程安全的API可以被多个线程调用以通过Futures API与之交互,因为该库可能会引入一些潜在的阻塞(IO)。我还想在必要时提供反压力,并在解析器无法赶上生产者的情况下返回失败的未来。
这实际上可能是一个通用的要求/问题,如何与不是线程安全的任何客户端/库(用户代理/地理位置解析器,redis之类的db客户端,fluentd之类的记录收集器)进行交互,并发环境。
我想出了以下公式:
将解析器封装在专用的Actor中。
创建一个akka流源队列,该队列接收包含用户代理和要完成的Promise的ParseReuqest,并使用通过mapAsync的ask模式与解析器actor进行交互。
创建另一个参与者以封装源队列。
这是要走的路吗?还有其他方法可以实现这一目标,也许更简单?也许使用图阶段?不用询问模式和更少的代码就能完成吗?
数字3中提到的actor,是因为我不确定源队列是否是线程安全的?我希望它只是在文档中说明,但事实并非如此。网络上有多个版本,有的说不是,有的说是。
源队列一旦实现,是否可以安全地从不同线程推送元素?
(该代码可能无法编译,容易出现潜在故障,并且仅适用于此问题)
class UserAgentRepo(dbFilePath: String)(implicit actorRefFactory: ActorRefFactory) {
import akka.pattern.ask
import akka.util.Timeout
import scala.concurrent.duration._
implicit val askTimeout = Timeout(5 seconds)
// API to parser - delegates the request to the back pressure actor
def parse(userAgent: String): Future[Option[UserAgentData]] = {
val p = Promise[Option[UserAgentData]]
parserBackPressureProvider ! UserAgentParseRequest(userAgent, p)
p.future
}
// Actor to provide back pressure that delegates requests to parser actor
private class ParserBackPressureProvider extends Actor {
private val parser = context.actorOf(Props[UserAgentParserActor])
val queue = Source.queue[UserAgentParseRequest](100, OverflowStrategy.dropNew)
.mapAsync(1)(request => (parser ? request.userAgent).mapTo[Option[UserAgentData]].map(_ -> request.p))
.to(Sink.foreach({
case (result, promise) => promise.success(result)
}))
.run()
override def receive: Receive = {
case request: UserAgentParseRequest => queue.offer(request).map {
case QueueOfferResult.Enqueued =>
case _ => request.p.failure(new RuntimeException("parser busy"))
}
}
}
// Actor parser
private class UserAgentParserActor extends Actor {
private val up = new UserAgentParser(dbFilePath, true, 50000)
override def receive: Receive = {
case userAgent: String =>
sender ! Try {
up.parseUa(userAgent)
}.toOption.map(UserAgentData(userAgent, _))
}
}
private case class UserAgentParseRequest(userAgent: String, p: Promise[Option[UserAgentData]])
private val parserBackPressureProvider = actorRefFactory.actorOf(Props[ParserBackPressureProvider])
}
答案 0 :(得分:0)
您有为此使用演员吗?
似乎您并不需要所有的复杂性,scala / java拥有“现成的”所有需要的工具:
class ParserFacade(parser: UserAgentParser, val capacity: Int = 100) {
private implicit val ec = ExecutionContext
.fromExecutor(
new ThreadPoolExecutor(
1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(capacity)
)
)
def parse(ua: String): Future[Option[UserAgentData]] = try {
Future(Some(UserAgentData(ua, parser.parseUa(ua)))
.recover { _ => None }
} catch {
case _: RejectedExecutionException =>
Future.failed(new RuntimeException("parser is busy"))
}
}