在Akka中实现基于内容的路由器模式

时间:2015-08-07 11:20:45

标签: java routing apache-camel akka actor

我正在尝试在我的Akka演员系统中实现content-based router,根据this documentConsistentHashingRouter是要走的路。阅读完官方文档后,我仍然对如何使用这个内置的哈希路由器感到困惑。我认为这是因为路由器本身是基于散列/密钥的,Akka doc作者选择使用的示例是涉及基于键值的缓存的场景......所以我无法分辨缓存使用哪些密钥以及哪些密钥使用路由器使用它!

我们举一个简单的例子。假设我们有以下消息:

interface Notification {
    // Doesn’t matter what’s here.
}

// Will eventually be emailed to someone.
class EmailNotification implements Notification {
    // Doesn’t matter what’s here.
}

// Will eventually be sent to some XMPP client and on to a chatroom somewhere.
class ChatOpsNotifications implements Notification {
    // Doesn’t matter what’s here.
}

等。从理论上讲,我们可能会有20 Notification次。我希望能够在运行时向演员/路由器发送Notification并让路由器将其路由到正确的NotificationPubisher

interface NotificationPublisher<NOTIFICATION implements Notification> {
    void send(NOTIFICATION notification)
}

class EmailNotificationPublisher extends UntypedActor implements NotificationPubisher<EmailNotification> {
    @Override
    void onReceive(Object message) {
        if(message instanceof EmailNotification) {
            send(message as EmailNotification)
        }
    }

    @Override
    void send(EmailNotification notification) {
        // Use Java Mail, etc.
    }
}

class ChatOpsNotificationPublisher extends UntypedActor implements NotificationPubisher<ChatOpsNotification> {
    @Override
    void onReceive(Object message) {
        if(message instanceof ChatOpsNotification) {
            send(message as ChatOpsNotification)
        }
    }

    @Override
    void send(ChatOpsNotification notification) {
        // Use XMPP/Jabber client, etc.
    }
}

现在我可以自己做这个路由,手动:

class ReinventingTheWheelRouter extends UntypedActor {
    // Inject these via constructor
    ActorRef emailNotificationPublisher
    ActorRef chatOpsNotificationPublisher
    // ...20 more publishers, etc.

    @Override
    void onReceive(Object message) {
        ActorRef publisher
        if(message instanceof EmailNotification) {
            publisher = emailNotificationPublisher
        } else if(message instanceof ChatOpsNotification) {
            publisher = chatOpsNotificationPublisher
        } else if(...) { ... } // 20 more publishers, etc.

        publisher.tell(message, self)
    }
}

或者我可以使用Akka-Camel module来定义基于Camel的路由器并将Notifications发送到Camel路由器,但似乎Akka aready已经建立了 - 在解决方案中,为什么不使用它呢?我只是想弄清楚如何将Cache示例从那些Akka文档转换为我的Notification示例。 ConsistentHashingRouter中“关键”的目的是什么?代码看起来会是什么样的呢?

当然,我很感激能帮助我解决这个问题的任何答案,但如果可能的话,我会非常喜欢基于Java的代码片段。 Scala看起来像是象形文字。

我同意Custom RouterConsistentHashingRouter更合适。在阅读自定义路由器上的文档后,似乎我会:

  1. 创建GroupBase impl并直接向其发送消息(notificationGroup.tell(notification, self));然后
  2. GroupBase impl,比如,NotificationGroup会提供一个Router实例,该实例是使用我的自定义RoutingLogic impl注入的
  3. NotificationGroup收到消息时,它会执行我的自定义RoutingLogic#select方法,该方法确定将Routee(我假定某种类型的演员?)发送给
  4. 如果这是正确的(如果我错了请纠正我),那么路由选择魔法会在这里发生:

    class MessageBasedRoutingLogic implements RoutingLogic {
        @Override
        Routee select(Object message, IndexedSeq<Routee> candidates) {
            // How can I query the Routee interface and deterine whether the message at-hand is in fact
            // appropriate to be routed to the candidate?
            //
            // For instance I'd like to say "If message is an instance of
            // an EmailNotification, send it to EmailNotificationPublisher."
            //
            // How do I do this here?!?
            if(message instanceof EmailNotification) {
                // Need to find the candidate/Routee that is
                // the EmailNotificationPublisher, but how?!?
            }
        }
    }
    

    但是你可以看到我有一些心理实施障碍要跨越。 Routee接口并没有真正给我任何我可以智能地用来决定特定Routee(候选)对于手头的消息是否正确的信息。

    所以我问:(1)如何将消息映射到Routees(有效地执行路由选择/逻辑)? (2)如何首先将我的发布者添加为路由? (3)我的NotificationPublisher impls仍然需要延长UntypedActor还是现在应该实施Routee

1 个答案:

答案 0 :(得分:2)

这是Scala中的一个简单的小A / B路由器。我希望这有帮助,即使你想要一个基于Java的答案。首先是路由逻辑:

class ABRoutingLogic(a:ActorRef, b:ActorRef) extends RoutingLogic{
  val aRoutee = ActorRefRoutee(a)
  val bRoutee = ActorRefRoutee(b)

  def select(msg:Any, routees:immutable.IndexedSeq[Routee]):Routee = {
    msg match{
      case "A" => aRoutee
      case _ => bRoutee
    }
  }
}

这里的关键是我在构造函数中传入我的ab actor refs,然后那些是我在select方法中路由到的那些。然后,Group用于此逻辑:

case class ABRoutingGroup(a:ActorRef, b:ActorRef) extends Group { 
  val paths = List(a.path.toString, b.path.toString)

  override def createRouter(system: ActorSystem): Router =
    new Router(new ABRoutingLogic(a, b))

  val routerDispatcher: String = Dispatchers.DefaultDispatcherId
}

在这里同样的事情,我正在通过构造函数创建我想要路由到的actor。现在是一个简单的actor类,可以充当ab

class PrintingActor(letter:String) extends Actor{
  def receive = {
    case msg => println(s"I am $letter and I received letter $msg") 
  }
}

我将创建两个这样的实例,每个实例都有不同的字母赋值,因此我们可以验证正确的实例是否按路由逻辑获取了正确的消息。最后,一些测试代码:

object RoutingTest extends App{
  val system = ActorSystem()
  val a = system.actorOf(Props(classOf[PrintingActor], "A"))
  val b = system.actorOf(Props(classOf[PrintingActor], "B"))
  val router = system.actorOf(Props.empty.withRouter(ABRoutingGroup(a,b)))

  router ! "A"
  router ! "B"

}

如果你跑了这个,你会看到:

I am A and I received letter A
I am B and I received letter B

这是一个非常简单的例子,但它显示了一种方法来做你想做的事情。我希望您可以将此代码桥接到Java中并使用它来解决您的问题。