Scala zio-actors与akka-actor集成

zio-actors与akka-actor集成

zio-actors 与 akka-actor 是两种不同实现,分两种情况:

  • zio actor 发消息给 akka actor
  • akka actor 发消息给 zio actor

依赖

不包括 akka actor 和 zio-actors 依赖,只是集成所需的

"dev.zio" %% "zio-actors-akka-interop" % <VERSION>"

所需的导入如下:

import zio.actors.Actor.Stateful
import zio.actors.{ ActorSystem, ActorRef, Context, Supervisor }
import zio.actors.akka.{ AkkaTypedActor, AkkaTypedActorRefLocal }
import zio.{ IO, Runtime }

import akka.actor.typed
import akka.actor.typed.Behavior
import akka.actor.typed.scaladsl.Behaviors
import akka.actor.typed.Scheduler
import akka.util.Timeout

import scala.concurrent.duration._

本章例子的样例类:

sealed trait TypedMessage[+_]
case class PingToZio(zioReplyToActor: ActorRef[ZioMessage], msg: String) extends TypedMessage[Unit]
case class PingFromZio(zioSenderActor: ActorRef[ZioMessage]) extends TypedMessage[Unit]

sealed trait ZioMessage[+_]
case class PongFromAkka(msg: String) extends ZioMessage[Unit]
case class Ping(akkaActor: AkkaTypedActorRefLocal[TypedMessage]) extends ZioMessage[Unit]

基本的 actors 使用需要定义一个Stateful来描述 actor 的行为。然后通过监督方式、初始状态和提到的Stateful来完成 actor 的创建。

在 zio actor 与 akka actor 通信

zio actor Stateful 实现如下:

val handler = new Stateful[Any, String, ZioMessage] {
  override def receive[A](state: String, msg: ZioMessage[A], context: Context): IO[Throwable, (String, A)] =
    msg match {             
      case PongFromAkka(msg) => IO.succeed((msg, ())) // zio actor接收akka actor的消息
      case Ping(akkaActor) => // akkaActor的类型是AkkaTypedActorRefLocal,而不是 akka actor 的ActorRef
              for {
                 self <- context.self[ZioMessage]
                 _    <- akkaActor ! PingFromZio(self) // 把self带上用于收回复
               } yield (state, ())
      case _=> IO.fail(new Exception("fail"))
    }
}

在 akka actor 中 发送消息到 zio actor

akka actor,需要一个行为(behavior)来定义要处理的消息,在这种情况下向 zio actor 发送和接收消息:

object TestBehavior {
    lazy val zioRuntime = Runtime.default
    def apply(): Behavior[TypedMessage[_]] =
      Behaviors.receiveMessage { message =>
        message match {                  
          case PingToZio(zioReplyToActor, msgToZio) => 
            // 在akka 中发消息,需要unsafeRun执行ZIO effect
            zioRuntime.unsafeRun(zioReplyToActor ! PongFromAkka(msgToZio)) 
          case PingFromZio(zioSenderActor)          => 
            zioRuntime.unsafeRun(zioSenderActor ! PongFromAkka("Pong from Akka"))
        }
        Behaviors.same
      }
  } 

主程序

我们已经准备好开始从 zio 向 akka 发送消息,或者通过fire-and-forget交互模式反过来,但首先我们需要用创建的 akka ActorRef(或ActorSystem)创建一个 ZIO 值,可以使用AkkaTypedActor.make

for {
  akkaSystem <- IO(typed.ActorSystem(TestBehavior(), "akkaSystem")) // akka actor 的 ActorSystem
  system     <- ActorSystem("zioSystem") // zio actor 的 ActorSystem
  akkaActor  <- AkkaTypedActor.make(akkaSystem) // 使用interop提供的AkkaTypedActor,对akka actor做一次包装
  zioActor   <- system.make("zioActor", Supervisor.none, "", handler) // 使用zio的ActorSystem创建zio actor
  _          <- akkaActor ! PingToZio(zioActor, "Ping from Akka")  // 发消息给akka actor,并带上zioActor,用于接收回复
  _          <- zioActor ! Ping(akkaActor) // 发消息给zio actor,并带上akkaActor,用于接收回复
} yield ()

zim 中应用

zim 不涉及到2种 actor 通信,websocket 使用的是 akka actor,而在定时任务处使用了 zio actor,实现一个基于 zio actor 的定时器如下:

object ScheduleStateful {

  val stateful: Stateful[Any, Unit, Command] = new Stateful[Any, Unit, Command] {

    override def receive[A](state: Unit, msg: Command[A], context: Context): UIO[(Unit, A)] = {
      val taskIO = msg match {
        case OnlineUserMessage(descr) =>
          WsService.getConnections.flatMap { i =>
            LogUtil.debug(s"${descr.getOrElse("receive")} Total online user => $i")
          }
        case _ => UIO.unit
      }
      // 这里返回的类型按照zio-actors官网的写法返回(Unit, A) idea会提示语法错误,目前还不知道是谁的问题,只能强制转换了
      taskIO.foldM(
        e => LogUtil.error(s"ScheduleStateful $e").as(() -> "".asInstanceOf[A]),
        _ => ZIO.succeed(() -> "".asInstanceOf[A])
      )
    }
  }
}

根据Stateful创建 actor

  lazy val scheduleActor: ZIO[Any, Throwable, ActorRef[protocol.Command]] =
    actorSystem
      .flatMap(_.make(Constants.SCHEDULE_JOB_ACTOR, zio.actors.Supervisor.none, (), ScheduleStateful.stateful))
      .provideLayer(Clock.live ++ InfrastructureConfiguration.live)

启动 actor,只需要像使用普通方法一样调用该方法即可:

  def scheduleTask: Task[Unit] = {
    val task = ZioActorSystemConfiguration.scheduleActor
      .flatMap(f => f ! OnlineUserMessage(Some("scheduleTask"))) repeat Schedule.secondOfMinute(0)
	// secondOfMinute类似于Cron的时间表,每分钟的指定秒数重复出现。此处为0秒
    task
      .foldM(
        e => LogUtil.error(s"error => $e").unit,
        _ => UIO.unit
      )
      .provideLayer(Clock.live)
  }

zim 是一个web端即时通讯系统,使用scala2语言,基于zio、tapir、akka,scallikejdbc等库实现。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值