当我初接触akka-cluster的时候,我有一个梦想,希望能充分利用actor自由分布、独立运行的特性实现某种分布式程序。这种程序的计算任务可以进行人为的分割后再把细分的任务分派给分布在多个服务器上的actor上去运算。这些服务器都处于同一集群环境里,它们都是akka-cluster中的节点(node)。akka-cluster的节点数量只需要通过系统配置方式按照计算能力要求随意增减,在集群上运行的分布式程序可以在不修改软件的情况下自动调整actors在各节点上的分布,重新平衡程序运算负载,不受任何影响继续运行。

在前面akka系列的博客里也介绍了一些akka-cluster的情况,最近在“集群环境内编程模式(PICE)”的专题系列里又讨论了如何在集群环境里通过protobuf-gRPC把多个不同类型的数据库服务集成起来。因为集群中的数据库服务是用akka-stream连接的,我们把程序与数据一起作为stream的流元素用Flow发送给相应的数据库服务进行处理。这时一个想法就产生了:当数据库服务接收了一项服务要求后(假设数据处理多是耗时、耗资源的任务)可以对任务进行分割,然后把这些小任务再分发给所属集群内的多个节点上去运算,再按计算要求收集,汇总结果。那么如果能按用户数量和运算任务的规模来任意添减服务器数量就能满足任何规模的运算需求了。最重要的是这种集群节点规模调整必须是某种配置方式,即通过修改配置文件,但不需要修改软件代码。这些需要恰恰又是akka-cluster的特殊能力。所以决定开个akka-cluster的专题系列来具体讨论集群环境下的分布式软件开发模式。

akka-cluster提供的以下几种方式比较符合我们的要求:

1、distributed pub/sub - 分布式发布订阅模式

2、cluster-singleton - 单例actor模式

3、cluster-load-balancing - 集群负载均衡模式

4、cluster-sharding - 集群分片模式

在这个系列下面的博客里我们会逐个模式讨论它们在具体编程的使用细节。但首先探讨一下如何通过配置文件来定义akka-cluster节点,实现集群规模调整。

集群节点(cluster node)的生命周期会经历以下阶段:

Joining->Up,Leaving->Exiting,Exiting->Removed,Unreachable->Up,Unreachable->Down,Down->Removed

下面我们就用运行在不同集群节点的actor,通过订阅系统的集群成员状态转换消息来观察每个节点的状态转变:

class EventListener extends Actor with ActorLogging {
import EventListner._ val cluster = Cluster(context.system) override def preStart(): Unit = {
cluster.subscribe(subscriber = self,initialStateMode = InitialStateAsEvents
,classOf[MemberEvent],classOf[UnreachableMember])
super.preStart()
}
override def postStop(): Unit = {
cluster.unsubscribe(self)
super.postStop()
} override def receive: Receive = {
case MemberJoined(member) =>
log.info("{} is JOINING...", member.address)
case MemberUp(member) =>
log.info("{} is UP!", member.address)
case MemberWeaklyUp(member) =>
log.info("{} is weakly UP!", member.address)
case MemberLeft(member) =>
log.info("{} is LEAVING...", member.address)
case MemberExited(member) =>
log.info("{} is EXITING...", member.address)
case MemberRemoved(member, prevStatus) =>
log.info("{} is REMOVED! from state {}", member.address, prevStatus)
case UnreachableMember(member) =>
log.info("{} is UNREACHABLE!", member.address)
case ReachableMember(member) =>
log.info("{} is REACHABLE!", member.address)
case UnreachableDataCenter(datacenter) =>
log.info("Data Center {} is UNREACHABLE!", datacenter)
case ReachableDataCenter(datacenter) =>
log.info("Data Center {} is REACHABLE!", datacenter)
case Leave =>
cluster.leave(cluster.selfAddress)
log.info("{} is asked to leave cluster.",cluster.selfAddress)
case Down =>
cluster.down(cluster.selfAddress)
log.info("{} is asked to shutdown cluster.",cluster.selfAddress)
} }

