赞
踩
自从Martin Fowler(马丁)在2014年提出了Micro Service(微服务)的概念之后,业界就卷起了一股关于微服务的热潮,大家讨论多年的SOA(Service-Oriented Architecture,面向服务的架构)终于有了新的解决方案,人们不再需要笨重的ESB(Enterprise Service Bus,企业服务总线)。恰逢Docker技术逐渐普及,一个崭新的轻量级SOA架构MSA(Micro Service Architecture,微服务架构)伴随着Docker容器技术正向我们携手走来。
微服务架构(MSA)的出现绝不是偶然的,由于传统应用架构的不合理,从而产生了新的架构模式,这类现象再正常不过了。那么,传统的架构究竟有哪些问题呢?下面进行分析。
下图是一个经典的Java Web应用程序,它包括Web UI部分,还包括若干个业务模块,就像这里出现的ModelA、ModelB、ModelC等。
WebUI与这些Model封装在一个war包中,因此需要将此war部署到Web Server(例如TOMCAT)上才能运行,该应用程序会连接到DataBase(如MySQL)上操作数据。
在系统运行过程中,我们通过监视程序发现,ModelA和ModelB都需要消耗10%的系统资源,加起来占总系统的20%,而ModelC却需要占用80%的系统资源。运行一段时间后,ModelC将会成为系统的瓶颈,从而降低系统的性能。
那么如何解决这个问题呢?人们想到了一个办法。
只需要将这个应用程序复制一份相同的程序,并将其部署到另外一个Web Server上,下方还是链接到相同的DataBase,只是在这些Web Server的上方架设一台Load Balancer(负载均衡器,LB),可见应用程序获得了“水平扩展”。
请求首先会发送到LB上,通过LB上的路由算法(例如轮询或哈希),将请求转发到后面具体的Web Server上,这类请求转发技术被称为Reverse Proxy(反向代理)。
由于进入LB的请求(流量)被均衡到下方各台Web Server中了,流量得到了分摊,负载得到了均衡,因此该技术也被称为Load Balance(负载均衡)。
如果流量加大,我们还可以继续水平扩展更多的Web Server,该架构理论上可以无限扩展,只要LB能够抗的住巨大流量就行。
通过上述方案,轻松的将负载进行了均衡,在一定的程度上缓解了流量对Web Server的压力,但此时却造成了大量的系统资源浪费,比如对系统资源占用率不搞的ModelA和ModelB也进行了水平扩展,其实我们只想对ModelC进行扩展而已。
除了水平扩展方案带来的系统资源浪费,实际上传统架构还有其他的问题,我们继续讨论。
传统应用架构实际上是一个Monolith(单块架构),因为整个应用都被封装在一个WebAPP中,就像是巨石一块,无法拆解,我们所做的水平扩展也只是在扩展一块块的巨石。为了便于表达,我们不妨将单块架构搭建起来的应用简称为“单块应用”。
我们再部署单块应用的时候,同样也会遇到很多的麻烦,比如:
此外,对于Java Web应用而言,打包war包里的代码一般是class 文件,这也就意味着,我们的单块应用只是基于java语言开发的,无法将应用中某个单个的Model通过其他开发语言来实现(假如我们不考虑JVM上运行动态语言的情况下),也许其他语言开发实现某个模块更加合适,这样就会产生技术选型的单一问题。
综上所述,传统应用架构存在以下问题:
当然,传统应用架构的问题还远不止这些。当业务越来越复杂时,应用会变得越来越臃肿,“身材”越来越“胖”而无法瘦身。于是,人们找到了新的思路来解决传统应用架构的问题,这就是微服务架构。
那么微服务架构与传统应用架构的区别到底在哪?我们继续探讨。
微服务架构从字面上来理解就是:许多微小服务搭建起来的应用架构。
这句话涉及到很多问题,我们逐一讨论,比如:
带着这些问题,开始下面的讨论,首先我们来看如何定义微服务架构。
当马丁大神提出微服务架构这个概念的时候,同时他也对微服务架构提出了几条要求,也就是说,当我们的应用满足一下条件时,才能称之为微服务架构,具体包括:
我们简单的分析一下:首先根据产品的业务功能模块来划分服务种类,也就是说,我们需要按照业务功能去划分种类,这是“垂直划分”;而在代码层面上进行划分,这是“水平划分”。每个服务可以独立部署,还需要相互隔离,也就是说,服务之间是没有任何干扰的,可将每个服务放入到独立的程序中运行,因为进程之间是完全隔离的。客户端通过轻量级API来调用微服务,比如可以通过HTTP或者RPC的方式来调用,目的是为了降低调用所产生的的性能开销。服务需要确保高可用性,不能长时间的无法响应,需要提供多个“候补队员”,在某个服务出现故障时,可以自动调用其中一个正常工作的服务。
微服务架构颠覆了传统应用架构的模式,若不定义良好的交付流程与开发规范,则很难让微服务发挥出真正的价值,下面我们来看一下微服务的交付流程。
使用微服务架构开发应用程序,我们实际上是针对一个个服务进行设计、开发、测试、部署。因为每个服务之间是没有彼此依赖的,大概的交付流程付下:
在设计阶段,架构师将产品功能拆分成若干个服务,为每个服务设计API接口(例如REST API),需要给出API文档,包括API的名称、版本、请求参数、响应结果、错误代码等信息。在开发阶段,开发工程师去实现API接口,也包括完成API的单元测试工作。在此期间,前段工程师会并行开发Web UI部分,可根据API文档造出一些假数据(我们称之为“mock”数据)。这样一来,前段工程师就不必等待后端API全部开发完毕,才能开始自己的工作。在测试阶段,前后端工程师分别将自己的代码部署到测试环境上,测试工程师将针对测试用例进行手工或自动化测试,随后产品经理将从产品功能上进行验收。在部署阶段,运维工程师将代码部署到预发环境中,测试工程师再一次进行一些冒烟测试,当不在发生任何问题的时候,经技术经理确认,运维工程师将代码部署到生产环境中,这一系列的部署过程都需要做到自动化,才能提高效率。
需要注意的是,以上过程中看似需要多种工程师参与,实际上并非每种角色都对应具体的工程师。往往在小的团队里,一名工程师可以身兼多职,这些都是正常现象。只是对于大团队而言,分工比较明确,更容易实施这套交付流程。
在以上交付流程中,开发、测试、部署这三个阶段可能都会涉及对代码的控制,我们还需要指定相关的开发规范,以确保多人能够良好的协作。
无论使用传统应用架构,还是微服务架构,我们都需要定义良好的开发规范。经验表明,我们需要善用代码版本控制系统。就拿Git来说,它很好的支持了多分支代码版本,我们需要利用这个特性来提高开发效率,下图是一副经典的分支管理规范:
最稳定的代码放在master分支上(想当于SVN的trunk分支),我们不要直接在master分支上提交代码,只能在分支上进行合并操作,例如将其他分支的代码合并到master分支上。
我们日常开发中的代码需要从master分支上拉下一条develop分支出来,该分支所有人都能访问,但一般情况下,我们也不会直接在该分支上提交代码,代码同样是从其他分支上合并到develop上去的。
当我们需要开发某个新特性的时候,需要从develop分支上拉出一条feature分支,例如feature1和feature2,在这些分支上并行的开发具体特性。
当特性开发完毕之后,我们决定发布某个版本了,此时需要从develop分支上拉出一条release分支,例如release-1.0,并将需要发布的特性从相关的feature分支合并到release分支上,随后对release分支部署测试环境,测试工程师在该分支上做功能测试,开发工程师在该分支上改bug。待测试工程师无法找到任何bug时,我们可以将release分支部署到预发环境中。再次检验以后,无任何bug,此时可以将release部署到生产环境上。待上线完成之后,将release分支上的代码同时合并到develop分支与master分支上,并在master分支上打一个tag,例如v1.0.0。
当在生产环境中发现bug时,我们需要从对应的tag上(例如v1.0.0)拉出一条hotfix分支(例如hotfix-1.0.1),并在该分支上进行bug修复,待bug完全修复后,需要将hotfix分支上的代码同时合并到master和develop上。
对于版本号我们也有要求,格式为:x.y.z,其中x用于有重大重构时才会升级,y用于有新特性发布时才会升级,z用于修复了某个bug后才会升级。
针对每个服务的开发工作,我们都需要严格按照以上开发规范来执行。
实际上,我们使用的开发规范是业界知名的Git Flow,可以通过以下博客了解到Git Flow的详细过程:
http://nvie.com/posts/a-successful-git-branching-model/。
此外,在GitHub中有一个基于以上Git Flow的命令行工具,名为git-flow,其项目地址如下:
https://github.com/nive/gitflow。
我们已经对微服务架构的概念、交付流程、开发规范进行了描述,下面我们大致归纳一下微服务有哪些特点。
世界著名软件大师Chris Richardson(克里斯)创建了一个总结微服务架构模式的网站。该网站上列出了大量的微服务架构模式,分为:核心模式、部署模式、通信模式、服务发现模式、数据管理模式等。该网站的网址:http://microservice.io/。
微服务架构相对于传统的架构有着显著的特点,同时微服务架构也给我们带来了一定的挑战,我们先从他的特点说起。
微服务的架构特点非常明显,可能还有很多,但同时微服务架构也给我们带来了许多挑战。
可见,微服务架构的要求还是想当高的,不仅仅对技术,而且对运维都有很高要求,我们需要设计出一款简单易用的轻量级微服务架构来满足自身的需求。下面用一张图来描述一下我们对微服务架构的愿景。
我们不妨从下往上来理解这张图。底层部署了一系列的Service,每个Service可能有自己的DB,或者多个Service公用一个DB,且同一个Service可部署多个。当Service启动时,会自动的将信息注册到Service Registry(服务注册表)中,比如:每个服务的IP与端口。当Web UI发出请求时,该请求会发送到Service Gateway(服务网关)中,Service Gateway读取请求数据,并从Service Registry中获取对应的Service信息(IP与端口号),最后,Service Gateway主动调用下面对应的Service。整个过程就是这样,其中Service Gateway担当了重要角色。
大家可能会认为Service Gateway将成为一个中心,造成单点故障。没错,完全有这个可能,所以我们将他做的越“薄”越好,所以我们再技术选型上,要谨慎考虑。
此外,对于Service Registry的高可用性也有很高的要求,他不仅在每个Service启动时提供“服务注册”,还需要在Service Gateway处理每个请求时提供“服务发现”。如果他失效了,那么整个系统将无法工作。
我们可以使用SpringBoot作为微服务开发框架,SpringBoot拥有嵌入式Tomcat,可直接运行一个jar包来运行微服务,此外,他还提供一些“开箱即用”的插件,可大大提高我们的开发效率,我们也可以去扩展更多的插件。
发布微服务时,可以链接ZooKeeper来注册微服务,实现“服务注册”。实际上ZooKeeper中有一个名为ZNode的内存树状模型,树上的结点用于存放微服务的配置信息。使用Node.js处理浏览器发送的请求,在Node.js中链接ZooKeeper,发现服务配置,实现“服务发现”,有大量的Node.js的ZooKeeper客户端可以完成这个任务。
通过Node.js将请求发送到Tomcat上,实现“反向代理”,同样也有大量的Node.js库供我们自由选择。Node.js的“单线程模型”且“非阻塞异步式I/O”特性通过“事件循环”的方式来支撑大量的高并发请求,此外Node.js原生也提供了集群特性,可确保高可用性。
为了实现微服务的自动化部署,我们可以通过Jenkins搭建自动化部署系统,并使用Docker将服务进行容器化封装。
综上所述,微服务架构技术选型如下所示:
SpringBoot:http://projects.spring.io/spring-boot/。
ZooKeeper:http://zookeeper.apache.org/。
Node.js:https://nodejs.org/。
Jenkins:https://jenkins.io/。
Docker:https://www.docker.com/。
我们通过一张图来归纳一下微服务架构的技术选型
除了上述的技术选型外,实际上还有其他可选的方案,比如Netflix公司开源的微服务技术栈:
Netflix:http://netflix.github.io/。
Spring官方在SpringBoot的基础上,封装了Netflix相关组件,提供了一个名为SpringCloud的开源项目。
SpringCloud:http://projects.spring.io/spring-cloud/。
就连曾今的JBoss也推出了自己的微服务框架WildFly Swarm。
WildFly Swarm:http://wildfly-swarm.io/。
此外,还有一个轻量级的REST框架也宣称具备可开发微服务的能力。
Dropwizad:http://dropwizad.io/。
以上仅为java相关的微服务技术选型,其他开发语言也有自己的微服务开发技术栈。
Spring犹如一股清风,吹散了EJB对javaEE的绝对统治地位。他的IOC、AOP等特性开启了我们对面向对象编程的新视野,让我们惊叹道:原来程序还能这样写!随着SpringMVC逐渐强大起来,赢得了JAVA WEB开发的很大的市场占有率。不管是struts还是Strust2都逊色于它,就连持久层技术Hibernate的市场也在逐渐缩小,因为Spring+MyBitis早已成为市场主流。可以断言,Spring“一统江湖”指日可待。实际上,Spring已经十多岁了,在开源世界里他已经不再年轻,给我们的感觉是它的体积越来越大,使用起来越来越方便 。就像事先商量好的一样,SpringBoot的诞生让Spring应用程序变得更加高效,恰巧当Spring Boot遇上“微服务”之后,让我们更加意识到,微服务的春天已经悄悄来到了。
SpringBoot是为生产级Spring应用而生的,他使得开发Spring应用程序更加高效、简洁。那么,SpringBoot具备哪些生产级特性呢?我们不妨从它的由来开始讲起。
在Spring1.0的时代,我们习惯于用XML文件来配置Bean,在XML文件中可以轻松的进行依赖注入,但当Bean的数量越来越多时,XML也会变得越来越复杂,少则上百行,多则上千行,没有人愿意维护一大段XML配置。紧接着Spring2.0很快到来了,他在XML命名空间上做了一些优化,让配置看起来尽可能的简单,但仍没有彻底解决配置上的问题,知道Spring3.0的出现,我们可以使用Spring提供的java注解取代曾今的XML配置了,似乎我们都忘记了曾今发生了过什么,Spring变得前所未有的简单。当Spring4.0出现后,我们甚至连XML配置文件都不需要了,完全使用java源码级别的配置与Spring提供的注解就能快速的开发出Spring应用程序。
尽管Spring已经非常优秀了,但仍无法改变Java Web应用程序的运行模式,也就是说,我们仍需要将war包部署到web Server上,才能对外提供服务。能否运行一个简单的main()方法就能启动web Server呢?Spring Boot满足了我们的需求,我们下面就来全面的了解一下Spring Boot的所有特性。
SpringBoot官方提供了大量插件,涉及面非常的广,包括Web、SQL、NoSQL、安全、验证、缓存、消息队列、分布式事物、模板引擎、工作流等,还提供了Cloud、Social、Ops方面的支持。
此外,SpringBoot对某项技术提供了多种选型,比如:
官方还提供了一个名为“Spring Initialzr”的在线代码生成器,我们只需选择自己想要的插件,就能一件下载对应的代码框架。就连IDEA也支持了Spring Initialzr。
SpringBoot拥有非常强大的插件体系,如此之多的插件,让我们在开发应用程序的时候如虎添翼,我们可以优先从这个“插件库”中选择插件,如果有的插件不够用或者不合适,我们还可以实现自己想要的插件。
SpringBoot所提供的功能强大且实用,但SpringBoot并非适合开发所有应用场景。那么那些场景比较适合使用SpringBoot呢?
现在大家应该了解到SpringBoot是什么以及他可以做什么了,下面通过几个简单的示例来展示一下SpringBoot的基本用法,目标是让大家快速上手。
不仅对SpringBoot框架,其实学习任何框架的第一步都是搭建开发环境,然后尝试写一个“helloworld”应用程序并试图让他跑起来,最后才去探索他的若干特性。我们现在就来搭建一个SpringBoot开发框架,充分体验一下SpringBoot给我们开发带来的快乐。
当然,大家也可以按照自己的喜好来命名,不一定非要与文中相似。
在pom文件中添加如下的SpringBoot的Maven配置:
<parent>
<groupId>org.springbootframwork.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.3.RELEASE</version>
</parent>
通过上面的配置,我们的应用才算是SpringBoot应用,该配置会继承大量的SpringBoot插件,但这些插件都未启用,我们下面要做的就是启用对我们有用的插件。例如,启用Web插件,需要继续添加如下的配置:
<dependencies>
<dependency>
<groupId>org.springframwork.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
以上配置只需要配置groupId和artifactId即可,因为在父pom中已经配置了version了。
SpringBoot提供了相应的Maven插件,只需要通过下面的配置即可使用:
<build>
<plugins>
<groupId>org.springframwork.boot</gourpId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugins>
</build>
需要说明的是spring-boot-maven-plugin并不是SpringBoot应用必须要求的,但仍建议大家使用,下面会讲到他的具体意义。
现在SpringBoot开发框架已经搭建完毕,下面要做的就是开发一些简单的功能来进一步体验它,我们就以“helloworld”应用程序来讲解。
由于msa-hello应用的groupId是demo.msa,因此我们需要定义一个名称也为demo.msa的包名,所有的代码在该包下。
import org.spingframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class HelloApplication{
public static void main(String[] args){
SpringApplication.run(HellowApplication.class,args);
}
}
以上HelloApplication类并不是一个普通的类,他必须拥有一下两个特点:
下面我们做一个简单的REST API,例如GET:/hello,表示API的请求方法是GET请求,请求路径是/hello,该API只返回一个”hello“字符串。
直接在HellowApplication类中添加如下代码:
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@SpringBootApplication
public class HelloApplication{
@RequestMapping(method=RequestMethod.GET,path="/hello")
public String hello(){
return "hello";
}
}
为了对外发布REST API,我们只需要做三件事情:
当然,如果我们只考虑“单一职责原则”,那么应该将@RestController与@RequestMapping注解以及所涉及的代码从HelloApplication类中抽取出来,将其放入单独的Controller类中,就像下面这样:
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController{
@RequestMapping(method=RequestMapping.GET,path="/hello")
public String hello(){
return "hello";
}
}
对于GET请求,我们可以简化@RequestMapping的写法,可以直接写成下面你的方法:
@RequestMapping(“/hello”)
因为默认的REST API就是GET请求。
现在一个简单的SpringBoot应用程序就开发完毕了。
我们可以通过Maven编译并打包,在target目录下将看到一个msa-hello-1.0.0.jar的文件,使用解压工具打开jar文件,将看到如下图的目录结构。
demo目录下存放项目的class文件,lib目录下存放项目运行所依赖的jar包,META-INF目录下存放Maven构件所生成的相关文件,其中包含一个MANIFEST.MF文件,该文件内容如下:
Manifest-Version:1.0
Implementation-Title:msa-hello
Implementation-Version:1.0.0
Archiver-Version:Plexus Archiver
Built-By:huangyong
Start-Class:demo.msa.HelloApplication
Implementation-Vendor-Id:demo.msa
Spring-Boot-Version:1.3.3.RELEASE
Created-By:Apache Maven 3.0.5
Build-Jdk:1.8.0_60
Implementation-Vendor:Pivotal Software, Inc.
Main-Class:org.springframework.boot.loader.JarLauncher
注意最后一行的Main-Class,它表示运行jar包所需的Main类,即提供main()方法所在的类。可见,其并非我们编写的HelloApplication类,而是SpringBoot提供的JarLauncher类,该类存放在jar包中的org目录下。
此外,我们也可以在IDEA中查看该项目jar包的依赖关系。
运行SpringBoot应用程序非常简单,我们可以根据实际情况,自由的选择使用一下三种方法来运行SpringBoot应用程序。
在IDEA中直接运行
直接在IDEA中执行HelloApplication类来启动SpringBoot应用程序。
该方式有利于程序的debug,在日常开发过程中优先使用这种方式,但debug方式肯定比run方式启动的慢一些。
使用Maven运行
可以使用Maven命令来运行
mvn spring-boot:run
以上就用到了spring-boot-maven-plugin插件,我们运行的是spring-boot插件的run目标,此外他还提供了spring-boot:start与spring-boot:stop目标。
使用Java命令运行
使用java命令行来运行SpringBoot应用程序:
java -jar msa-hello-1.0.0.jar
该方式看似不需要spring-boot-maven-plugin插件,但实际上是需要的。通过该插件所打的jar包不是一般的jar包,它比一般的jar包体积要大许多,因为它包含运行该jar包所需要的其他jar包。
可见,SpringBoot还是很容易上手的,只要会Maven并熟悉Spring的基本用法,就能够迅速搭建SpringBoot框架。后面的内容中还会继续扩展此框架,让他成为一款真正的轻量级微服务开发框架。
除了SpringBoot的基本特性外,还有一些更高级的特性,尤其是SpringBoot提供的生产级特性,也对我们的开发非常的有帮助。
SpringBoot提供了大量开箱即用的插件,其中有一个名为Actuator的插件提供了大量生产级特性,可以通过Maven配置使用该插件。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
添加Maven依赖后,我们重启SpringBoot应用程序,就能开启端点生产级特性了。
SpringBoot的Actuator插件提供了一系列的http请求,我们可以发送相应的请求,来获取SpringBoot应用程序的相关信息。这些HTTP请求都是GET类型的,而且不带任何参数,他们就是所谓的“端点”,也许他的英文“Endpoint”更容易理解,SpringBoot的Actuator插件默认提供了一下端点。
端点 | 描述 |
---|---|
autoconfig | 获取自动配置信息 |
beans | 获取Spring Bean基本信息 |
configprops | 获取配置项信息 |
dump | 获取当前线程基本信息 |
env | 获取环境变量信息 |
health | 获取健康检查信息 |
info | 获取应用基本信息 |
metrics | 获取性能指标信息 |
mappings | 获取请求映射信息 |
trace | 获取请求调用信息 |
例如我们再浏览器地址发送/metrics请求时,会看到性能指标的返回结果。
默认情况下,以上的端点都是开启的,我们随时可以访问,根据实际情况我们可以自由控制哪些端点需要启用,哪些端点需要停用。也可以全部停用、启用,这些端点的控制在application.properties文件中配置。一下给一些实际的操作。
关闭metrics端点
endpoints.metrics.enabled=false
在浏览器访问/metircs的时候,看到的将是“Whitelable Error Page"的错误页面,对应的HTTP状态码为404(not found)。
关闭所有端点,仅开启metrics端点
endpoints.enable=false
endpoints.metrics.enabled=true
现在只有metrics端点是启用的,访问其他的端点会报错。
修改metrics端点的名称
endpotins.metrics.id=performance
这样我们就可以通过/performance请求来访问以前的mertics端点了,此时继续发送/mertics,会看到报错信息。
修改metrics端点的请求路劲
endpoints.mertics.path=/endpotins/mertics
通过以上的配置,我们需要在发送/endpoints/mertics请求后才能访问metrics端点。
如果我们想知道SpringBoot为我们提供了那些端点,应该如何做呢?
SpringBoot的HATEOAS插件为我们提供了帮助,实际上,HATEOAS是一个超媒体(Hypermedia)技术,他也是REST应用程序架构的一种约束。通过他可以汇总端点信息,包括各个端点的名称与链接。开启HATEOAS插件,只需要添加一下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
此后,我们将拥有actuator端点,当我们发送/actuator请求后,将看到所有的端点及其链接方式。
在SpringBoot所提供的端点中,有一个名为health的端点,用于查看当前以你该用的运行状态,即应用的健康状况。检查应用的健康状况,我们简称为“健康检查”。
当我们在浏览器发送/health请求后,将看到应用的健康情况,可以看到应用的运行状态、磁盘空间情况等信息。
实际上,SpringBoot包含了许多内置的健康检查功能,每项功能对应具体的健康检查指标类(HealthIndicator),如下所示:
名称 | 描述 |
---|---|
ApplicationHealthIndicator | 检查应用运行状态(对应status部分) |
DiskSpaceHealthIndicator | 查看磁盘空间(对应diskSpace部分) |
DataSourceHealthIndicator | 检查数据库连接 |
MailHealthIndicator | 检查邮箱服务器 |
JmsHealthIndicator | 检查JMS代理 |
RedisHealthIndicator | 检查Redis服务器 |
MongoHealthIndicator | 检查MongoDB数据库 |
CassandraHealthIndicator | 检查Cassandra数据库 |
RabbitHealthIndicator | 检查Rabbit服务器 |
SolrHealthIndicator | 检查Solr服务器 |
ElasticsearchHealthIndicator | 检查ElasticSearch集群 |
我们添加相关的SpringBoot插件后,即可开启对应的健康检查功能。默认情况下只有ApplicationHealthIndicator与DiskSpaceHealthIndicator是启用的。我们还可以通过management.health.defaults.enabled属性来控制是否开启健康检查特性,默认为true,表示是开启的。
虽然SpringBoot提供的健康检查已经很全面了,但如果我们还觉得不够用的话,也可以实现自己的健康检查,需实现ort.springframework.boot.actuate.health.HealthIndicator接口,并覆盖health()方法即可。
实际上,我们可以利用健康检查特性来开发一个微服务系统监控平台,用于获取每个微服务的运行状态与性能指标。当然也有现成的解决方案,比如spring-boot-admin,它就是一款基于Spring Boot的开源监控平台。
spring-boot-admin项目地址:https://github.com/codecentric/spring-boot-admin。
除了health端点以外,还有一个名为info的端点,我们可以用它来获取SpringBoot应用程序的基本信息,比如应用程序的名称、描述、版本等。当我们发送/info请求时,却获取不到任何数据,因为我们目前还没有配置任何的应用信息。
应用基本信息的相关配置都是以info为前缀的配置项,就像下面这样:
info.app.name=Hello
info.app.description=This is a demo of Spring Boot
info.app.version=1.0.0
随后我们就可以通过浏览器获取上面的配置信息了
使用SpringBoot开发的REST API是想当容易的,一般情况下,REST API是独立部署的,如果WebUI也进行独立部署,那么REST API与WebUI可能在不同的域名部署,从WebUI发送的AJAX请求去调用REST API时就会遇到“跨域问题”,在浏览器控制台会报错:“No’Access-Control-Allow-Origin’header is present on the requested resource.”,因为AJAX的安全限制,它是不支持跨域的,我们需要通过技术手段来解决这个问题。
曾今我们可以使用JSONP(JSON with Padding)来实现跨域问题,简单的来说就是客户端发送一个AJAX请求,并在请求参数后面添加一个callback参数,指向一个JS函数(成为callback回调函数)。服务器返回了一个JavaScript函数,该函数将JSON数据做了一个封装(Padding),就像这样callback({…});,这样我们只需要在客户端上定义一个callback回调函数,就能获取从服务器端返回的JSON数据了。
JSONP看似简单好用,实际上它也有非常明显的限制,只支持GET请求,如果我们需要使用JSON技术发送其他请求(比如POST)就不太可能了。当然也可以通过其他手段来实现,比如iframe,但该方法过于繁琐,多年前早已弃用。现在,我们优先选择的是更加轻量级的CORS(Cross-Origin Resource Sharing)来实现跨域问题,他目前也加入到了W3C规范中了,而且当前主流浏览器都能很好的支持该规范。
关于CORS理论知识可以参见它的官方网站:http://www.w3.org/TR/cors/。
SpringBoot很好的支持了CORS,我们只需要添加关于CORS的端点配置就能随时开启该特性,默认情况下他是禁用的,通过以下配置可以使用:
endpoints.cors.allowed-origins=http://www.com
endpoints.cors.allowed-mehtods=GET,POST,PUT,DELETE
此外,也可以在HelloApplication类上加上@CorssOrigin注解来实现跨域,就像这样:
import org.springframework.web.bind.annotation.CrossOrigin;
@SpringBootApplication
@RestController
@CrossOrigin
public class HelloApplication{
...
}
在@CrossOrigin注解中也提供了origins、methods等属性,我们可以自行配置。当然,我们也可以使用如下方法配置CORS下相关属性。
import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter{ @Override public void addCorsMappings(CorsRegistry registry){ registry.addMapping("/**").allowedOrigins("http://www.xxx.com") .allowMethods("GET","POST","PUT","DELETE"); } }
实际上,在Spring4.2以后才开始支持CORS。可以阅读Spring官方博客上的一篇关于CORS的文章。
CORS support in Spring Framework :http://spring.io/blog/2015/06/08/cors-support-in-spring-framework。
我们可以在application.properies配置文件中指定SpringBoot的相关配置项,还可以@…@占位符获取Maven资源过滤的相关属性,此外还可以通过外部配置覆盖SpringBoot配置项的默认值,可以先从以下位置获取:
以“java命令行参数”为例,我们在运行SpringBoot的jar包时,可以通过以下方式指定外部配置:
java -jar xxx.jar --server.port=18080
通过上面的–server.port配置,可以将默认的8080端口改为18080
SpringBoot提供了一个名为Remote Shell的插件,允许我们可以通过ssh远程链接正在运行中的SpringBoot应用程序,只需要添加以下配置:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-remote-shell</artifactId>
</dependency>
当我们启动SpringBoot应用程序的时候,将在控制台看到监控相关的信息。
在上一章节中,我们使用SpringBoot开发了一个简单的服务,也讨论了前后端不在一个域名下会产生的“跨域问题”,好在SpringBoot已经提供了CORS跨域特性,我们才能在前端自由的调用后端发布的服务。这样的架构看似不错,但似乎还有点问题,比如,每个微服务可能部署在不同的IP与端口上,前端必须知道后端服务部署的位置,那么能否做到前端无须知道后端具体服务的具体细节,通过一个统一的方式去调用后端服务呢?其实我们需要的是一个“API网关”,前端所有的请求都会进入该网关,通过网关去调用后端的服务。
本章我们使用Node.js搭建一个统一的网关,我们在微服务架构中称其为API Gateway(API 网关)或Service Gateway(服务网关),所有从前端发送的请求都会到这个网关上,Node.js通过“方向代理”技术来调用后端发布的服务。不过在完成这件事之前,我们有必要搞清楚Node.js究竟是什么。
从字面上的意思来看,Node.js更像是一款JavaScript框架,其实不完全是这样的,我们千万不要被她的名字所误导。
Node.js官网
在首页上我们就可以清楚的看到对Node.js的诠释:
Node.js是一个基于ChromeV8引擎的JavaScript运行环境,它使用了一个“事件驱动”且“异步非租塞I/O”的模型使其轻量且高效,Node.js的包管理器NPM是全球最大的开源库生态系统。
针对Node.js的定义,我们想稍微补充说明一下:
此外,Node.js非常的小巧,但模块体系却非常的庞大,我们可以在NPM官网上寻找对自己有帮助的模块。
对于“高并发”我们一般采用的“多线程”技术来解决,也就是说,创建大量的线程来处理更多并发的请求。而创建管理线程是一件非常消耗内存的事情,且在CPU核心数不多的情况下,会出现大量的上下文切换现象,因为CPU需要不断的从一堆线程中选择一个线程来处理它的内部的指令。由此说来,该方案在内存较大且CPU强的环境下还是想当不错的选择,Java提供的就是这样的解决方案。
Node.js另辟蹊径,采用“单线程”技术来解决高并发的问题,在内部提供一个“事件驱动”与“异步非阻塞I/O”模型,让我们可以在硬件资源相对普通的条件下也能扛得住高并发的压力。
解决高并发,实际上就是解决I/O问题。我们通常所说的I/O问题其实包括两方面,即“网络I/O”与“磁盘I/O”。java的I/O模型是同步的,直到后来NIO(非阻塞I/O)技术的出现,Java编程模型才有了异步的特性,而Node.js与生俱来就有异步的特性,而且利用JavaScript的回调函数可以使异步编程变得格外的简单。
可见Node.js只是利用了JavaScript语言的特性去完成服务端的事情,甚至有人提到,几乎所有的服务端开发语言(例如java、PHP、Python等)可以做的,它都能做到,而且做得更好。虽然我们对这一点实际上是保持怀疑态度的,但不得不否认Node.js确实是一项颠覆性的技术。它让JavaScript自AJAX以后迎来了第二次高潮,而且这次高潮明显盖过上一次。
下面我们通过一段简单的Node.js代码来快速的了解他的基本用法
var fs = require('fs');
fs.readFile('/etc/hosts',function(err,data){
if(err)throw err;
console.log(data.toString());
})
首先我们通过Node.js内置的require()函数来引入FS模块(它是Node.js内置的模块),同时创建了一个名为fs的变量。随后我们通过调用fs变量的readFile()函数来读取/etc/hosts文件,但此函数并没有返回值。实际上,Node.js会创建一个读取文件的事件,并立刻将该事件加入到事件队列中,当前线程并不会阻塞在这里。理论上后续不管有多少线程都会进来并产生一系列事件,这些事件会加入到同样的事件队列中,他们会在事件队列中进行循环,一旦某个事件被触发(比如文件读取成功),则会执行后面定义的回调函数(比如readFile()函数的第二个参数,回调函数一般是最后一个参数)。
可见Node.js完全利用了JavaScript的编程范式,采用回调函数来实现异步行为。但也有很多人不太习惯这种异步的API,往往跳同步API会让人觉得更加的自然,毕竟调用某函数拿到某返回值,这是非常容易理解的。
实际上,Node.js也提供了同步API。针对以上文件读取的示例,我们用同步API来实现,代码如下:
var fs = require('fs');
var data = fs.readFile('ect/hosts');
console.log(data.toString());
我们调用JS变量的readFileSync()函数可以通过同步的方式来获取文件的内容,此时不在出现回到函数,而是在调用API后直接拿到函数的返回值。
Node.js上手过程其实非常容易,代码风格也非常的优雅。既然这么好的武器,我们应该在那些场景下考虑使用它呢?
Node.js是针对“实时Web”应用程序而开发的,非常适合为了实时性较强且并发量较大的应用场景。
Node.js属于上手非常快的技术,学习他没有太高的难度,只要我们会写JavaScript,并且了解一些基本的编程思想就能快速使用了。但他所设计的面还是挺广的,如果深入掌握他,还是需要一个过程的。
为了让大家快速的学会使用Node.js,我们不放从安装开始。
官方网站提供了不同操作系统下的Node.js安装包,我们只需要根据自己的需求,下载对应的安装包即可。
当然,如果大家使用的是Mac操作系统,还可以使用更加简单的方法安装Node.js。我们可以安装一个名为Homebrew的软件用它来暗转Node.js程序,当然其他流行的命令行程序也能通过它来安装。
Hombrew官网:http://brew.sh/。
第一步,新建一个app.js的Node.js程序,代码如下:
var http = require('http');
var PORT = 1234;
var app = http.createServer(function(req,res){
res.writeHead(200,{'Content-Type':'text/html'});
res.wirte('<h1>Hellow</h1>');
res.end();
)}
app.listen(PORT,function(){
console.log('Server is run at %d',PORT);
})
首先我们通过Node.js内置的require()函数引入HTTP模块,该模块是开发WEB应用的核心模块。随后我们调用http对象的createServer函数来创建app对象。在该函数的参数中有一个function(req,res){…}回调函数,包含req请求参数与res响应参数。该函数用于处理所有的HTTP请求,我们只需要写入具体的数据到res响应对象中即可。最后需要调用app对象的listen()函数,并在响应的端口上启动Web应用,该函数同样也有一个回调函数,当Web应用启动后被调用。
需要注意的是,res对象的weiteHead()函数用于写入响应头(Response Head),write()函数用于写入响应体(Response Body),最后一定要使用end()函数用于发送数据写入完毕事件这样才能结束整个HTTP请求与响应过程。
第二步,可以通过命令来执行Node.js程序:
$ node app.js
随后我们可以在浏览其中输入一下地址来访问Node.js的WEB应用。
http://localhost:1234/
浏览器中将输出“hello”字样的HEML页面,至此一个简单的WEB应用就开发完毕了。
在Node.js应用运行过程中,如果我们修改了源文件,此时是无法生效的。因为Node.js为了确保高性能,在启动服务的时候就将代码加载到了内存中运行。修改了源文件对实际运行的效果没有任何影响,就算被删除了也是一样的。该特性确保了运行阶段的效率,但影响了开发阶段的效率。
Service Registry(服务注册表)是整个“微服务架构”中的“核心”,他不仅提供了Service Registry(服务注册)功能,同时也为Service Discovery(服务发现)功能提供了支持。服务注册很好理解,就是在服务启动后,将服务的相关配置信息(例如IP与端口)注册到服务注册表中。当客户调用这些服务时,将通过Service Gateway(服务网关)从服务注册表中获取这些服务配置,然后通过反向代理的方式去调用具体的服务接口,从服务注册表中获取服务配置的过程就是服务发现。
此外,服务注册表会定期检测已经注册的服务,若发现某些服务已经无法访问了,则将从服务注册表中移除掉,这个定期检查的过程被称为“心跳检测”。由此可见,服务注册表对“分布式数据一致性”的要求是想当高的,换句话说,服务注册表中的服务配置一旦变更了,通知机制必须做到高性能,且服务注册表本身还需要具备高可用。
ZooKeeper是服务注册表的最佳解决方案之一。
ZooKeeper字面上的意思就是动物园管理员的意思,ZooKeeper在软件世界里就是一名管理员,它被用来提供分布式环境下的协调服务。Yahoo公司使用java语言开发了ZooKeeper,它是Hadoop项目中的子项目,基于Google的Chubby的开源实现,在Hadoop、HBase、Kafka等技术中充当了核心组件的角色。他的设计目标就是将哪些复杂且容易出错的分布式一致性服务加一封装,构成一个高效且可靠的服务,并为用户提供了一系列简单易用的接口。
ZooKeeper是一个经典的分布式数据一致性解决方案,分布式应用程序可以基于它实现数据发布与订阅、负载均衡、命名服务、分布式协调服务与通知、集群管理、领导选举、分布式锁、分布式队列等功能。
ZooKeeper一般是以集群的方式对外提供服务,一个集群包括多个节点,每个节点对应一台ZooKeeper服务器,所有的节点共同对外提供服务。整个集群环境对分布式数据一致性提供了全面的支持,具体包括以下五大特性:
ZooKeeper内部拥有一个树状内存模型,类似于文件系统,有若干个目录,每个目录中也有若干个文件,只是在ZooKeeper中将这些目录和文件称为ZNode,每个ZNode有对应的路径及其包含的数据。ZNode可由ZooKeeper客户端来创建,当客户端与服务器端建立链接后,服务端将为客户端创建一个Session(会话),客户端对ZNode的所有操作均在这个会话中来完成。
ZNode实际上有四类节点,如下表:
ZNode类型 | 说明 |
---|---|
Persistent(持久节点) | 当会话结束后,该节点不会被删除 |
Persistent Sequence(持久顺序结点) | 当会话结束后,该节点不会被删除,且结点名中带自增数后缀 |
Ephemeral(临时结点) | 当会话结束后,该节点会被删除 |
Ephemeral Sequence(临时顺序结点) | 当会话结束后,该节点会被删除,且结点名中带自增数后缀 |
需要注意的是,持久性结点才能有子节点,这是ZooKeeper所限制的。
ZooKeeper使用这个基于内存的树状模型来存储分布式数据,正式因为将所有的数据都存放在内存中,所以才能实现高性能的目的,提高吞吐率。此外,这个树状模型还有助于集群环境下的数据同步,下面就来了解一下ZooKeeper的集群结构。
ZooKeeper并非采用经典的分布式一致性协议Paxos,而是参考了Paxos协议,设计了一款更加轻量级的协议,名为Zab(ZooKeeper Atomic Broadcast原子广播协议)。Zab协议分为两个阶段:Leader Election(领导选举)与Atomic Broadcast(原子广播)。当ZooKeeper集群启动时,将会选举出一台节点为Leader(领导),而其他节点均为Follower(追随者)。当Leader节点出现故障时,会自动选举出行的Leader节点并让所有节点恢复到一个正常的状态,这就是领导选举阶段。当领导选举阶段完毕后,将进入原子广播阶段,该阶段同步Leader节点与各个Follower节点之间的数据,确保Leader与Follower节点具有相同的状态。所有的写操作都会发送到Leader节点,并通过广播的方式将数据同步到其他Follower节点。
一个ZooKeeper集群通常由一组节点组成,在一般情况下,3~5个节点就可以组成一个可用的ZooKeeper集群,理论上,节点越多越好。
组成ZooKeeper集群的每个结点都会在内存中维护当前服务器的状态,并且每个结点之间都会保持相互的通信,目的就是告诉其他节点“自己还活着”。需要注意的是,只要集群中存在超过“半数以上”的结点可以正常工作,那么整个集群就能够正常的对外提供服务。因此,我们一般提供奇数个节点,比较节省资源。此外,ZooKeeper客户端可以选择集群中任意一个节点来建立链接,而一旦客户端与某个节点之间断开链接,客户端会自动链接到集群中的其他节点。
由于ZooKeeper是由Java语言开发的,因此在使用它之前,需要安装JDK运行环境,在生产环境下官方建议JDK需要1.6版本或以上,并建议使用Oracle发布的JDK,而并非开源社区的OpenJDK。虽然在Linux、Windows、Mac OSX操作系统上都可以使用ZooKeeper,不过官方建议大家使用Linux操作系统作为生产环境。
我们需要从ZooKeeper官方网站下载它的安装包,该安装包实际上就是一个压缩包,加压后可以使用。建议在生产环境下使用stable版本。
第一步:修改ZooKeeper配置文件
ZooKeeper提供了一份名为:zoo_sample.cfg的示例配置文件,我们必须在次基础上进行调整,才能成功运行ZooKeeper。
我们只需要复制conf目录下的zoo_sample.cfg文件,并将其命名为zoo.cfg即可,就像下面这样:
cp conf/zoo_sample.cfg conf/zoo.cfg
该配置文件中带有大量的注释,便于我们更加清晰的了解到这些配置是做什么的,将这些注释全部去掉之后,可以看到下面的5个重要配置:
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/tmp/zookeeper
clientPort=2181
下面我们对这些配置项进行解释:
第二步:启动ZooKeeper服务器
启动ZooKeeper服务器非常的简单,只需要执行ZooKeeper提供的脚本即可。
bin/zkServer.sh start
执行以上脚本,将在后台启动ZooKeeper服务器。此外,还可以使用start-foreground参数,用于在前台启动ZooKeeper服务器,此时我们将看到ZooKeeper的控制台,随后可以在控制台中看到许多重要的日志。
实际上,我们可以直接执行zkServer.sh脚本来获取相关的使用帮助。
bin/zkServer.sh
第三步,验证ZooKeeper服务是否有效
可以执行如下脚本来获取ZooKeeper的服务状态:
bin/skServer.sh status
我们以三个节点为例,搭建一个ZooKeeper集群环境。通过客户端链接任意一个节点,随后可做一些数据变更,并观察节点之间是否会进行数据同步。
节点可以分布在不同的机器上,当然也可以在本地搭建一个集群环境,只是需要使用不用的端口,以防止端口被占用而导致冲突。像这类在本地搭建的集群环境,并非真正意义上的“集群模式”,称为“伪集群模式”,其本质还是集群模模式,只不过在单机下单件而已。
为了搭建方便,下面我们在本地搭建一个为集群模式的ZooKeeper环境。
第一步:修改ZooKeeper配置文件
我们以第一个节点为例,配置如下:
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/tmp/zookeeper1
clientPort=2181
server.1=127.0.0.0:2888:3888
server.2=127.0.0.0:2889:3889
server.3=127.0.0.0:2890:3890
首先,我们指定了dataDir配置,表示ZooKeeper数据目录所存放的地方,需要注意的是,请不要在生产环境下使用/tmp目录。
随后,我们添加了一组server配置,表示集群中所包含的三个节点,需要注意的是,server配置需要满足一定的格式:
server.<id>=<ip>:<port1>:<port2>
下面我们对以上格式进行详细解释:
需要主要的是,在真正的集群环境中,clientPort,Port1,Port2可以配置的完全一样,因为集群中的每个节点都分布在不同的机器上,每个机器都拥有自己的IP地址,端口也不会被其他几点占用。
参照上面的做法,对其他两个结点也做同样的配置后,一个“伪集群”环境就搭建完毕了,它拥有“真集群”的所有特性。需要注意的是,对于“伪集群”环境而言,在每个ZooKeeper节点中,zoo.cfg配置文件中的dataDir与clientPort需要保持不同,对于“真集群”而言,dataDir、clientPort以及Port1和Port2都可以相同。
所有的结点都配置完毕了之后,我们即可以启动ZooKeeper集群。
第二步:启动ZooKeeper集群
与单机模式启动的方法相同,只需要一次启动所有的ZooKeeper节点即可启动整个集群。我们可以一个个手动去启动,当然,也可以写一个脚本去一次性启动。
第三步:检验ZooKeeper集群环境是否有效
同样可以通过zkServer.sh脚本与telnet命令来查看每个节点的状态,此时会看到“Mode:leader”或者“Mode:Follower”的信息,表明该节点是Leader还是Follower。
ZooKeeper提供了一系列的脚本程序,他们全部存放在bin目录下,例如:
我们可以使用zkCli.sh脚本轻松链接ZooKeeper服务器,实际上它就是一个命令行客户端。下面我们来学习如何使用zkCli.sh链接ZooKeeper服务器,并使用ZooKeeper提供的客户端命令来完成一系列的操作。
当ZooKeepe服务器正常启动后,我们可以使用ZooKeeper自带的zkCli.sh脚本,作为命令行客户端来链接ZooKeeper。使用方法非常的简单,若链接的是本地的ZooKeeper,则只需要执行一下脚本即可:
bin/zkCli.sh
若想在本地链接远程的ZooKeeper,则在zkCli.sh脚本中添加-server选项即可,例如:
bin/zkCli.sh -server <ip>:<port>
当通过命令行成功链接ZooKeeper后,我们就可以输入相关的命令来操作ZooKeeper了。有一个小技巧,当输入help命令(或者其他非法命令)后,将输出ZooKeeper相关客户端命令的帮助。
ZooKeeper官方提供了Java客户端的API,首先我们可以通过添加如下的maven依赖来获取ZooKeeper客户端jar包:
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.8</version>
</dependency>
随后我们写一段简单的代码,用于链接ZooKeeper,后面我们将基于此代码,针对ZooKeeper客户端的常用操作逐一进行探索。代码如下:
import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooKeeper; import java.util.concurrent.CountDownLatch; public class ZooKeeperDemo{ private static final String CONNECTION_STRING="127.0.0.1:2181"; private static final int SESSION_TIMEOUT=5000; private static CountDownLatch latch = new CountDownLatch(1); public static void mian(String[] args)throws Exception{ //链接ZooKeeper ZooKeeper zk = new ZooKeeper(CONNECTION_STRING,SESSION_TIMEOUT,new Watcher(){ @Override public void process(WatchedEvent event){ if(event.getState() == Event.KeeperState.SyncConnected){ latch.countDown(); } } }) latch.await(); //获取ZooKeeper客户端对象 System.out.println(zk); } }
我们需要创建一个ZooKeeper对象(对象名为zk),他的本质是对ZooKeeper会话的封装,后续的所有操作都在该会话对象上完成。
创建zk对象需要以下三个参数:
ZooKeeper(String connectString,int sessionTimeout,Watcher watcher);
由于建立ZooKeeper会话的过程是异步的,也就是说,当构造完成zk对象之后,线程将继续执行后续的代码,但此时会话可能尚未建立完毕。因此,我们需要使用CountDownLatch工具,当创建zk对象完毕后,立即调用latch.await()方法(关闭阀门),当使用线程处于等待状态,等待SyncConnected事件到来时,在执行latch.countDown()方法(打开阀门),此时会话已经创建完毕,接下来当前线程就可以继续执行后续的代码了。
现在zk对象已经创建完毕,已经成功链接ZooKeeper服务器,下面所有的操作将会在该会话中进行,我们只需要调用zk对象的相关API即可。
我们在NPM官网上,如果输入“Zookeeper”关键字,将会看到大量的ZooKeeper客户端,业界评价比较好的是node-zookeeper-client,他的API命名习惯于java的非常类似,下面我们就对其基本用法做出简单介绍。
首先我们通过NPM安装note-zookeeper-client模块:
npm install node-zookeeper-client
随后我们写一段简单的代码,用于链接ZooKeeper,后面我们将基于该代码,这对ZooKeeper客户端的常用操作逐一进行探索。代码如下:
var zookeeper = require('node-zookeeper-client');
var CONNECTION_STRING = 'localhost:2181';
var OPTIONS = {
sessionTimeout:5000
};
var zk = zookeeper.createClient(CONNECTION_STRING,OPTIONS);
zk.on('connected',function(){
console.log(zk);
zk.close();
});
zk.connect();
我们需要加载node-zookeeper-client模块,并创建ZooKeeper客户端对象(对象名为zk),此时需要传入“链接字符串”与“相关选项”(包括“会话超时时间”)。随后需要监听connected事件,并调用zk.connect()方法来链接ZooKeeper服务器。当触发connected事件时,说明客户端已经链接成功了ZooKeeper服务器,在回调函数中可以将zk对象输出至控制台,我们此时可以运行一下该程序并观察控制台中zk对象的输出情况。
若zk对象可以正常输出,说明可以连接ZooKeeper服务器并建立了正常的会话,下面所有的操作将在该会话中进行,我们只需要调用zk对象的相关API即可。与java客户端API不同的是,Node.js客户端仅提供了异步方式,我们不能通过同步来调用他。
服务注册组件即Service Registry(服务注册表),他内部有一个数据结构,用于储存已发布服务的信息。
通过ZooKeeper的学习,我们可以了解到,他内部提供了一个基于ZNode节点的树状模型,根节点为“/”,我们可以在根节点下方扩展任意的子节点,其子节点也分为四种创建模式,包括:持久节点、持久顺序结点、临时结点、临时顺序结点。
似乎可以借助ZNode树状模型来存储服务配置,那么应该如何设计呢?
我们不妨先定义一个根节点,由于根节点下会有其他子节点,因此根节点一定是持久节点的(ZooKeeper只有持久节点才会有子节点),而且根节点还必须只有一个。
我们再根节点下可以添加若干子节点,可以用服务名称作为这些节点的名称。为了便于描述,不妨将此类节点称为“服务节点”。此外,为了服务的高可用性,我们可能发布多个相同功能的服务,因此服务注册表中会存在一些同名的服务,但是服务节点又不允许重名的(这是ZNode树状节点限制的),因此我们要在服务节点下在添加一级节点,所以服务节点也是持久的。
我们再来分析下服务节点下面的子节点,他们实际上都对应了某一特定的服务,我们需要将服务配置存放在该节点中。简单情况下,服务配置中可以存放服务的IP与端口号。为了便于描述,我们不妨将该节点称之为“地址节点”。一旦某个服务成功注册到了ZooKeeper中,ZooKeeper服务器就会与该服务所在的客户端进行心跳检测,如果某个服务出现了故障,心跳检测就会失效,客户端将自动断开与服务端的对话。对应的节点也及时从ZNode树状模型中删除,然而如果注册了多个相同的服务,这样的地址节点就可能会有多个,因此地址节点必须为临时且顺序的。
我们使用SpringBoot开发了许多服务,每个服务都是以jar包的形式存在,可将这些jar包部署到不同的服务器上面,并通过java -jar的命令来运行这些服务。当服务启动后,会将自身的配置信息注册到“服务注册表”中。所有的客户端请求都会进入“服务网关”,服务网关首先从服务注册表中根据当前请求中的服务名称来获取对应的服务配置(该过程成为“服务发现”),随后服务网关通过服务配置直接调用已发布的服务(该过程称之为“反向代理”)。
这样的架构看起来不错,但是我们发现维护并管理每个服务会带来巨大的成本。比如,我们每次发布一个服务都必须做三件事情:编译、打包、部署。这三件事看似容易,实际上却想当的繁琐,尤其是服务数量较多的场景,其实我们想要的只是一个可运行的jar包而已。再比如,在微服务架构中,每个服务可能由不同的编程语言来实现,运行服务还需要不同的环境,想要让服务跑起来,必须先安装支持他的运行环境,然而安装环境往往比运行服务更加的繁琐。
面对这些问题,我们需要想办法将服务及运行环境加以封装,并确保将这个封装后的产物作为我们的交付物,这个交付物可随时构建、装载、运行。Docker正是为此而生的。
Docker在英语里面是“码头工人”的意思,大家可以想象,码头上有很多的工人,他们正在忙于装载货物。首先将货物放入集装箱中,然后将集装箱放在货船上,货船将这些集装箱以及其他的货物送到指定的目的地。
在2013年,dotCloud公司发布了一款名为Docker的开源软件,仅花了一年左右的时间,Docker几乎动摇了传统虚拟化技术的统治地位,越来越多的公司开始逐步使用Docker来替换现有的虚拟化技术。正式因为Docker太红了,就连dotCloud公司也因此而改名为Docker公司了,并给予Docker推出了一系列的相关生态产品。比如Docker Engine、Docker Machine、Docker Toolbox、Docker Compose、Docker Hub、Docker Registry、Docker Swarm、Docker Notary、Docker Cloud、Docker Store等。
Docker源码地址:https://github.com/docker/docker
Docker的图标就很生动的表达了他的含义,是一直可爱的鲸鱼,拖着许多的集装箱,漂浮在云上。在Docker的世界中,这只鲸鱼就是Docker Engine(Docker引擎),上面一个个的集装箱就是Docker容器(Docker Container),Docker引擎可以运行在基于Docker的云平台(Docker Cloud)上。
这里有一些概念,做一下解释:
Docker本质上为我们提供了一个“沙箱(Sandbox)”环境,它能将应用程序进行封装,并提供了与虚拟机相似的隔离性,但这种隔离性是想当轻量的。那么虚拟机与Docker有什么区别呢?一起来讨论下。
当我们需要在宿主机上运行一个虚拟操作系统时,首先需要安装一个虚拟机软件,常用的虚拟机软件比如Oracle VirtualBox或者VMware等,随后我们可以使用虚拟镜像文件,在虚拟机上安装虚拟操作系统。此时,虚拟软件需要模拟硬件与网络资源,会占用大量的系统开销。一般情况下,在一台普通的服务器上,最多只能启动十几个虚拟机,而且虚拟机的启动一般要几分钟甚至更长时间。
若我们使用Docker虚拟化技术,则只需要在宿主机上安装一个Docker引擎,随后可以从Docker镜像仓库中下载所需的Docker镜像,并启动相应的Docker容器。此时,Docker引擎完全利用宿主机硬件与网络资源,占用的系统开销较少。一般情况下,在一台普通的服务器上,可以启动上千个Docker容器。
Docker是通过在底层上封装了Linux容器技术(LXC)来实现的,换句话说,Docker没有创造出任何新的技术,仅仅只是“新瓶装老酒”而已。下面归纳了Docker的四大特点:
我们使用Git管理代码,使用Maven构建项目,使用Docker封装服务,这些事情都需要手工的方式去一步步的执行,能否有快捷的方式呢?Jenkins就是这中快捷方式
在软件行业发展中,持续集成(Continuous Integration,简称CI)是利用一系列的工具、方法与规则,快速的构建代码,并自动的进行测试,从而完成代码开发的效率和质量。Jenkins是一款持续集成软件,拥有简单的安装、开箱即用、易于管理、易于维护、插件扩展等特性。只需要一个java的运行环境,就能将Jenkins跑起来,可以通过图形化界面为每个项目创建对应的构建任务(也称为“构建作业”)。Jenkins可以连接我们的代码仓库系统,从中获取源码并自动完成构建,当创建完毕后,还能执行一些后续的任务,比如:生成单元测试报告、归档程序包、部署程序包到Maven仓库、记录文件电子指纹、发送邮件通知等一系列的操作。为提高持续集成的执行效率,Jenkins支持主从(Master-Slave)运行模式,一台Master机器可以控制多台Slave机器,构件任务可并行在多台Slave机器上执行。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。