对于许多人来说,“原生云”和“应用程序的12要素”是同义词。本文的目的是为了说明,对于原生云,除了坚持基本的12大要素外,还有一些其他的因素。在
大多数情况下,Java 能胜任这一任务。在本文中,我们将研究概念和代码示例,看看超越标准的12个因素以外的因素。 正如Kevin
Hoffmann 最近在 O’Reilly 出版的书上提到了超越 12 个因素的应用。
1. 一个代码库
虽然少了一个特定的 Java 概念,这个因素一般是指单个代码库在源代码控制或管理一组的存储库是来自于一个共同的根。 获取单个代码库 使
它能更清洁地构建,并在各种环境下推出任意数量不同的发布版本。当你的应用程序是由一打或者更多的代码库构成的,那么这就是最好的反面案例。当使用一个代
码库来生产多种可以工作的应用程序,且目标在应用和代码库间是一种一对一的关系。操作从一个代码库就可以做到,这不是说没有挑战。有时,对一个团队或者组
织来说,一个应用程序对应一个代码库是最简单的关系。
2. 依赖关系管理大部分Java开发人员(包括Groovy)会使用Maven和Gradle之类的工具。想要正确地地编译及执行项目,这些工具提供了描述依赖关系的方法。核心思想是允许开发人员描述依赖关系,并使用工具确保这些依赖关系被实现、打包进一个单独的二进制部署的构件中。Maven Shade或者Spring Boot这样的插件使你能够把应用和依赖关系绑定成一个单独的“uberjar” or “fat jar”文件中,这样就提供了隔离这些依赖关系的方法。 图1是Spring Boot应用的Maven编译文件pom.xml例子的一部分,它显示了开发人员所指定的依赖关系。 图1: 应用依赖POM.xml 的其中一部分 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version> 1.3 . 7 .RELEASE</version>
<relativePath/> <!-- lookup parent from repository
-->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
|
图2是相同应用中列出的依赖关系的另外一部分。这展示了JARS文件被绑定到应用的uberjar中,并从可变的环境中隔离这些依赖关系。应用的构建依靠这些依赖关系而不是部署目标中可能产生冲突的类库。 图2: 部分maven依赖关系:示例项目树状关系 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | [INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building quote-service 0.0 . 1 -SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-dependency-plugin: 2.10 :tree ( default -cli) @quote -service ---
[INFO] com.example:quote-service:jar: 0.0 . 1 -SNAPSHOT
[INFO] +- org.springframework.cloud:spring-cloud-starterconfig:jar: 1.1 . 3 .RELEASE:compile
[INFO] | +- org.springframework.cloud:spring-cloud-starter:jar: 1.1 . 1 .RELEASE:compile
[INFO] | | +- org.springframework.cloud:spring-cloud-context:jar: 1.1 . 1 .RELEASE:compile
[INFO] | | | \- org.springframework.security:springsecurity-crypto:jar: 4.0 . 4 .RELEASE:compile
[INFO] | | +- org.springframework.cloud:spring-cloud-commons:jar: 1.1 . 1 .RELEASE:compile
[INFO] | | \- org.springframework.security:spring-securityrsa:jar: 1.0 . 1 .RELEASE:compile
[INFO] | | \- org.bouncycastle:bcpkixjdk15on:jar: 1.47 :compile
[INFO] | | \- org.bouncycastle:bcprovjdk15on:jar: 1.47 :compile
[INFO] | +- org.springframework.cloud:spring-cloud-configclient:jar: 1.1 . 2 .RELEASE:compile
[INFO] | | \- org.springframework.boot:spring-boot-autoconfigure:jar: 1.3 . 7 .RELEASE:compile
[INFO] | \- com.fasterxml.jackson.core:jacksondatabind:jar: 2.6 . 7 :compile
[INFO] | \- com.fasterxml.jackson.core:jacksoncore:jar: 2.6 . 7 :compile
[INFO] +- org.springframework.cloud:spring-cloud-startereureka:jar: 1.1 . 5 .RELEASE:compile
[INFO] | +- org.springframework.cloud:spring-cloud-netflixcore:jar: 1.1 . 5 .RELEASE:compile
[INFO] | | \- org.springframework.boot:springboot:jar: 1.3 . 7 .RELEASE:compile
|
3. 编译, 发布, 运行
一个独立的代码库是通过编译构成并生成独立构件;然后和项目外部的配置信息进行合并。代码库随后被发布到云环境下运行。千万不要在运行期间改变代码。由于系统提供了使用相同方式把构件组装到一起的单独的位置,编译的思想在于自然地过渡到下一步的集成 (CI)。 现代的Java框架可以生成uberjars文件,或者更传统的WAR文件, 这些文件作为独立的容易集成的构件。发布阶段主要是合并外部的配置信息(参考下面的配置)和独立的项目构件以及像JDK,OS,和Tomcat这样的依赖。目的是生成可执行的、版本化的、可以撤销的发布版本。云平台拿到发布版本后使用一种严格独立的方式来处理运行阶段。 4. 配置这一要素是具体化配置的形式,随着部署环境的变化有所不同。(开发, 演示, 生产). 配置信息无处不在: 分布在应用的代码中, 在如YAML这样的属性源文件中, Java属性, 环境变量, CLI参数, 系统参数, JNDI, 等等。有多重解决方案—重构代码以寻找环境变量。 对于简单的系统, 一个最直接的方案是使用Java的System.getenv()功能从环境中拿到一个或者多个配置信息,或者全部key值和value值的Map. 图 3是代码示例. 图3: 项目依赖POM.xml的一部分
1 2 | private String userName = System.getenv(“BACKINGSERVICE_UID”);
private String password = System.getenv(“BACKINGSERVICE_PASSWORD”);
|
对于更复杂的系统, Spring Cloud和Spring Boot 更流行,它们提供了强大的功能来控制源文件以及解析配置数据. 5. 日志日志应该被视为事件流:一个由应用产生的具有时序性的事件序列。自从使用了云,你就不能使用文件的形式记录日志,你需要将日志输出到stdout/stderr上,供云端提供者,或者相关工具处理。举例说明,Cloud Foundry的loggregator将日志以流的形式输出,以供日志聚合和集中管理。在java中以stdout/stderr的方式记录日志的简单实例如下: 1 2 3 4 5 6 7 | Logger log = Logger.getLogger(MyClass. class .getName());
log.setLevel(Level.ALL);
ConsoleHandler handler = new ConsoleHandler();
handler.setFormatter( new SimpleFormatter());
log.addHandler(handler);
handler.setLevel(Level.ALL);
log.fine(“This is fine.”);
|
6. 可自由支配如果你有流程需要一段时间来启动或关闭,那么该流程需要分离一个后台服务并优化提高相应性能. 一个依赖云建立的程序是可自由支配的—它能在任何时间被创建或者销毁。 这种设计有助于确保服务长时间运行良好,并且使你从这种类似于自动扩展的特性中受益。 7.支持服务一个支持服务是一些你的app依赖的外部事物,例如一个数据库或者一个消息服务器。 app应该以外部配置的方式声明所需的支持服务,例如YAML或者一个跟踪配置服务. 一个云平台处理绑定你的app到相应服务,理想情况下绑定或重新绑定不需要重新启动你的app。 这种松耦合有许多优势,例如允许你使用断路器模式优雅地处理一个强迫停运的情景 8. 环境一致共同开发和QA沙箱环境,从生产规模和可靠性角度与生产环境比都有不同,但你不能使用一个“雪花”(差距过大,无法正确评估性能)环境! 云平台保持多app环境一致并且可以消除调试环境的差异。 9. 管理琐碎过程一些你需要管理的程序,例如定时任务,一次性脚步,和其他一些你可能会使用一种编程shell脚本等,来自云平台的支持服务和其他功能,能够帮助你运行这些程序。 目前Java并不像Python或者Ruby以shell方式运行,云平台生态系统有很多选项能够便捷的运行一次性程序或者制作一个shell接口。 10、端口绑定在无云的世界,典型地看到一些app运行在相同容器,通过端口号来隔离每个app,然后使用DNS来提供一个友好名称来访问。而在云端,你若避开这个微管理——云端提供者将会根据路径与估摸来管理端口分发。 虽然它可能依赖外部机制,为您的app提供流量,但这些机制在容器、机器与平台之间各不同。端口绑定为你提供全量控制,提示你该如何接收与响应应用请求,而不管其部署在哪里。 11、进程这里提及的原始12-因素定义指出app是无状态的。但是,有一些状态需要出现在指定的地方。沿着这些线,这个因素提倡将任何长期运行的状态转换为高速缓存或者数据存储,来实现外部的、逻辑的支撑服务。 12、并发云平台是沿着横向规模构建的。这些设计如此考虑——你的app应被暴露,处于无状态,并且使用无共享的进程。在平台的进程管理模型中协调工作,重要的是懂得利用特征,比如自动缩放,蓝绿色部署以及其他方面。 13.超出12大要素之外的因素:遥测,安全,API优先设计原则12大要素的提出大约是在2012年,让我们来看看现代云中的一些基本功能,这些功能会使应用程序的运行更加稳定可靠。 在Cloud Foundry上,Java应用程序日志可以简单的的定向到标准输出/标准错误,这里可以通过运算符控制。 Spring Boot使JMX更加快捷,而且在商用云平台可以提供像APM这样的先进功能。 应用程序外部的安全,比如应用于使用RBAC应用程序的终端(URLs),在云平台为SSO & OAUTH2提供集成显得尤为非重要。否则,对于多java应用程序的安全性变得难以保障。 Beyond the 12 Factor App中这样描述将 API优先法则:“是需求优先发展模式的一种扩展,其开发者优先关注于构建应用程序的边缘和接口,随着连续通过CI服务器对集成点的测试,工作团队可以专注各自的服务,仍然保持合理的运行,这是一切正常工作的保证。” 平台革新总而言之,一个很重要的认识是你不需要仅仅为了满足所有的15条因素而去重新部署一个在云上运行的上线的应用程序,这种cloud-native 成熟度模型(由大型金融服务组织表述)说明了被用于处理大型复杂的整体的应用程序和他们“12分解”在逐步发展。 |