Leave和Down是自定义消息类型:

object EventListner {
trait Messages {}
case object Leave extends Messages
case object Down extends Messages
def props = Props(new EventListener)
...
}

akka-cluster最基本的配置文件内容如下:

akka {
actor {
provider = "cluster"
}
remote {
log-remote-lifecycle-events = off
netty.tcp {
hostname = "localhost"
port =
}
}
cluster {
seed-nodes = [
"akka.tcp://ClusterSystem@localhost:2551"]
}
}

实际上hostname,port,seed-nodes这些参数都可以在程序里配置,如果有需要,我们只要在配置文件里注明这是一个集群模式的程序就行了,其它参数放到程序里去定义:

akka {
actor {
provider = "cluster"
}
}

然后我们可以在程序里配置缺失的集群参数:

object EventListner {
trait Messages {}
case object Leave extends Messages
case object Down extends Messages
def props = Props(new EventListener) def create(host: String = "localhost", port: Int = , seednode: String = "") = {
var config = ConfigFactory.parseString(s"akka.remote.netty.tcp.hostname=${host}")
.withFallback(ConfigFactory.parseString(s"akka.remote.netty.tcp.port=${port}"))
if (seednode.length > ) {
val strConfig = "akka.cluster.seed-nodes=[\"" + seednode + "\"]"
val configSeed = ConfigFactory.parseString(strConfig)
config = config.withFallback(configSeed)
}
config = config.withFallback(ConfigFactory.load("akka-cluster-config"))
val clusterSystem = ActorSystem(name="ClusterSystem",config=config)
clusterSystem.actorOf(Props[EventListener])
} }

在create函数里ConfigFactory.parseString可以把一个字符串转换成集群配置参数,多个参数可以用withFallback来补充定义。

以下是EventListener的测试程序:

import EventListner._
object EventDemo extends App { val listner1 = EventListner.create(port = ) //seed node
scala.io.StdIn.readLine()
val listner2 = EventListner.create() //port=0 random port
scala.io.StdIn.readLine()
val listner3 = EventListner.create() //port=0 random port scala.io.StdIn.readLine() listner3 ! Leave
scala.io.StdIn.readLine() listner2 ! Down
scala.io.StdIn.readLine() listner1 ! Leave
scala.io.StdIn.readLine() }

第一个运行的必须是seednode,因为每个节点在启动时都需要连接seednode。下面是每个阶段的输出结果:

