使用akka框架和scala语言编写简单的RPC通信案例并打包到linux机器上模拟提交到集群上运行

前言

1)akka框架是一个并发的、分布式的、可伸缩性的、高性能的RPC通信框架,大数据开发框架Spark、flink底层原理中或多或少都用到了

2)scala语言真的很强大、好用、方便,结合了面向对象语言和函数式语言的特点

akka的原理图

大多数分布式框架或工具 都遵循着主从节点的架构设计,在这里我们暂不考虑高可用的模式(高可用可参考文章Zookeeper之HDFS-HA高可用模式

每个机器上的一个进程中只存在着1个通信角色对象  ActorSystem  ,也就是说 ActorSystem 对象的示例只有一个,但由它创建的Master和Worker可以有多个,是多例

1)启动master   内部定时器定期检查有无超时连接(就是在一定时间内没有向我发送心跳的worker),并将失效的进行移除

2)启动worker,跟master建立网络连接,将自己的信息(workerid,内存,内核数cpu等信息)发给master进行注册

3)master收到注册信息,将注册的信息进行保存到内存(高效),也可以持久化到磁盘或zookeeper当中(数据安全),之后向worker发送注册成功的信息

4)worker收到master发来的注册成功的信息,很高兴,并启动定时器,定期发送心跳,向master报活

代码实现

Worker类代码:

import java.util.UUID
import java.util.concurrent.TimeUnit

import akka.actor.{Actor, ActorSelection, ActorSystem, Props}
import com.typesafe.config.ConfigFactory

import scala.concurrent.duration._

/**
 * @author:tom
 * @Date:Created in 16:49 2020/12/18
 */
class Worker(val masterHost: String, val masterPort: String, val workerMemory: String, val workerCores: Int) extends Actor {


  var masterRef: ActorSelection = _

  var workerId = UUID.randomUUID().toString

  //在执行构造函数(实例化对象)之后、receive方法执行之前一定会执行一次
  override def preStart(): Unit = {

    //向master 进行注册信息
    //可以与master建立连接
    masterRef = context.actorSelection(s"akka.tcp://${Master.MASTER_ACTOR_SYSTEM_NAME}@localhost:8888/user/${Master.MASTER_ACTOR_NAME}")
    //发送消息
    masterRef ! RegisterWorker(workerId, "2048", 4)
  }

  override def receive: Receive = {
    //自己给自己发送的周期消息
    case SendHeartbeat => {
      //      if () {
      //
      //      } 向Master发送心跳
      masterRef ! HeartBeat(workerId)
    }

    case RegisteredWorker => {
      //      println("a response from master")

      //启动一个定时器
      import context.dispatcher
      context.system.scheduler.schedule(Duration(0, TimeUnit.MILLISECONDS), 10000.millisecond, self, SendHeartbeat)

    }
  }
}

object Worker {

  val WORKER_ACTOR_SYSTEM_NAME = "WorkerActorSystem"
  val WORKER_ACTOR_NAME = "WorkerActor"

  def main(args: Array[String]): Unit = {
    val masterHost = args(0)
    val masterPort = args(1)

    val workerHost = args(2)
    val workerPort = args(3)

    val workerMemory = args(4)
    val workerCores = args(5).toInt
    val configStr =
      s"""
         |akka.actor.provider = "akka.remote.RemoteActorRefProvider"
         |akka.remote.netty.tcp.hostname = $workerHost
         |akka.remote.netty.tcp.port = $workerPort
         |""".stripMargin
    val config = ConfigFactory.parseString(configStr)
    //创建workerActorSystem
    val workerActorSystem = ActorSystem.apply(WORKER_ACTOR_SYSTEM_NAME, config)
    //创建workerActor
    val workerActor = workerActorSystem.actorOf(Props(new Worker(masterHost, masterPort,
      workerMemory,
      workerCores)), WORKER_ACTOR_NAME)
  }


}

Master代码:

import akka.actor.{Actor, ActorSystem, Props}
import com.typesafe.config.ConfigFactory

import scala.collection.mutable
import scala.concurrent.duration._

/**
 * @author:tom
 * @Date:Created in 16:08 2020/12/18
 */
class Master extends Actor {

  //定义一个可变的HashMap集合用来存储worker的信息
  val idToWorker = new mutable.HashMap[String, WorkerInfo]()


  //master定期检查自己  是否有新的节点(worker出现)
  override def preStart(): Unit = {
    import context.dispatcher
    context.system.scheduler.schedule(0 millisecond, 15000.millisecond, self, CheckTimeOutWorker)
  }

  //用来接收消息
  override def receive: Receive = {

    //模式匹配
    case "hello" => {
      println("hello~")
    }
    case "hi" => {
      println("hi~")
    }

    //定时检查
    case CheckTimeOutWorker => {
      val deadWorkers = idToWorker.values.filter(w => System.currentTimeMillis() - w.lastHeartbeatTime > 30000)
      deadWorkers.foreach(dw => {
        idToWorker -= dw.workerId
      })
      println(s"current alive worker size:${idToWorker.size}")
    }

    //有worker来进行注册信息需要执行的逻辑
    case RegisterWorker(workerId, memory, cores) => {
      //      println(s"workerId:$workerId,memory:$memory,cores:$cores")

      //worker 注册成功应该执行的逻辑

      //将信息存入到内存集合当中
      val workerInfo: WorkerInfo = new WorkerInfo(workerId, memory, cores)
      idToWorker.put(workerId, workerInfo)
      //返回一个注册成功的信息
      sender() ! RegisteredWorker
    }

    //worker端发送过来的心跳信息
    case HeartBeat(workerId) => {
      //根据workerId到Map中查找对应的WorkerInfo
      if (idToWorker.contains(workerId)) {
        //如果存在 则取出信息
        val workerInfo = idToWorker(workerId)
        //更新上一次的心跳时间
        workerInfo.lastHeartbeatTime = System.currentTimeMillis()
      }
    }

  }
}

