I am trying to setup a simple process with Spark Streaming, using Apache Bahir to connect to Akka. I tried to follow their example together with this older one. I have a simple forwarder actor
class ForwarderActor extends ActorReceiver {
def receive = {
case data: MyData => store(data)
}
}
and I create a stream with
val stream = AkkaUtils.createStream[RSVP](ssc, Props[ForwarderActor], actorName)
the configuration looks like this:
akka {
actor {
provider = "akka.remote.RemoteActorRefProvider"
}
remote {
enabled-transports = ["akka.remote.netty.tcp"]
netty.tcp {
hostname = "localhost"
port = 7777
}
}
}
and my problem is: how do I send messages to the Forwarder actor? Maybe I don't understand how Akka Remote is used in this case. When the app starts, I see a log
[akka.remote.Remoting] Remoting started; listening on addresses :[akka.tcp://test#localhost:7777]
and later on I see
[akka.remote.Remoting] Remoting now listens on addresses: [akka.tcp://streaming-actor-system-0#192.168.192.7:52369]
Which seems to remind to the description in the ScalaDoc:
/**
* A default ActorSystem creator. It will use a unique system name
* (streaming-actor-system-<spark-task-attempt-id>) to start an ActorSystem that supports remote
* communication.
*/
All in all I am not sure how I am supposed to send messages to the Forwarder actor. Thanks for any help!
Akka actors can send messages to other Akka actors running on a remote JVM. So... when the sender actor needs to know the address of the intended receiver actor.
AkkaUtil (Bahir) enables you to create a spark-stream from the messages that a ReceiverActor receives. But, where is is going to receive messages from ? Well... some remote actor. And to send messages this remote actor is going to need the address of your ReceiverActor which is running in your spark-application.
In general, you can not be too sure about the ip which will be running your spark application. So, we will make it so that the actor running with spark will tell the producer actor its reference and request it to send its things.
Just make sure that both applications are written using same version of Scala and are running the same JRE.
Now... lets first write the actor who will be the data source,
import akka.actor.{Actor, ActorRef, ActorLogging, ActorSystem, Props}
import akka.actor.Actor.Receive
import com.typesafe.config.{Config, ConfigFactory}
case class SendMeYourStringsRequest(requesterRef: ActorRef)
case class RequestedString(s: String)
class MyActor extends Actor with ActorLogging {
val theListOfMyStrings = List("one", "two", "three")
override def receive: Receive = {
case SendMeYourStringsRequest(requesterRef) => {
theListOfMyStrings.foreach(s => {
requesterRef ! RequestedString(s)
})
}
}
}
object MyApplication extends App {
val config = ConfigFactory.parseString(
"""
|akka{
| actor {
| provider = remote
| }
| remote {
| enabled-transports = ["akka.remote.netty.tcp"]
| untrusted-mode = off
| netty.tcp {
| hostname="my-ip-address"
| port=18000
| }
| }
|}
""".stripMargin
)
val actorSystem = ActorSystem("my-actor-system", config)
var myActor = actorSystem.actorOf(Props(classOf[MyActor]), "my-actor")
}
Now... lets write our simple spark app,
import akka.actor.{Actor, ActorRef, ActorLogging, ActorSystem, Props}
import akka.actor.Actor.Receive
import com.typesafe.config.{Config, ConfigFactory}
import org.apache.spark.SparkConf
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.akka.{ActorReceiver, AkkaUtils}
case class SendMeYourStringsRequest(requesterRef: ActorRef)
case class RequestedString(s: String)
class YourStringRequesterActor extends ActorReceiver {
def receive = {
case RequestedString(s) => store(s)
}
override def preStart(): Unit = {
val myActorPath = ActorPath.fromString("akka.tcp://my-actor-system#my-ip-address:18000/user/my-actor")
val myActorSelection = context.actorSelection(myActorPath)
myActorSelection ! SendMeYourStringsRequest(self)
}
}
object YourSparkApp {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setAppName("ActorWordCount")
if (!sparkConf.contains("spark.master")) {
sparkConf.setMaster("local[2]")
}
val ssc = new StreamingContext(sparkConf, Seconds(2))
val stringStream = AkkaUtils.createStream[String](
ssc,
Props(classOf[YourStringRequesterActor]),
"your-string-requester-actor"
)
stringStream.foreach(println)
}
}
Note :: Just take care of my-ip-address. If there are any other problems, please let me know in comments.
Related
I'm just messing around with some basics to learn akka remoting, and I'm running into an issue where my proxy class sends Identify messages to my backend, but never receives a response back.
I've verified that the backend receives messages sent to the ActorSelection, and I've seen a logging message where the backend actor says it will use xxx for serialization of the ActorIdentity messages. Not sure where I'm getting it wrong.
Here is my proxy class:
package remoting
import akka.actor.{Actor, ActorIdentity, ActorRef, Identify, ReceiveTimeout, Terminated}
import com.typesafe.config.ConfigFactory
import scala.concurrent.duration._
class BackendProxyActor extends Actor {
context.setReceiveTimeout(3.seconds)
val path = createPath
val backendSelection = context.actorSelection(path)
println(f"BackendSelection is $backendSelection")
override def preStart(): Unit = backendSelection ! Identify(1)
override def receive: Receive = {
case ActorIdentity(1, Some(actor)) =>
context.setReceiveTimeout(Duration.Undefined)
context.watch(actor)
context.become(active(actor))
case ActorIdentity(1, None) =>
println("Backend actor not available")
case ReceiveTimeout =>
backendSelection ! Identify(1)
case msg: Any => println(f"Received $msg while identifying backend")
}
def active(backend: ActorRef): Receive = {
case msg: Any => backend ! msg
case Terminated(backend) =>
println("backend terminated, going to identifying state")
context.setReceiveTimeout(3.seconds)
context.become(receive)
}
def createPath: String = {
val config = ConfigFactory.load("frontend").getConfig("backend")
val name = config.getString("name")
val host = config.getString("host")
val port = config.getString("port")
val system = config.getString("system")
val protocol = config.getString("protocol")
f"$protocol://$system#$host:$port/$name"
}
}
Here is my backend class:
package remoting
import akka.actor.{Actor, Identify, PoisonPill}
import com.typesafe.config.ConfigFactory
class BackendActor extends Actor {
val config = ConfigFactory.load("backend")
override def receive: Receive = {
case "stop" => self ! PoisonPill
case msg: Any => println(f"Received $msg")
}
}
My frontend class:
package remoting
import akka.actor.{Actor, Props}
class FrontendActor extends Actor {
val proxy = context.actorOf(Props[BackendProxyActor], "backendProxy")
override def receive: Receive = {
case msg: Any => proxy ! msg
}
}
and finally my App class:
package remoting
import akka.actor.{ActorSystem, Props}
import com.typesafe.config.ConfigFactory
object Main extends App {
val frontendConfig = ConfigFactory.load("frontend")
val frontend = ActorSystem("frontend", frontendConfig)
val frontendActor = frontend.actorOf(Props[FrontendActor], "FrontendActor")
(1 to 20).foreach(i => frontendActor ! f"Msg #$i")
frontendActor ! "stop"
}
My backend is being started in another process, and is run on port 2551, while my frontend is on port 2552.
I am trying to send and receive messages using akka-camel and created a sample example for producer and consumer like below :
Producer:
import akka.actor.{Actor, ActorSystem, Props}
import akka.camel.Producer
class CamelJmsProducer extends Actor with Producer {
override def endpointUri = "test"
}
object CamelJmsProducerApp extends App {
val system = ActorSystem("some-system")
val ref = system.actorOf(Props[CamelJmsProducer])
ref ! "HEY"
}
Consumer:
import akka.actor.{Actor, ActorSystem, Props}
import akka.camel.{CamelMessage, Consumer}
class CamelJmsConsumer extends Actor with Consumer {
override def receive = {
case msg: CamelMessage ⇒ println("RECEIVED >>> " + msg)
case _ ⇒ println("RECEIVED NOTHING>>> ")
}
override def endpointUri = "test"
}
object CamelJmsConsumerApp extends App {
val system = ActorSystem("some-system1")
system.actorOf(Props[CamelJmsConsumer])
}
But I am facing issue in both producer and consumer like below. What I am missing?
Producer:
java.lang.IllegalArgumentException: destination must be specified
Consumer :
Caused by: org.apache.camel.NoSuchEndpointException: No endpoint could
be found for: test, please check your classpath contains the needed
Camel component jar.
I believe you need to provide a name to the test mock endpoint, just test might not work. Can you try doing doing test:myMockEndpoint?
You can take a look here: http://camel.apache.org/components.html
For router actor with "group mode", we new a series of actors in advance, and then we will use actorOf to associate one router with remote routees.
In fact, it internal will use actorSelection, my question is: how to assure the association already finish?
For actorSelection, we can use resolveOne to make sure the selection is successful, then send message. But how about router actor? See following code, how to assure line2 associate with remote routees before send message to it, just like line line1 does?
package local
import akka.actor._
import akka.routing.RoundRobinGroup
import akka.util.Timeout
import scala.concurrent.duration._
import com.typesafe.config.ConfigFactory
import scala.util.{Failure, Success}
object Local extends App {
val config = ConfigFactory.parseString(s"akka.remote.netty.tcp.port=2251")
val system = ActorSystem("LocalSystem", config.withFallback(ConfigFactory.load()))
system.actorOf(Props[LocalActor], "LocalActor")
val config2 = ConfigFactory.parseString(s"akka.remote.netty.tcp.port=2252")
val system2 = ActorSystem("LocalSystem2", config2.withFallback(ConfigFactory.load()))
system2.actorOf(Props[LocalActor2], "LocalActor2")
}
class LocalActor extends Actor {
def receive = {
case _ => println("hi")
}
}
class LocalActor2 extends Actor {
import scala.concurrent.ExecutionContext.Implicits.global
implicit val timeout = Timeout(5 seconds)
val a = context.actorSelection("akka.tcp://LocalSystem#127.0.0.1:2251/user/LocalActor")
a.resolveOne().onComplete { // line 1
case Success(actor) => println("ready")
a ! 1
case Failure(ex) => println("not ready")
}
val paths = List("akka.tcp://LocalSystem#127.0.0.1:2251/user/LocalActor")
val b = context.actorOf(RoundRobinGroup(paths).props(), "LocalActorRouter") // line 2
b ! 1
def receive = {
case _ =>
}
}
I've followed the example for creating Web Sockets with Scala Play and Akka actors:
https://www.playframework.com/documentation/2.5.x/ScalaWebSockets#Handling-WebSockets-with-Akka-Streams-and-actors
On resume, the controller:
import play.api.mvc._
import play.api.libs.streams._
class Controller1 #Inject() (implicit system: ActorSystem, materializer: Materializer) {
def socket = WebSocket.accept[String, String] { request =>
ActorFlow.actorRef(out => MyWebSocketActor.props(out))
}
And the Actor:
import akka.actor._
object MyWebSocketActor {
def props(out: ActorRef) = Props(new MyWebSocketActor(out))
}
class MyWebSocketActor(out: ActorRef) extends Actor {
def receive = {
case msg: String =>
out ! ("I received your message: " + msg)
}
}
The actors created (one per websocket connection) are child of /user actor. I've created 3 connections and the actor created were:
/user/$b
/user/$c
/user/$d
I want to change the actors' name based in a field of the web socket message. How could i do this?.
You can set the name of the actor as follows:
Create a file BetterActorFlow.scala
package your.package
import akka.actor._
import akka.stream.scaladsl.{Keep, Sink, Source, Flow}
import akka.stream.{Materializer, OverflowStrategy}
object BetterActorFlow {
def actorRef[In, Out](props: ActorRef => Props, bufferSize: Int = 16, overflowStrategy: OverflowStrategy = OverflowStrategy.dropNew, maybeName: Option[String] = None)(implicit factory: ActorRefFactory, mat: Materializer): Flow[In, Out, _] = {
val (outActor, publisher) = Source.actorRef[Out](bufferSize, overflowStrategy)
.toMat(Sink.asPublisher(false))(Keep.both).run()
def flowActorProps: Props = {
Props(new Actor {
val flowActor = context.watch(context.actorOf(props(outActor), "flowActor"))
def receive = {
case Status.Success(_) | Status.Failure(_) => flowActor ! PoisonPill
case Terminated(_) => context.stop(self)
case other => flowActor ! other
}
override def supervisorStrategy = OneForOneStrategy() { case _ => SupervisorStrategy.Stop }
})
}
def actorRefForSink =
maybeName.fold(factory.actorOf(flowActorProps)) { name => factory.actorOf(flowActorProps, name) }
Flow.fromSinkAndSource(Sink.actorRef(actorRefForSink, Status.Success(())), Source.fromPublisher(publisher))
}
}
Use BetterActorFlow instead of ActorFlow:
BetterActorFlow.actorRef(out =>
ChatActor.props(out), 16, OverflowStrategy.dropNew, Some("alicebob"))
This worked for me. The created actor is at user/alicebob (use this with context.system.actorSelection("user/alicebob"))
According to the source code of ActorFlow, it is currently not possible to thange the name of the actual actor spawned for a connection (line 38):
Sink.actorRef(factory.actorOf(Props(new Actor { ... }) /*, name parameter is omitted */)
However, ActorFlow.actorRef accepts an implicit ActorRefFactory, which is implicit system: ActorSystem in all cases in your code. As we know, there are 2 most common ActorRefFactories: ActorSystem and ActorContext. You can modify your code in such a way that each time a connection is accepted another dummy actor would spawn with your preferred name (e.g. myActor1), and pass this new actor's context to ActorFlow.actorRef instead. In return, actors for connections would be named as follows:
/user/myActor1/$a
/user/myActor2/$a
etc
I created the following Akka Actor code in Scala. Code works fine when a single workerActor is created. But code silently fails when I try to create a pool of worker actors using round robin logic. Any idea how to fix this? How do I get more debug info to be printed?
import scala.collection.immutable.Map
import scala.collection.mutable.ArrayBuffer
import akka.actor.actorRef2Scala
import akka.actor.ActorSystem
import akka.actor.Props
import scala.concurrent.Await
import scala.concurrent.duration._
import akka.pattern.ask
import akka.util.Timeout
import akka.actor._
import org.junit._
import org.junit.Assert._
import messaging.actors._
import akka.routing.RoundRobinRouter
import akka.routing._
class MainEngineActorTest {
#Test
def testMainActor () = {
val _system = ActorSystem("MainEngineActor")
val master = _system.actorOf(Props[MainEngineActor], name = "EngineActor")
println ("Created Main Engine Actor")
implicit val timeout = Timeout(5 seconds)
val userID = new UserID ("test1")
println ("Sending messages")
for (i <- ( 1 to 10)) {
master ! "Hello"
master ! "World"
}
}
}
class MainEngineActor extends Actor with ActorLogging{
// works if we create only a single workerActor
//val workerActors = context.actorOf(Props[WorkerActor], name = "WorkerActors")
// Doesn't work when we create a pool of worker actors - how do we fix this?
// why doesn't this work and why aren't any error messages printed?
val workerActors = context.actorOf(RoundRobinPool(5).props(Props[WorkerActor]), name = "WorkerActors")
def receive: Receive = {
case request => {
workerActors forward request
}
}
}
class WorkerActor extends Actor {
def receive: Receive = {
case request => {
println ("RequestReceived =" + request)
}
}
}
Try creating your pool like this instead:
val workerActors = context.actorOf(Props[WorkerActor].withRouter(RoundRobinPool(5)), name = "WorkerActors")
In addition, when running this as a Junit test, the program is terminating before the child actors have a chance to receive the message. I verified this by adding a Thread.sleep(5000) after the loop that is sending the Hello and World messages to master. I then tweaked your code a little bit to use Akka's TestActorRef from akka-testkit which will force everything to use the CallingThreadDispatcher to get synchronous execution throughout the test and everything works as expected. The two lines I changed are:
implicit val _system = ActorSystem("MainEngineActor")
val master = TestActorRef(new MainEngineActor())