使用Akka Streams管理共享状态的惯用方式

时间:2018-10-30 08:51:00

标签: scala akka akka-stream

我需要根据一些黑名单过滤我的流程,该流程可以在流程执行之外进行更改。因此,我看到了两种方法可以做到这一点:

将黑名单封装在单独的服务中

    class Blacklist(init: Set[String]) {
      @volatile private var set: Set[String] = init

      def get: Set[String] = set
      def update(newSet: Set[String]): Unit = {
        set = newSet
      }
    }

    val blacklist = new Blacklist(Set.empty)

    Flow[String]
      .filterNot(blacklist.get)

在演员中加入黑名单

    class Blacklist extends Actor {
      import Blacklist._
      private var set = Set.empty[String]

      override def receive: Receive = {
        case UpdateBlacklist(newset: Set[String]) =>
          set = newset
        case GetBlacklist =>
          sender ! set
      }
    }

    object Blacklist {
      case class UpdateBlacklist(set: Set[String])
      case object GetBlacklist
    }

    val parallelism: Int = ???
    val blacklist = system.actorOf(Props(new Blacklist()))

    Flow[String]
    .mapAsync(parallelism) { str =>
      val ask = blacklist ? Blacklist.GetBlacklist
      ask.mapTo[Set[String]] map { str -> _ }
    } filterNot { case (str, exclude) =>
      exclude(str)
    }

恐怕mapAsync的参与者持有者解决方案会引入新的异步边界,从而阻止操作员进行融合。 那么,我应该选择哪一个呢?还是有一种更惯用的方式?

1 个答案:

答案 0 :(得分:0)

我认为在您的情况下,单独的服务解决方案更为合理。这是一个干净的解决方案,并且避免了参与者的开销。

但是,您的代码无法正常工作。您正在将不可变的Set传递给filterNot而不是函数。您应该考虑直接使用同步集(如in this answer所述,基于ConcurrentHashMap),或在黑名单上实现apply,如下所示:

class Blacklist(init: Set[String]) {
    val blacklist = new AtomicReference[Set[String]](init)

    def apply(s: String) = (blacklist.get) (s)

    def update(newSet: Set[String]): Unit = {
        blacklist.set(newSet)
    }
}

val blacklist = new Blacklist(Set.empty)

Flow[String].filterNot(blacklist)

我建议始终使用AtomicReference而不是@volatile,因为它更明确和易于理解。