Kafka给websocket留言

时间:2016-04-01 04:25:56

标签: websocket akka-stream akka-http

我正在尝试使用reactive-kafka,akka-http和akka-stream将一个Kafka使用者写入websocket流。

  val publisherActor = actorSystem.actorOf(CommandPublisher.props)
  val publisher = ActorPublisher[String](publisherActor)
  val commandSource = Source.fromPublisher(publisher) map toMessage
  def toMessage(c: String): Message = TextMessage.Strict(c)

  class CommandPublisher extends ActorPublisher[String] {
    override def receive = {
      case cmd: String =>
        if (isActive && totalDemand > 0)
          onNext(cmd)
    }
  }

  object CommandPublisher {
    def props: Props = Props(new CommandPublisher())
  }

  // This is the route 
  def mainFlow(): Route = {
    path("ws" / "commands" ) {
       handleWebSocketMessages(Flow.fromSinkAndSource(Sink.ignore, commandSource))
    } 
  }

从kafka使用者(此处省略),我执行publisherActor ! commandString动态添加内容到websocket。

但是,当我启动多个客户端到websocket时,我在后端遇到了这个异常:

[ERROR] [03/31/2016 21:17:10.335] [KafkaWs-akka.actor.default-dispatcher-3][akka.actor.ActorSystemImpl(KafkaWs)] WebSocket handler failed with can not subscribe the same subscriber multiple times (see reactive-streams specification, rules 1.10 and 2.12)
java.lang.IllegalStateException: can not subscribe the same subscriber multiple times (see reactive-streams specification, rules 1.10 and 2.12)
  at akka.stream.impl.ReactiveStreamsCompliance$.canNotSubscribeTheSameSubscriberMultipleTimesException(ReactiveStreamsCompliance.scala:35)
  at akka.stream.actor.ActorPublisher$class.aroundReceive(ActorPublisher.scala:295)
  ...

所有websocket客户端都不能使用一个流程吗?或者应该为每个客户创建流/发布者actor吗?

在这里,我打算向所有websocket客户端发送“当前”/“实时”通知。通知历史无关紧要,新客户需要忽略。

1 个答案:

答案 0 :(得分:4)

我很遗憾地承受了坏消息,但看起来这是akka关于的明确设计。您无法根据需要为所有客户端重用流的实例。扇出必须是明确的"作为Rx模型的结果。

我遇到的示例使用了特定于路由器的Flow

  // The flow from beginning to end to be passed into handleWebsocketMessages
  def websocketDispatchFlow(sender: String): Flow[Message, Message, Unit] =
    Flow[Message]
      // First we convert the TextMessage to a ReceivedMessage
      .collect { case TextMessage.Strict(msg) => ReceivedMessage(sender, msg) }
      // Then we send the message to the dispatch actor which fans it out
      .via(dispatchActorFlow(sender))
      // The message is converted back to a TextMessage for serialization across the socket
      .map { case ReceivedMessage(from, msg) => TextMessage.Strict(s"$from: $msg") }

  def route =
    (get & path("chat") & parameter('name)) { name =>
      handleWebsocketMessages(websocketDispatchFlow(sender = name))
    }

以下是对它的讨论:

  

这正是我在Akka Stream中所不喜欢的,这是明确的   扇出。当我从某个地方收到我想要的数据源时   进程(例如Observable或Source),我只想订阅它   我不想关心它是冷或热,还是不关心   是否已被其他订阅者订阅。这是我的河流比喻。   河流不应该关心谁和它喝酒的饮料   不应该关心河流的来源或关于其他多少   有吸烟者。我的样本,相当于一个Mathias   提供,确实共享数据源,但它只是参考   计数,你可以有2个订阅者,或者你可以有100个,不是   物。在这里,我已经得到了想象,但引用计数并没有   如果您不想丢失事件或想要确保这样做,请努力工作   流始终保持开启状态。但是你使用ConnectableObservable   它有connect(): Cancelable,而且非常适合说...   Play的LifeCycle插件。并且你可以使用的基础   如果要重复上一个,则为BehaviorSubject或ReplaySubject   新订阅者的价值观。事情就是在那之后工作,没有手册   绘制所需的连接图。   ...   ......(来自https://bionicspirit.com/blog/2015/09/06/monifu-vs-akka-streams.html)   ...   对于采用Observable并返回Observable的函数,我们   确实有升力,这是最接近东西的东西   名称,可以在Monifu中用于Subject或其他   由于LiftOperators1(和2)的可观察类型,这是什么   可以转换Observable而不会丢失它们的类型 -   这是对RxJava对lift所做的OOP改进。

     

但是,这些功能并不等同于Processor / Subject。该   区别在于Subject是消费者和消费者的同时   制片人。这意味着订户无法完全控制   当数据源启动时,数据源本质上是    hot (意味着多个订阅者共享相同的数据源)。在Rx中,如果你建模可观测量(意思是),它就完全没问题了   可观察量,每个人开始一个新的数据源   订户)。另一方面,在Rx(一般情况下),它不可行   数据源,只能订阅一次,然后才能订阅。该   Monifu中这个规则的唯一例外是由Observable制作的   GroupBy运算符,但这就像确认了   规则。

     

这意味着什么,特别是加上另一个限制   Monifu和Reactive Streams协议的合同(你应该   没有多次订阅同一个消费者),是一个   SubjectProcessor实例不可重复使用。为了这样   一个可重复使用的实例,Rx模型需要一个工厂   Processor。此外,这意味着无论何时你想使用一个   Subject / Processor,您的数据源必须自动   (可在多个订户之间共享)。