object Master {

  val MASTER_ACTOR_SYSTEM_NAME = "MasterActorSystem"
  val MASTER_ACTOR_NAME = "MasterActor"

  def main(args: Array[String]): Unit = {
    val host = args(0)
    val port = args(1)
    val configStr =
      s"""
         |akka.actor.provider = "akka.remote.RemoteActorRefProvider"
         |akka.remote.netty.tcp.hostname = $host
         |akka.remote.netty.tcp.port = $port
         |""".stripMargin
    val config = ConfigFactory.parseString(configStr)
    //创建一个ActorSystem实例(单例)
    val masterActorSystem = ActorSystem(MASTER_ACTOR_SYSTEM_NAME, config)
    //创建一个Actor
    val actor = masterActorSystem.actorOf(Props[Master], MASTER_ACTOR_NAME)
//    //自己给自己发消息
//    actor ! "hello"
  }
}

Messages类:

/**
 * @author:tom
 * @Date:Created in 15:21 2020/12/20
 */
case object CheckTimeOutWorker

case class HeartBeat(workerId: String)

//Master返回给Worker的注册成功的消息
case object RegisteredWorker

case object SendHeartbeat

case class RegisterWorker(workerId: String, memory: String, cores: Int)

在提交大数据作业到集群上运行时,通常需要将项目打包成JAR包,这里以maven为例,常用打包方式如下:

  • 不加任何插件,直接使用 mvn package 打包;
  • 使用 maven-assembly-plugin 插件;
  • 使用 maven-shade-plugin 插件;
  • 使用 maven-jar-plugin 和 maven-dependency-plugin 插件;

我们比较常用并且非常好用值得推荐的是maven-shade-plugin插件

maven-shade-plugin 功能更为强大,比如你的工程依赖很多的 JAR 包,
而被依赖的 JAR 又会依赖其他的 JAR 包,这样,当工程中依赖到不同的版本的 JAR 时,并且 JAR 中具有
相同名称的资源文件时,shade 插件会尝试将所有资源文件打包在一起时,而不是和 assembly 一样执
行覆盖操作。
通常使用  maven-shade-plugin 就能够完成大多数的打包需求,其配置简单且适用性最广,因此建议
优先使用此方式。

如果你使用到 Scala 语言进行编程,此时需要特别注意 :默认情况下 Maven 是不会把  scala 文件打
入最终的 JAR 中,需要额外添加  maven-scala-plugin 插件,常用配置如下:

<plugin>
  <groupId>org.scala-tools</groupId>
  <artifactId>maven-scala-plugin</artifactId>
  <version>2.15.1</version>
  <executions>
    <execution>
      <id>scala-compile</id>
      <goals>
        <goal>compile</goal>
      </goals>
      <configuration>
        <includes>
          <include>**/*.scala</include>
        </includes>
      </configuration>
    </execution>
    <execution>
      <id>scala-test-compile</id>
      <goals>
        <goal>testCompile</goal>
      </goals>
    </execution>
  </executions>
</plugin>

 

基本配置:

在bulid构建层下的plugins下的,新建个plugin

配置如下:

<!-- 打包jar插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.4.3</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <filters>
                                <filter>
                                    <artifact>*:*</artifact>
                                    <excludes>
                                        <exclude>META-INF/*.SF</exclude>
                                        <exclude>META-INF/*.DSA</exclude>
                                        <exclude>META-INF/*.RSA</exclude>
                                    </excludes>
                                </filter>
                            </filters>

                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                    <resource>reference.conf</resource>
                                </transformer>

                                <!-- 指定maven方法 -->
                                <!--                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">-->
                                <!--                                    <mainClass>cn._51doit.rpc.Master</mainClass>-->
                                <!--                                </transformer>-->
                            </transformers>

                        </configuration>
                    </execution>
                </executions>
            </plugin>

注意:上述注释的代码放开的话,会指定一个main方法,而本案例中暂不需要,如果需要指定一个入口,main方法,去掉上述注释即可。

之后如上图所示,点开maven项目,双击package。

接着如上图所示,打包完成,会在左边项目目录生成橙色target目录,下面有两个jar结尾的包,提交到服务器集群上运行,需要使用非original开头的jar包

下面,我们模拟集群运行,将包分别放在虚拟机的linux01  和本地win上运行

将包放在桌面上并重命名为test

打开虚拟机,连接CRT,并上传包

执行命令  传入参数 并运行  java   -cp   jar包名   主类全限定名称   参数

 注:平常我们使用的是java -jar  jar包名  类的全限定名称  参数,这次使用java -cp  是因为我们的程序中有两个main方法,需要打包是没有指定主类,可以用java -cp xxx.jar 主类名称(绝对路径)

本地win上执行   java -cp test.jar Worker 192.168.117.3  8888 192.168.117.2  9999 2048 4

查看master 的运行,发现到了worker:

终止本地worker,试试是否变为0

测试发现,果然又变为了0  

注意点:

1)每次改动代码,都需要重新打包,最好删掉之前的target目录,不然会出现多的文件包

2)传参,都需要传入准确的ip地址,不要用域名映射的linux01  或者localhost 或者127.xxxx  什么什么的

3)报错注意找提示的错误信息

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值