Playframework 分布式播放框架应用中远程参与者系统间的交叉通信

Playframework 分布式播放框架应用中远程参与者系统间的交叉通信,playframework,playframework-2.0,akka,akka-remote-actor,Playframework,Playframework 2.0,Akka,Akka Remote Actor,我正试图找出构建应用程序的最佳方法,以便以冗余方式从play framework应用程序发送推送通知 我想实现一个“休息期”,在用户修改数据30秒后向移动设备发送推送通知。如果用户在30秒内进行了另一次修改,则需要取消原始通知并替换为新通知,该通知应在最新修改后30秒发送,依此类推 问题是,我的API后端需要相互通信,以确保它们不会在30秒内发送多个通知,因为它们是负载平衡的。例如: User1进行修改并发送到API Server1。30秒后会触发通知 User1在5秒后对同一条记录进行第二次修

我正试图找出构建应用程序的最佳方法,以便以冗余方式从play framework应用程序发送推送通知

我想实现一个“休息期”,在用户修改数据30秒后向移动设备发送推送通知。如果用户在30秒内进行了另一次修改,则需要取消原始通知并替换为新通知,该通知应在最新修改后30秒发送,依此类推

问题是,我的API后端需要相互通信,以确保它们不会在30秒内发送多个通知,因为它们是负载平衡的。例如:

  • User1进行修改并发送到API Server1。30秒后会触发通知
  • User1在5秒后对同一条记录进行第二次修改,最终被路由到API Server2。另一个通知将在30秒内被触发发送,因为它不知道Server1接收到的信息
  • 这是不正确的行为-User1应该只收到一个通知,因为修改发生时数据没有“静止”30秒

    因为我对阿克卡不是特别熟悉,所以这似乎是一个很好的学习机会。看起来我可以用Akka远程处理解决这个问题

    这是我能想到的最简单的架构:

    • 在每个API实例中创建一个akka系统(“通知”),并使用路由器向每个API实例发送消息,每个API实例都有一个akka参与者(“notificationActor”)
    My application.conf如下所示:

    akka {
    
      actor {
        provider = "akka.remote.RemoteActorRefProvider"
    
        deployment {
          /router {
            router = round-robin-group
            routees.paths = [
              "akka.tcp://notifications@server1:2552/user/notificationActor",
              "akka.tcp://notifications@server2:2552/user/notificationActor"]
          }
        }
      }
      remote {
        enabled-transports = ["akka.remote.netty.tcp"]
        netty.tcp {
          hostname = "server1" // set to server1 or server 2 upon deployment
          port = 2552
        }
      }
    }
    
    // the system, instantiated once per server
    private val system = ActorSystem("notifications")
    
    // the local actor, instantiated once per server
    private val notificationActor = system.actorOf(Props[NotificationActor], name = "notificationActor")
    
    // the router, instantiated once per server
    private val router = system.actorOf(FromConfig.props(Props[NotificationActor]), name = "router")
    
    我正在这样设置系统、参与者和路由器:

    akka {
    
      actor {
        provider = "akka.remote.RemoteActorRefProvider"
    
        deployment {
          /router {
            router = round-robin-group
            routees.paths = [
              "akka.tcp://notifications@server1:2552/user/notificationActor",
              "akka.tcp://notifications@server2:2552/user/notificationActor"]
          }
        }
      }
      remote {
        enabled-transports = ["akka.remote.netty.tcp"]
        netty.tcp {
          hostname = "server1" // set to server1 or server 2 upon deployment
          port = 2552
        }
      }
    }
    
    // the system, instantiated once per server
    private val system = ActorSystem("notifications")
    
    // the local actor, instantiated once per server
    private val notificationActor = system.actorOf(Props[NotificationActor], name = "notificationActor")
    
    // the router, instantiated once per server
    private val router = system.actorOf(FromConfig.props(Props[NotificationActor]), name = "router")
    
    当我需要发送通知时,我会告诉我的演员安排时间。这样,每个系统都可以保留密钥/值对中的可取消实例,并在不同服务器上更新数据时取消通知:

    Client.scala(近似值,可能有拼写错误)

    NotificationController.scala(近似值,可能有输入错误)

    CancelNotification.scala(近似值,可能有拼写错误)

    ScheduleNotification.scala(近似值,可能有拼写错误)

    NotificationActor.scala(近似值,可能有拼写错误)

    这在本地运行得很好,但一旦我将其部署到测试环境(使用多台机器)中,所有其他消息似乎都会丢失。我假设这是因为它正试图将这些消息发送到Server2,但我在这两个应用程序的日志文件中都没有看到任何错误。我已尝试将更多日志添加到akka配置中,但在logs/application.log(默认播放框架日志)中没有看到任何额外的输出:

    为什么Server2不接收消息?我用每个实例上所有服务器的参与者实例化一个参与者系统,这样可以吗?他们应该能够相互交流吗


    而且,如果我把这件事搞得过于复杂,我愿意接受其他的解决方案。如果我能让它工作起来,这似乎是最简单的方法。

    我想我找到了答案。这个体系结构似乎可以工作,但我有两个问题,我通过在本地计算机上运行它并将server1和server2添加到配置中来解决

  • 当应用程序启动时,我没有实例化我的actor系统——我使用的是lazy-vals,这意味着actor没有准备好,因为它没有收到导致创建它的请求。换句话说,Server2上的notificationActor实例没有侦听,因为它尚未初始化

    为了解决这个问题,我将所有akka相关组件(系统、参与者和路由器)的初始化移到了GlobalSettings类的onStart方法中,以便在play应用程序启动时就可以开始了。这是首要问题

  • 我的邮件无法序列化

  • case class CancelNotification(pushNotification: Notification)
    
    case class ScheduleNotification(pushNotification: Notification)
    
    val cancellableMap: Map[String, Cancellable] = // (new concurrent hash map)
    def receive: Receive = {
      case ScheduleNotification(pushNotification) => //uses - this.context.system.scheduler.scheduleOnce and stores the key/Cancellable pair in cancellableMap
      case CancelNotification(pushNotification) => //use stored Cancellable to cancel notification, if present
      case Notification(key) => //actually send the push notification
    }
    
    akka {
    
      loglevel = "DEBUG"
      log-config-on-start = on
    
      actor {
        provider = "akka.remote.RemoteActorRefProvider"
    
        deployment {
          /router {
            router = round-robin-group
            routees.paths = [
              "akka.tcp://notifications@server1:2552/user/notificationActor",
              "akka.tcp://notifications@server2:2552/user/notificationActor"]
          }
        }
    
        debug {
          unhandled = on
        }
      }
      remote {
        log-sent-messages = on
        log-received-messages = on
        enabled-transports = ["akka.remote.netty.tcp"]
        netty.tcp {
          hostname = "server1" // set to server1 or server 2 upon deployment
          port = 2552
        }
      }
    }