[INFO] [// ::40.888] [main] [akka.cluster.Cluster(akka://ClusterSystem)] Cluster Node [akka.tcp://ClusterSystem@localhost:2551] - Started up successfully
[INFO] [// ::40.931] [ClusterSystem-akka.actor.default-dispatcher-] [akka.cluster.Cluster(akka://ClusterSystem)] Cluster Node [akka.tcp://ClusterSystem@localhost:2551] - Node [akka.tcp://ClusterSystem@localhost:2551] is JOINING itself (with roles [dc-default]) and forming new cluster
[INFO] [// ::40.933] [ClusterSystem-akka.actor.default-dispatcher-] [akka.cluster.Cluster(akka://ClusterSystem)] Cluster Node [akka.tcp://ClusterSystem@localhost:2551] - Cluster Node [akka.tcp://ClusterSystem@localhost:2551] dc [default] is the new leader
[INFO] [// ::40.943] [ClusterSystem-akka.actor.default-dispatcher-] [akka.cluster.Cluster(akka://ClusterSystem)] Cluster Node [akka.tcp://ClusterSystem@localhost:2551] - Leader is moving node [akka.tcp://ClusterSystem@localhost:2551] to [Up]
[INFO] [// ::41.037] [ClusterSystem-akka.actor.default-dispatcher-] [akka.tcp://ClusterSystem@localhost:2551/user/$a] akka.tcp://ClusterSystem@localhost:2551 is UP!
[INFO] [// ::47.363] [ClusterSystem-akka.actor.default-dispatcher-] [akka.tcp://ClusterSystem@localhost:51679/user/$a] akka.tcp://ClusterSystem@localhost:51679 is JOINING...
[INFO] [// ::47.930] [ClusterSystem-akka.actor.default-dispatcher-] [akka.cluster.Cluster(akka://ClusterSystem)] Cluster Node [akka.tcp://ClusterSystem@localhost:2551] - Leader is moving node [akka.tcp://ClusterSystem@localhost:51679] to [Up]
[INFO] [// ::47.931] [ClusterSystem-akka.actor.default-dispatcher-] [akka.tcp://ClusterSystem@localhost:2551/user/$a] akka.tcp://ClusterSystem@localhost:51679 is UP!
[INFO] [// ::48.109] [ClusterSystem-akka.actor.default-dispatcher-] [akka.tcp://ClusterSystem@localhost:51679/user/$a] akka.tcp://ClusterSystem@localhost:51679 is UP!
[INFO] [// ::53.765] [ClusterSystem-akka.actor.default-dispatcher-] [akka.tcp://ClusterSystem@localhost:51681/user/$a] akka.tcp://ClusterSystem@localhost:51681 is JOINING...
[INFO] [// ::53.930] [ClusterSystem-akka.actor.default-dispatcher-] [akka.tcp://ClusterSystem@localhost:51679/user/$a] akka.tcp://ClusterSystem@localhost:51681 is JOINING...
[INFO] [// ::54.929] [ClusterSystem-akka.actor.default-dispatcher-] [akka.cluster.Cluster(akka://ClusterSystem)] Cluster Node [akka.tcp://ClusterSystem@localhost:2551] - Leader is moving node [akka.tcp://ClusterSystem@localhost:51681] to [Up]
[INFO] [// ::54.929] [ClusterSystem-akka.actor.default-dispatcher-] [akka.tcp://ClusterSystem@localhost:2551/user/$a] akka.tcp://ClusterSystem@localhost:51681 is UP!
[INFO] [// ::00.806] [ClusterSystem-akka.actor.default-dispatcher-] [akka.tcp://ClusterSystem@localhost:51681/user/$a] akka.tcp://ClusterSystem@localhost:51681 is asked to leave cluster.
[INFO] [// ::00.807] [ClusterSystem-akka.actor.default-dispatcher-] [akka.cluster.Cluster(akka://ClusterSystem)] Cluster Node [akka.tcp://ClusterSystem@localhost:51681] - Marked address [akka.tcp://ClusterSystem@localhost:51681] as [Leaving]
[INFO] [// ::00.808] [ClusterSystem-akka.actor.default-dispatcher-] [akka.tcp://ClusterSystem@localhost:51681/user/$a] akka.tcp://ClusterSystem@localhost:51681 is LEAVING...
[INFO] [// ::00.809] [ClusterSystem-akka.actor.default-dispatcher-] [akka.tcp://ClusterSystem@localhost:51679/user/$a] akka.tcp://ClusterSystem@localhost:51679 is asked to shutdown cluster.
[INFO] [// ::00.809] [ClusterSystem-akka.actor.default-dispatcher-] [akka.cluster.Cluster(akka://ClusterSystem)] Cluster Node [akka.tcp://ClusterSystem@localhost:51679] - Marking node [akka.tcp://ClusterSystem@localhost:51679] as [Down]
[INFO] [// ::00.810] [ClusterSystem-akka.actor.default-dispatcher-] [akka.tcp://ClusterSystem@localhost:2551/user/$a] akka.tcp://ClusterSystem@localhost:51681 is LEAVING...
[INFO] [// ::00.933] [ClusterSystem-akka.actor.default-dispatcher-] [akka.tcp://ClusterSystem@localhost:51679/user/$a] akka.tcp://ClusterSystem@localhost:51681 is LEAVING...
[INFO] [// ::01.101] [ClusterSystem-akka.actor.default-dispatcher-] [akka.cluster.Cluster(akka://ClusterSystem)] Cluster Node [akka.tcp://ClusterSystem@localhost:51679] - Shutting down myself
[INFO] [// ::01.102] [ClusterSystem-akka.actor.default-dispatcher-] [akka.cluster.Cluster(akka://ClusterSystem)] Cluster Node [akka.tcp://ClusterSystem@localhost:51679] - Shutting down...
[INFO] [// ::01.104] [ClusterSystem-akka.actor.default-dispatcher-] [akka.cluster.Cluster(akka://ClusterSystem)] Cluster Node [akka.tcp://ClusterSystem@localhost:51679] - Successfully shut down
[INFO] [// ::01.110] [ClusterSystem-akka.actor.default-dispatcher-] [akka.tcp://ClusterSystem@localhost:51679/user/$a] akka.tcp://ClusterSystem@localhost:2551 is REMOVED! from state Up
[INFO] [// ::01.110] [ClusterSystem-akka.actor.default-dispatcher-] [akka.tcp://ClusterSystem@localhost:51679/user/$a] akka.tcp://ClusterSystem@localhost:51679 is REMOVED! from state Down
[INFO] [// ::01.111] [ClusterSystem-akka.actor.default-dispatcher-] [akka.tcp://ClusterSystem@localhost:51679/user/$a] akka.tcp://ClusterSystem@localhost:51681 is REMOVED! from state Leaving
[INFO] [// ::02.925] [ClusterSystem-akka.actor.default-dispatcher-] [akka.cluster.Cluster(akka://ClusterSystem)] Cluster Node [akka.tcp://ClusterSystem@localhost:2551] - Leader is moving node [akka.tcp://ClusterSystem@localhost:51681] to [Exiting]
[INFO] [// ::02.926] [ClusterSystem-akka.actor.default-dispatcher-] [akka.tcp://ClusterSystem@localhost:2551/user/$a] akka.tcp://ClusterSystem@localhost:51681 is EXITING...
[INFO] [// ::02.927] [ClusterSystem-akka.actor.default-dispatcher-] [akka.cluster.Cluster(akka://ClusterSystem)] Cluster Node [akka.tcp://ClusterSystem@localhost:51681] - Exiting, starting coordinated shutdown
[INFO] [// ::02.927] [ClusterSystem-akka.actor.default-dispatcher-] [akka.tcp://ClusterSystem@localhost:51681/user/$a] akka.tcp://ClusterSystem@localhost:51681 is EXITING...
[INFO] [// ::02.934] [ClusterSystem-akka.actor.default-dispatcher-] [akka.cluster.Cluster(akka://ClusterSystem)] Cluster Node [akka.tcp://ClusterSystem@localhost:51681] - Exiting completed

Akka-Cluster(0)- 分布式应用开发的一些想法的更多相关文章

  1. Scala 的 Web 框架 Lift 开始 3.0 版本开发

    Scala 的 Web 框架 Lift 开始 3.0 版本开发 http://demo.liftweb.net/ http://liftweb.net/download Lift 框架在不断的成长和改 ...

  2. Akka Cluster简介与基本环境搭建

      akka集群是高容错.去中心化.不存在单点故障以及不存在单点瓶颈的集群.它使用gossip协议通信以及具备故障自动检测功能. Gossip收敛   集群中每一个节点被其他节点监督(默认的最大数量为 ...

  3. 《深入实践Spring Boot》阅读笔记之二:分布式应用开发

    上篇文章总结了<深入实践Spring Boot>的第一部分,这篇文章介绍第二部分:分布式应用开发,以及怎么构建一个高性能的服务平台. 主要从以下几个方面总结: Spring Boot SS ...

  4. akka cluster 初体验

    cluster 配置 akka { actor { provider = "akka.cluster.ClusterActorRefProvider" } remote { log ...

  5. asp.net 分布式应用开发

    Net Framework推出的许多新技术为上述任务的实现提供了相对简单的解决方案.其中,基于SOAP的Web Service在处理分布式应用时具有比传统的DCOM/CORBA明显的优点,结合基于We ...

  6. 高性能分布式应用开发中间件ICE介绍

    作为一个技术人员,你是否在为不断增长的数据量和日益复杂的业务逻辑而头疼不已,杂乱堆砌在一起的庞大业务让系统越来越脆弱,于是你想到了网格,想到了利用分布式来重组一个健壮的系统架构. 随后,RMI,EJB ...

  7. Akka系列(十):Akka集群之Akka Cluster

    前言........... 上一篇文章我们讲了Akka Remote,理解了Akka中的远程通信,其实Akka Cluster可以看成Akka Remote的扩展,由原来的两点变成由多点组成的通信网络 ...

  8. akka cluster sharding source code 学习 (1/5) 替身模式

    为了使一个项目支持集群,自己学习使用了 akka cluster 并在项目中实施了,从此,生活就变得有些痛苦.再配上 apache 做反向代理和负载均衡,debug 起来不要太酸爽.直到现在,我还对 ...

  9. 关于Quartus II 13.0对应开发NIOS II软件程序时报错Symbol 'NULL' could not be resolved问题的解决方法

    关于Quartus II 13.0对应开发NIOS II软件程序时报错Symbol 'NULL' could not be resolved问题的解决方法 近期在评估使用NIOS II处理器进行项目的 ...

随机推荐

  1. javascript:控制台详解

    javascript工具——浏览器控制台详解  大神这篇博客是写在2011年,主要介绍 “Firefox” 浏览器插件 “Firebug” 的操作,如今主流浏览器对控制台都已经提供了很好的支持.我自己 ...

  2. [FE] 有效开展一个前端项目1

    今天的前端如果没有用到 npm,效率是比较低的:所以要从使用的工具来讲. 1. 一切都依赖于 nodejs: 下载一个 linux 的源码包就可以开始安装了. $ wget https://nodej ...

  3. alert()、confirm()、prompt()的区别

    使用消息框 使用警告.提示和确认 可以使用警告.确认和提示消息框来获得用户的输入.这些消息框是 window 对象的接口方法.由于 window 对象位于对象层次的顶层,因此实际应用中不必使用这些消息 ...

  4. service mysqld start,Failed to start mysqld.service: Access denied

    service mysqld start 然后报: ==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-units ===Authentic ...

  5. php 二维数组按照某个键排序

    $date = array_column($arr, 'run_date'); //上面得到的结果:array(0=>'2017-11-21',1=>'2017-11-20',3=> ...

  6. Python学习—爬虫篇之破解ntml登陆问题

    之前帮公司爬取过内部的一个问题单网站,要求将每个问题单的下的附件下载下来.一开始的时候我就遇到一个破解登陆验证的大坑......      (╬ ̄皿 ̄)=○ 由于在公司使用的都是内网,代码和网站的描述 ...

  7. DRF的解析器和渲染器

    解析器 解析器的作用 解析器的作用就是服务端接收客户端传过来的数据,把数据解析成自己可以处理的数据.本质就是对请求体中的数据进行解析. 在了解解析器之前,我们要先知道Accept以及ContentTy ...

  8. centos部署redis主从

    安装环境 CentOS 6.5 .CentOS 7.4 主Redis:10.159.44.175 从Redis: 10.159.44.176.10.159.44.177 Redis下载和安装 在3台机 ...

  9. 【收藏】UICrawler

    基于 Appium 的 App UI 遍历 & Monkey 工具 (支持操作步骤回放) UICrawler https://github.com/lgxqf/UICrawler 基于Appi ...

  10. python 文件读写方式

    一.普通文件读写方式 1.读取文件信息: with open('/path/to/file', 'r') as f: content = f.read() 2.写入文件中: with open('/U ...