Akka使用Typesafe Config Library做配置,默认具备所有合理配置,作为库,这些默认配置位于各个模块的reference.conf. 如果你想调整配置,可以创建应用配置application.conf随意调整,扔到classPath即可(相同配置项自动覆盖默认库配置)。你可能要调整:
1、log level and logger backend
2、enable remoting
3、message serializers
4、definition of routers
5、tuning of dispatchers
日暮途乐,未尝不可typeSafe config保持了JSON基本的类型和树形结构,具备:完善的merge能力、力求简洁、可从所有URI(URL、文件、classPath)读取、所有配置片段(配置树节点)均作为一个独立Config、可以用Java system properties替代所有配置如java -Dmyapp.foo.bar=10(最高优先级)、可以增加你的app独立配置、支持"512k" or "10 seconds" or "yes"这样的配置并能自动转换它们、各处都支持a.b=c这样的用法、各处都支持${HOME}这样的环境变量引用。
但是typeSafe config遵循HOCON (Human-Optimized Config Object Notation),语法容忍度更高,语法比JSON还灵活,比如直接解析一段语法不严格的字符串当做配置对象都ok:
import com.typesafe.config._
stringConfig = ConfigFactory.parseString("foo.bar=10, foo.baz=12")
stringConfig 等价JSON格式:{"foo":{"bar":10,"baz":12}}
typeSafe config库引用位于maven-central,groupId是com.typesafe、artifactId是config。应用配置文件就一个application.conf(其中application叫做basename);以下API用法足够了(typeSafe config就是一个以ConfigFactory为根Config为叶节点的配置树,Config是immutable的,ConfigFactory提供了一堆方法从各种各样的URI源读取配置得到Config):
import com.typesafe.config.ConfigFactory
Config conf = ConfigFactory.load(); //ConfigFactory.load("myapp")可以指定其他basename,令应用读取myapp.{conf,json,properties}配置文件;
int bar1 = conf.getInt("foo.bar");
Config foo = conf.getConfig("foo");
int bar2 = foo.getInt("bar");
所有Config都是
ConfigFactory.load()的默认行为是加载如下配置(按优先级排列):
1、system properties (以最高优先级强势插入JVM虚机属性做配置)
2、application.conf (all resources on classpath with this name)
3、application.json (all resources on classpath with this name)
4、application.properties (all resources on classpath with thisname)
5、reference.conf (all resources on classpath with this name)
Akka遵从了typeSafe config最佳实践,在actor system具备settings对象,其config方法对外提供所有已读取解析的配置,所以在Akka app代码中可以这样读任意一个配置片段:
val appConfig: Config = system.settings.config.getConfig("app.foo")
对于Akka应用来说,ActorSystem是配置中心,它已经为你准备了system.settings.config,你可以在应用各处代码随时调用读取配置,config.root取所有配置、config.getConfig()取某段配置;实际上在创建ActorSystem时,它做了这件事:
Config conf = ConfigFactory.load()
system.settings.config = conf
核心思路
1、库或框架的默认配置应该统一位于reference.conf in their jar.
2、你的应用应该自定义配置application.conf.
3、如果要加载其他自定义的myapp配置比如一个模块,应该使用ConfigFactory.load("myapp")去读取加载自定义配置文件myapp.conf.
4、不要依赖写在代码中的配置,所有可调整配置全部放到配置文件由程序读取——开闭原则;
5、nice for humans to read, type, and maintain, with more lenient syntax——比JSON还要宽容的语法要求以及更灵活广泛的语法支持;
这样不管你是开发一个库/框架、应用或是模块,配置都有明确的去处。而且application.conf配置对库也能起作用,允许库的使用者在开发应用时,对库配置的调整。且最终用户总是能够定义所有的配置(包括所使用的库配置)
所以如果你是在开发一个库,如果库用户没有在代码中显示提供Config,那么你只要默认使用ConfigFactory.load()去加载配置就好了,比如如果你在使用Akka开发一个Akka-based library库,那么你的配置打包到库的JAR文件根目录下的reference.conf文件即可;如果你是在开发基于Akka的应用,那么应用的配置放到应用class path根目录下的application.conf配置文件。
如果你是在开发正常的应用就更简单了,你只要关注application.conf就好。不正常的应用比如说你要从远程去加载配置、或者要从应用外的磁盘路径加载,那么你得自己写代码手动构建Config;对于这些口味独特的,库也允许应用手动提供Config对象来代替默认配置,还给你提供withFallback方法可以随意merge配置树、在代码中调整多个配置对象。
typeSafe config内部使用withFallback方法来merge同文件的重复配置项、merge多个配置文件:前面的配置可以覆盖后面的相同配置项,ConfigFactory.load()也用它,大概是这样:
system properties .withFallback(application.conf) .withFallback(reference.conf)
基于typeSafe config,定位为一个库的Akka可以基于typeSafe config的标准做配置,你的应用作为Akka的使用者,也可以无障碍的做应用配置、以及调整Akka的配置。对于Akka来说,ActorSystem作为伟大的DDD工厂 + Actor托管容器 + 默认线程调度层,同时也是配置中心,ActorSystem就使用了ConfigFactory.load(),如果你没有别的独特口味,就这样。
回到开头,要自定义调整配置,你的application.conf可能会写成这样:
akka {
# Loggers to register at boot time (akka.event.Logging$DefaultLogger logs to STDOUT)
loggers = ["akka.event.slf4j.Slf4jLogger"]
# Log level used by the configured loggers (see "loggers") as soon as they have been started; before that, see "stdout-loglevel" Options: OFF, ERROR, WARNING, INFO, DEBUG
loglevel = "DEBUG"
# Log level for the very basic logger activated during ActorSystem startup. This logger prints the log messages to stdout (System.out). Options: OFF, ERROR, WARNING, INFO, DEBUG
stdout-loglevel = "DEBUG"
# Filter of log events that is used by the LoggingAdapter before publishing log events to the eventStream.
logging-filter = "akka.event.slf4j.Slf4jLoggingFilter"
actor {
provider = "cluster"
default-dispatcher {
# Throughput for default Dispatcher, set to 1 for as fair as possible
throughput = 10
}
}
remote {
# The port clients should connect to. Default is 2552.
netty.tcp.port = 4711
}
}
最后,对于应用特定设置,最佳实践是用Extention。对于一些独特口味奇门遁甲,罗列如下:
1、你非要指定其它默认配置文件而不是application.{conf,json,properties},通过JVM虚机属性指定:
1) -Dconfig.file=path/to/myapp.conf — 直接指定具体路径配置文件;
2) -Dconfig.resource=myapp.conf — 直接指定classPath路径配置文件;
3) -Dconfig.url=...myapp.conf — 指定url路径配置文件;
在myapp.conf中你还可以 include "application" 来包含application.{conf,json,properties}。如果你要更狂野一点on-the-fly,非要在代码中用System.setProperty()动态指定config.resource/config.file/config.url,那么注意ConfigFactory是有缓存的,需要在代码中ConfigFactory.invalidateCaches()动态刷新JVM虚机属性。
2、替换语法${foo.bar}需要注意,引用变量的查找依然是按照先reference.conf后application.conf,所以你不能在前者引用后者的变量,在这里也没必要了,库在reference.conf可以直接定义,库用户在application.conf可以直接覆盖。
3、一系列的withFallBack用法,gearpump用的多。
4、作为java库,typeSafe config允许null,大量获取具体配置项的方法虽然有默认值,比如getBoolean(String path, boolean fallback),但是当配置项存在但是未设置时会返回null、在scala会返回None,不检查会导致NullPointerException,一般来说,配置出错只是一个需要修正的bug,不管是代码导致的还是部署环境导致的都是如此,因此,如果一个配置项unset(存在但未设值),我们设计的Config接口只会抛出异常,fix it.!
Remoting乱入
Remoting是Akka集群内部通讯模块,少量的功能也可以给你用,一般不会单独使用。Remoting以P2P对等通讯方式链接两个节点,使得其中的actor可以无缝通讯,它有单独的jar包以及配置:
akka.actor.provider ="akka.remote.RemoteActorRefProvider"
Remoting 以 JAR artifact 包形式给出,它更像是一个内部模块module而不是库library,用户大多是通过配置来使用它。Remoting解决的问题包括:
1、如何寻址远程actor systems;
2、如何在远程actor systems中找到一个远程actor,即ActorSelection,通过它可以使用逻辑名“user/someName”去找到一个actor并得到它的actorRef;
3、如何将消息编码为bytes以便传输,其消息的wire protocol是内部私有的,可能根据AK版本和配置而不同;
4、如何管理各节点机之间的底层网络链接以及重连,自动发现崩溃的actor system或主机;
我们用得上的就俩方法:
1、Lookup : 查找远程节点上的actor:actorSelection(path)
2、Creation : 在远程节点上创建actor:actorOf(Props(...), actorName)
查找示例:val selection = context.actorSelection("akka.tcp://actorSystemName@10.0.0.1:2552/user/actorName"),其中的path通用格式是类似这样的uri:
akka.<protocol>://<actor system name>@<hostname>:<port>/<actor path>
用一个selection可以和actorRef一样发消息:selection ! "Pretty awesome feature",还可以使用Akka系统内建的Identify标识定位消息来获取到actorRef,所有actor都会对此消息应答一个ActorIdentity,里面包含自己的actorRef,第三种方式是调用selection.resolveOne方法,异步获取actorRef.
actor ref provider自适应,远程的用远程本地的自动用本地。 Aside from providing better performance, this also means that if the hostname you configure remoting to listen as cannot actually be resolved from within the very same actor system, such messages will (perhaps counterintuitively) be delivered just fine. 远程创建是配置好的,只要是actorName这个名字的actor的创建就会远程进行。实际上要创建的actor系统只是发一个创建请求给另一个actor系统,后者真正创建actor、前者指定的Props包含在创建请求中发送出去,也就是说Props需要序列化,所以工厂方法要写在伴生对象中,而不要写成内部类,内部类用到的对象都是按引用的,不可序列化. 所有远程Props可以测试:akka.actor.serialize-creators=on
Remoting实现了AK宣称的位置透明feature,其设计基于两个考量:
1、系统间通讯是对等的:如果系统A可以主动连接到系统B做通讯则B也必然可以主动连接到A;
2、通讯系统角色对等:不能限制系统只能被连接,也不能限制系统只能发起连接,就是说P2P通讯和client-server通讯不同,完全不存在客户端或服务端角色之分,client-server通讯可以用HTTP or Akka I/O.
除了可以在一个集群的不同节点机上运行一个actor system,在单机上还可以扩展actor子树去充分利用多核。The clones can then be routed to in different fashions, e.g. round-robin. The only thing necessary to achieve this is that the developer needs to declare a certain actor as “withRouter”, then—in its stead—a router actor will be created which will spawn up a configurable number of children of the desired type and route to them in the configured fashion. Once such a router has been declared, its configuration can be freely overridden from the configuration file, including mixing it with the remote deployment of (some of) the children. Read more about this in Routing.
一个actor创建子actor的时候,actor system的deployer会决定这个新的子actor是部署在同一个JVM还是远程部署:部署到另外一个节点去,远程部署情况下,子actor的创建过程实际上是通过网络通讯去触发、然后在另一个不同的节点机/JVM(也是不同的actor system,此处称为remote system远程系统)上去实际执行的,远程系统会把新的子actor放到一个特定的专用path下,该path是专为远程actor准备的(此处远程的含义是这个actor的父监管者是远程actor). 父监管者actorRef是context.paren亲生父母,和context.path.parent(the parent node in the actor’s path)养父母不是同一个actor. 但是从亲生父母actor查找这个子actor的名字是可以找到它的,并且可知它在远程节点上, preserving logical structure e.g. when sending to an unresolved actor reference.
图示:
跨网络发送actorRef的时候,它的path就是主要信息,所以,一个path文本必须把向一个actor发送消息的所有必要信息编码进去,包括protocol传输协议、path字符串地址部分的host 和 port . 当actor system从远程节点接收到一个actor path,它首先会检查path的地址是否匹配自己的主机地址,然后从path解析出actor的本地地址local reference.
我们常用的actor path范围
1、"/user":ActorSystem.actorOf创建的actor都在这下面;
2、"/remote":所有监管者是远程actor的actor都在这下面;
path层次当中的一切都是actor
网友评论