轻量级微服务架构

微服务架构设计概述

自从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中,就像是巨石一块,无法拆解,我们所做的水平扩展也只是在扩展一块块的巨石。为了便于表达,我们不妨将单块架构搭建起来的应用简称为“单块应用”。
我们再部署单块应用的时候,同样也会遇到很多的麻烦,比如:

  • 修改了一个Model(可能只是修改了一行代码),就需要重新部署整个应用。
  • 部署整个应用所消耗的时间与对系统带来的开销都是非常多的。

此外,对于Java Web应用而言,打包war包里的代码一般是class 文件,这也就意味着,我们的单块应用只是基于java语言开发的,无法将应用中某个单个的Model通过其他开发语言来实现(假如我们不考虑JVM上运行动态语言的情况下),也许其他语言开发实现某个模块更加合适,这样就会产生技术选型的单一问题。
综上所述,传统应用架构存在以下问题:

  • 系统资源浪费
  • 部署效率太低
  • 技术选型单一

当然,传统应用架构的问题还远不止这些。当业务越来越复杂时,应用会变得越来越臃肿,“身材”越来越“胖”而无法瘦身。于是,人们找到了新的思路来解决传统应用架构的问题,这就是微服务架构。
那么微服务架构与传统应用架构的区别到底在哪?我们继续探讨。

微服务架构是什么

微服务架构从字面上来理解就是:许多微小服务搭建起来的应用架构。
这句话涉及到很多问题,我们逐一讨论,比如:

  • 服务多“微”才能叫微服务?
  • 如何管理越来越多的微服务?
  • 客户端是怎样调用微服务的?

带着这些问题,开始下面的讨论,首先我们来看如何定义微服务架构。

微服务架构概念

当马丁大神提出微服务架构这个概念的时候,同时他也对微服务架构提出了几条要求,也就是说,当我们的应用满足一下条件时,才能称之为微服务架构,具体包括:

  • 根据业务模块划分服务种类;
  • 每个服务可以独立部署且相互隔离
  • 通过轻量级API调用服务
  • 服务保证良好的高可用性

我们简单的分析一下:首先根据产品的业务功能模块来划分服务种类,也就是说,我们需要按照业务功能去划分种类,这是“垂直划分”;而在代码层面上进行划分,这是“水平划分”。每个服务可以独立部署,还需要相互隔离,也就是说,服务之间是没有任何干扰的,可将每个服务放入到独立的程序中运行,因为进程之间是完全隔离的。客户端通过轻量级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/。

微服务架构有哪些特点和挑战

微服务架构相对于传统的架构有着显著的特点,同时微服务架构也给我们带来了一定的挑战,我们先从他的特点说起。

微服务架构的特点

  1. 微小度颗粒
    微服务的粒度是根据业务功能来划分的,对于某些复杂的业务来说,可能粒度较大,相对于简单的业务而言,可能粒度较小。总之,微服务的粒度可大可小,但往往我们希望他尽可能的小,但又不希望微服务之间有直接的依赖,因此粒度的划分是一件非常考验架构师水平的事情。
  2. 责任单一性
    我们需要确保每个微服务只做一件事情,也就是我们经常提到的“单一职责原则”,该原则对微服务的划分提供了指导方针。如果我们将一个服务提供多个API,那么就必须做到每个API职责单一性。
  3. 运行隔离性
    每个服务相互隔离,且互不影响。也就是说,每个服务运行在自己的进程中。众所周知,进程之间是隔离的,是安全的,而进程内部或线程之间的资源是共享的。换句话说,一个服务出了问题,不会影响到其他服务。
  4. 管理自动化
    随着业务功能不断增多,服务的数量也会逐渐增加,我们需要对服务提供自动化部署与监控预警的能力,这样才能更加高效的管理这些服务。需要注意的是,我们必须借助自动化技术,才能确保管理服务更加的容易。

微服务的架构特点非常明显,可能还有很多,但同时微服务架构也给我们带来了许多挑战。

微服务架构的挑战

  1. 运行要求较高
    运维工程师除了需要掌握使用自动化技术来部署微服务,还需要对整个微服务系统进行有效的监控,并保障系统的高可用性。可见,微服务架构的引入会带来运维成本的上升。
  2. 分布式复杂性
    微服务架构的本质还是一个分布式架构,每个服务可以部署在任意的机器上。对于分布式系统而言,网络延迟、系统容错、分布式事务等问题都会给我们带来很大的挑战。
  3. 部署依赖较强
    对于业务复杂的情况,可能存在多个服务来共同完成一件事情,服务之间虽然没有相互调用,但可能会有调用的顺序要求。业务上的依赖性导致了部署的依赖性,从而在某一时间点,同一服务可能具备多个版本。
  4. 通信成本较高
    既然微服务是隔离在自己进程中运行的,那么从客户端调用微服务,需要跨进程进行调用,而进程间的调用一定比进程内的调用更加消耗资源,从而带来通信成本上的开销。

如何搭建微服务架构

可见,微服务架构的要求还是想当高的,不仅仅对技术,而且对运维都有很高要求,我们需要设计出一款简单易用的轻量级微服务架构来满足自身的需求。下面用一张图来描述一下我们对微服务架构的愿景。

微服务架构图

我们不妨从下往上来理解这张图。底层部署了一系列的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/。
我们通过一张图来归纳一下微服务架构的技术选型

  1. 使用Jenkins部署服务
  2. 使用SpringBoot开发服务
  3. 使用Docker封装服务
  4. 使用ZooKeeper注册服务
  5. 使用Node.js调用服务

除了上述的技术选型外,实际上还有其他可选的方案,比如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遇上“微服务”之后,让我们更加意识到,微服务的春天已经悄悄来到了。

Spring Boot是什么

SpringBoot是为生产级Spring应用而生的,他使得开发Spring应用程序更加高效、简洁。那么,SpringBoot具备哪些生产级特性呢?我们不妨从它的由来开始讲起。

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的所有特性。

Spring Boot 特性

  1. 可创建独立的Spring应用程序
    使用SpringBoot所创建的应用程序都是一个个独立的jar包,而并非war包,即使是war应用也是jar包,这方面似乎带有一点颠覆性的味道。我们可以直接运行带有@SpringBootApplication注解的main()方法就能运行一个Spring应用程序,实际上是在SpringBoot应用程序内部嵌入了一个WebServer而已。但这些并不能说明SpringBoot就不能以war包的形式部署到Web Server中了,我们同样可以使用SpringBoot来开发传统的javaweb应用。
  2. 提供嵌入式的Web Server(无需部署war包)
    我们不在将war包部署到WebServer中,而是启动SpringBoot应用程序后,会在默认端口8080下启动一个嵌入式的tomcat,也可以在SpringBoot提供的application.properties配置文件中配置具体的端口号。当然,除了Tomcat,SpringBoot还提供了Jetty、Undertow等嵌入式的Web Server,我们可以根据实际情况,自行在Maven配置文件中添加相关的Web Server的插件,SpringBoot插件体系十分的广泛。
  3. 无任何代码生成技术也无任何XML配置
    在某些开源框架中,会使用字节码生成技术(例如CGLib、Javassist、ASM等),在程序运行时动态的生成class文件并将其加载到JVM中,我们称这种行为叫做“代码生成技术”,在Spring Boot中没有使用任何代码生成技术。此外,SpringBoot不像传统Spring应用那样配置大量的XML文件,除了使用一个application.properties配置文件,SpringBoot再无其他配置文件了,而且所有插件相关的配置也在这个配置文件中。
  4. 自动化配置
    Spring Boot的配置都在application.properties文件中,但并不意味着在SpringBoot应用就必须包含该文件。实际上,配置文件中包含了大量的配置项,而许多配置项都有默认值,很多配置项我们其实都不用去修改,使用其默认值就行,这类行为叫做“自动化配置”,我们只需要使用SpringBoot提供的相关注解就能启动具体特性。这一特性实际上是由SpringBoot提供的一些列@ConditionalOnXxx条件注解来实现的,而底层使用了Spring4.0的Condition接口。
  5. 提供一些列生产级特性
    SpringBoot是为了生产级Spring应用而生的,提供了大量的生产级特性,例如核心指标、健康检查、外部配置等,这类技术对微服务架构想当有价值。例如,核心指标的是我们可以随时给SpringBoot发送/metrics请求,随后可以获取一个JSON数据,包括内存、Java堆、类加载、处理器、线程池等信息。我们还能再Java命令行上直接运行SpringBoot应用,并带上外部配置参数,这些参数将覆盖已有的默认值配置参数。甚至我们还能通过发送一个URL请求去关闭SpringBoot应用,在自动化技术中会有一定的帮助。
  6. 提供开箱即用的Spring插件
    SpringBoot提供了大量“开箱即用”的插件,我们只需要添加一段Maven依赖配置即可开启使用。这些插件在SpringBoot的世界里有一个优雅的名字,叫做Starter。每个Starter可能都会有自己的配置项,而这些配置项都是可以在application.properties文件中统一配置。
    SpringBoot是一个典型的“核心+插件”的系统架构,核心包含Spring最核心的功能,其他更多的功能都是通过插件的方式来扩展。那么SpringBoot拥有哪些插件呢?

SpringBoot相关插件

SpringBoot官方提供了大量插件,涉及面非常的广,包括Web、SQL、NoSQL、安全、验证、缓存、消息队列、分布式事物、模板引擎、工作流等,还提供了Cloud、Social、Ops方面的支持。
此外,SpringBoot对某项技术提供了多种选型,比如:

  • SQL API ------- JDBC、JPA、JOOQ等;
  • 关系数据库------MySQL、PostgreSQL等;
  • 内存数据库------H2、HSQLDB、Derby等;
  • NoSQL数据库-----Redis、MongoDB、Cassandra等;
  • 消息队列-------RabbitMQ、Bitronix等;
  • 模板引擎------Velocity、Freemarker、Mustache等。

官方还提供了一个名为“Spring Initialzr”的在线代码生成器,我们只需选择自己想要的插件,就能一件下载对应的代码框架。就连IDEA也支持了Spring Initialzr。
SpringBoot拥有非常强大的插件体系,如此之多的插件,让我们在开发应用程序的时候如虎添翼,我们可以优先从这个“插件库”中选择插件,如果有的插件不够用或者不合适,我们还可以实现自己想要的插件。
SpringBoot所提供的功能强大且实用,但SpringBoot并非适合开发所有应用场景。那么那些场景比较适合使用SpringBoot呢?

SpringBoot的应用场景

  1. 传统的WebMVC架构
    传统的WebMVC架构比较适合用SpringBoot来开发,View层可以使用JSP或者模板引擎(例如Velocity),从View层发送请求到Spring的controller,通过操作数据库并将获取的数据封装进Model,最后将Model返回到View中。总之,SpringMVA能够做到的,SpringBoot都能做到,因为SpringBoot在更高层对SpringMVC进行了封装。
  2. 前后端分离架构
    在前后端分离的架构中,后端可基于SpringBoot开发REST API,前端通过REST API来获取JSON数据,从而进行视图渲染,生成最终的HTML界面。实际上,移动端H5应用我们也可以采用类似的方式来实现。在前后端分离的架构中,可能会遇到“跨域问题”,SpringBoot对跨域问题也做了非常好的支持。
  3. 微服务架构
    微服务架构要求我们对产品功能进行细粒度划分,且每个微服务之间需要使用轻量级技术进行通信,因此每个微服务需要对外提供轻量级API接口(例如REST API),使用SpringBoot发布REST API是最方便的。此外,SpringBoot还拥有一系列的生产级特性,他与微服务是天作之和。

现在大家应该了解到SpringBoot是什么以及他可以做什么了,下面通过几个简单的示例来展示一下SpringBoot的基本用法,目标是让大家快速上手。

如何使用SpringBoot框架

不仅对SpringBoot框架,其实学习任何框架的第一步都是搭建开发环境,然后尝试写一个“helloworld”应用程序并试图让他跑起来,最后才去探索他的若干特性。我们现在就来搭建一个SpringBoot开发框架,充分体验一下SpringBoot给我们开发带来的快乐。

搭建SpringBoot开发框架

  • 使用IDEA创建一个Maven项目
    我们再IDEA中创建一个Project,名称为msa-hello,对应的Maven坐标为:
  • groupId:demo.msa
  • artifactId:msa-hello
  • version:1.0.0

当然,大家也可以按照自己的喜好来命名,不一定非要与文中相似。
在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”应用程序来讲解。

开发一个简单的SpringBoot应用程序

由于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类并不是一个普通的类,他必须拥有一下两个特点:

  1. 类名上有@SpringBootApplication注解,表示他是一个SpringBoot应用。
  2. 类名中包含一个main()方法,且通过SpringApplication类的run()方法运行该类。

下面我们做一个简单的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,我们只需要做三件事情:

  1. 在类名上添加@RestController注解,表示它具备发布REST API的能力,还能将每个REST API的返回值自动序列化为JSON格式。
  2. 在类中加一个hello()方法,并通过@RequestMethod注解定义REST API的请求信息,包括请求类型与请求路径。
  3. 完成hello()方法,目前是返回一个简单的“hello”字符串,实际上返回任何java对象,且返回的java对象被JSON序列化后返回客户端。

当然,如果我们只考虑“单一职责原则”,那么应该将@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
  • lib
  • META-INF
  • org

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应用程序非常简单,我们可以根据实际情况,自由的选择使用一下三种方法来运行SpringBoot应用程序。

  1. 在IDEA中直接运行
    直接在IDEA中执行HelloApplication类来启动SpringBoot应用程序。
    该方式有利于程序的debug,在日常开发过程中优先使用这种方式,但debug方式肯定比run方式启动的慢一些。

  2. 使用Maven运行
    可以使用Maven命令来运行

    mvn spring-boot:run

    以上就用到了spring-boot-maven-plugin插件,我们运行的是spring-boot插件的run目标,此外他还提供了spring-boot:start与spring-boot:stop目标。

  3. 使用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的生产级特性

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文件中配置。一下给一些实际的操作。

  1. 关闭metrics端点

    endpoints.metrics.enabled=false

    在浏览器访问/metircs的时候,看到的将是“Whitelable Error Page"的错误页面,对应的HTTP状态码为404(not found)。

  2. 关闭所有端点,仅开启metrics端点

    endpoints.enable=false
    endpoints.metrics.enabled=true

    现在只有metrics端点是启用的,访问其他的端点会报错。

  3. 修改metrics端点的名称

    endpotins.metrics.id=performance

    这样我们就可以通过/performance请求来访问以前的mertics端点了,此时继续发送/mertics,会看到报错信息。

  4. 修改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配置项的默认值,可以先从以下位置获取:

  1. Java命令行参数。
  2. JNDI属性。
  3. JAVA系统属性。
  4. 操作系统环境变量。
  5. jar包外的application.properties配置文件。
  6. jar包内的application.properites配置文件。
  7. @PropertySource注解。
  8. SpringApplication.setDefaultProperties默认值。

以“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是什么

从字面上的意思来看,Node.js更像是一款JavaScript框架,其实不完全是这样的,我们千万不要被她的名字所误导。
Node.js官网
在这里插入图片描述
在首页上我们就可以清楚的看到对Node.js的诠释:

Node.js是一个基于ChromeV8引擎的JavaScript运行环境,它使用了一个“事件驱动”且“异步非租塞I/O”的模型使其轻量且高效,Node.js的包管理器NPM是全球最大的开源库生态系统。

针对Node.js的定义,我们想稍微补充说明一下:

  1. Node.js是一个运行环境,并非JavaScripte类库或者框架
  2. Node.js是基于ChormeV8引擎开发的,改引擎是业界公认的高性能JavaScript引擎(没有之一)。
  3. Node.js提供了“事件驱动”模型,可以将当前事件加入到事件队列中去轮询。
  4. Node.js提供了“异步非阻塞I/O”模型,它比传统的“同步阻塞式I/O”模型具备更高的吞吐率。
  5. Node.js的NPM与java的Maven异曲同工,但生态圈似乎更加庞大(官方号称)。

此外,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快速入门

下面我们通过一段简单的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的应用场景

Node.js是针对“实时Web”应用程序而开发的,非常适合为了实时性较强且并发量较大的应用场景。

  1. I/O密集型Web应用
    我们日常看到的应用一般分为两大类,即“CPU密集型”和“I/O密集型”。前者对CPU要求较高,需要一个强大的计算过程,需要较多的CPU核心来完成具体的业务,比如股票交易系统、数据分析系统等。后者更加偏重于对I/O的要求,常会有频繁的网络传输或者磁盘存储等现象,比如高并发网站、实时Web系统等。
    由于Node.js采用的是单线程模型,肯定不适合做CPU密集型的应用,否则CPU资源将被长期消耗,从而影响整个系统的吞吐率。因此开发I/O密集型才是Node.js的强项,它充分利用了事件驱动与异步非阻塞技术,能支持大量的并发链接,从而提高整个系统的吞吐率。
    尤其在Web方面,Node.js有着强大的优势,他内置了一个HTTP服务器(实际上是一个HTTP模块),性能与稳定性方面都与流行的Nginx不分伯仲(业界有人做过这方面的测试)。此外,基于Node.js的模块体系非常强大,其中不乏优秀的Web框架,Express就是这样的框架,它将基于Node.js的Web应用开发过程变得非常的简单与高效,下文我们也会了解到他。
    Express官网
  2. Web聊天室
    Node.js是为实时性而生的,Web聊天室正符合这类实时性的要求。使用Node.js集成Socket.IO可以轻松的搭建一个Web Socket服务器,此外,Socket.IO也提供了客户端JS类库,我们可以在短时间内开发一套Web聊天室。
    Socket.IO官网
    当我们打开浏览器,进入Web聊天室时,客户端会主动与服务器建立一个WebSocket链接,而且这是一个长链接。在同一时段内,可能会有很多人进入聊天室,此时会有多个客户端与服务器建立WebSocket链接。当某人输入一段文字,然后单击“发送”按钮,此时会将消息通过WebSocket协议发送服务端。当服务端收到消息后,立即通过WebSocket广播的方式,将消息推送到所有已经建立链接的客户端。
    在服务器端处理客户端发送过来的大量消息时,会利用Node.js的异步特性,当收到消息后,将产生一个事件并将其加入到队列中,同时立刻返回客户端,当事件触发后,立即将消息广播到所有的客户端。
  3. 命令行工具
    使用Node.js可以轻松开发命令行工具,我么可以写一段Node.js程序,通过NPM提供的命令将其安装到操作系统中,随时可以在命令控制行控制台上输入该命令来运行Node.js程序。
  4. HTTP代理服务器
    Node.js可以通过异步的方式处理大量的并发请求,他可以作为服务端应用程序的代理,起到充当HTTP代理服务器的作用,类似于Nginx、Apache等。
    在Node.js的NPM中同样存在这样的HTTP代理模块,我们只需要将其引入进来,并使用该模块提供的API,就能快速的搭建一个HTTP代理服务器。

如何使用Node.js

Node.js属于上手非常快的技术,学习他没有太高的难度,只要我们会写JavaScript,并且了解一些基本的编程思想就能快速使用了。但他所设计的面还是挺广的,如果深入掌握他,还是需要一个过程的。
为了让大家快速的学会使用Node.js,我们不放从安装开始。

安装Node.js

官方网站提供了不同操作系统下的Node.js安装包,我们只需要根据自己的需求,下载对应的安装包即可。
当然,如果大家使用的是Mac操作系统,还可以使用更加简单的方法安装Node.js。我们可以安装一个名为Homebrew的软件用它来暗转Node.js程序,当然其他流行的命令行程序也能通过它来安装。
Hombrew官网:http://brew.sh/。

使用Node.js开发WEB应用

第一步,新建一个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字面上的意思就是动物园管理员的意思,ZooKeeper在软件世界里就是一名管理员,它被用来提供分布式环境下的协调服务。Yahoo公司使用java语言开发了ZooKeeper,它是Hadoop项目中的子项目,基于Google的Chubby的开源实现,在Hadoop、HBase、Kafka等技术中充当了核心组件的角色。他的设计目标就是将哪些复杂且容易出错的分布式一致性服务加一封装,构成一个高效且可靠的服务,并为用户提供了一系列简单易用的接口。
ZooKeeper是一个经典的分布式数据一致性解决方案,分布式应用程序可以基于它实现数据发布与订阅、负载均衡、命名服务、分布式协调服务与通知、集群管理、领导选举、分布式锁、分布式队列等功能。
ZooKeeper一般是以集群的方式对外提供服务,一个集群包括多个节点,每个节点对应一台ZooKeeper服务器,所有的节点共同对外提供服务。整个集群环境对分布式数据一致性提供了全面的支持,具体包括以下五大特性:

  1. 顺序性
    从同一个客户端发送的请求,最终将会严格按照其发送顺序进入到ZooKeeper中。可见,这就像一个队列,拥有“先进先出”的特性,也就确保了请求的顺序性。
  2. 原子性
    所有请求的响应结果在整个分布式集群环境中具备原子性,也就是说,要么在整个集群中所有机器都成功的处理了某一个请求,要么就都没有处理,绝对不会出现集群中部分机器处理了某一个请求,而另一部分机器却没有处理的情况,这方面的要求与事务的原子性是一样的。
  3. 单一性
    无论客户端链接到哪个ZooKeeper服务器,每个客户端所看到的的服务端数据模型都是一致的,不可能出现两种不同的数据状态。实际上每台ZooKeeper服务器之间会进行数据同步的,而这个同步过程是相当高效的。
  4. 可靠性
    一旦服务数据状态发生了变化,就会立刻存储起来,除非此时又有另一个请求对其进行了变更,否则数据一定是可靠的。
  5. 实时性
    当某个请求被成功处理了之后,客户端能够立即获取服务端的最新数据状态,整个过程具备实时性。

ZooKeeper树状模型

ZooKeeper内部拥有一个树状内存模型,类似于文件系统,有若干个目录,每个目录中也有若干个文件,只是在ZooKeeper中将这些目录和文件称为ZNode,每个ZNode有对应的路径及其包含的数据。ZNode可由ZooKeeper客户端来创建,当客户端与服务器端建立链接后,服务端将为客户端创建一个Session(会话),客户端对ZNode的所有操作均在这个会话中来完成。
ZNode实际上有四类节点,如下表:

ZNode类型说明
Persistent(持久节点)当会话结束后,该节点不会被删除
Persistent Sequence(持久顺序结点)当会话结束后,该节点不会被删除,且结点名中带自增数后缀
Ephemeral(临时结点)当会话结束后,该节点会被删除
Ephemeral Sequence(临时顺序结点)当会话结束后,该节点会被删除,且结点名中带自增数后缀

需要注意的是,持久性结点才能有子节点,这是ZooKeeper所限制的。
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

由于ZooKeeper是由Java语言开发的,因此在使用它之前,需要安装JDK运行环境,在生产环境下官方建议JDK需要1.6版本或以上,并建议使用Oracle发布的JDK,而并非开源社区的OpenJDK。虽然在Linux、Windows、Mac OSX操作系统上都可以使用ZooKeeper,不过官方建议大家使用Linux操作系统作为生产环境。

运行ZooKeeper

我们需要从ZooKeeper官方网站下载它的安装包,该安装包实际上就是一个压缩包,加压后可以使用。建议在生产环境下使用stable版本。

  1. 第一步:修改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

    下面我们对这些配置项进行解释:

    • tickTime:称之为“滴答时间”,用于配置ZooKeeper中最小时间单元长度,实际上ZooKeeper中很多运行时间的时间间隔都是使用tickTime的倍数来表示的。例如,ZooKeeper中会话的最小超时时间默认两倍的tickTime。该配置的默认值是3000,单位为毫秒。
    • initLimit:用于配置Leader节点等待Follower节点启动并完成数据同步的时间。Follower节点在启动过程中会与leader节点建立链接并完成数据同步,从而确定自己对外提供服务的起始状态,Leader节点允许Follower节点在initLimit时间内完成这个工作。该配置的默认值为10,即10*tickTime。通常情况下,我们不太注意这个配置项,这个使用默认值即可。如果随着ZooKeeper集群管理的数量不断增大,Follower节点在启动的时候,从Leader节点上进行数据同步的时间也会相应变长,于是无法在短时间内完成数据同步,在这种情况下,有必要适当调大这个参数。
    • syncLimit:用于配置Leader节点和Follower节点之间进行“心跳检测”的最大延时时间。在ZooKeeper集群运行过程中。Leader节点会与所有的Follower节点进行心跳检测来确定该节点是否存活。如果Leader在syncLimit时间内无法获取Follower节点的心跳检测响应,那么Leader节点就会认为该Follower节点已经脱离了与自己的同步,该配置的默认项为5,即5*tickTime。
    • dataDir:用于配置当前ZooKeeper服务器存储快照的文件的目录,不建议将其指定到/tmp目录下,因为该目录的文件可能被自动删除。在ZooKeeper集群环境中。将生成一个名为myid的文件,该文件用于存放ZooKeeper集群节点的ID,我们需要保证在整个集群环境中,整个ID是唯一的。
    • clientPort:用于配置当前ZooKeeper服务器对外暴露的端口,客户端会通过该端口在ZooKeeper服务器上建立链接并创建会话,一般设置为2181。每台ZooKeeper服务器都可以配置任何可用的端口,实际上,集群中的所有服务器也无需使用相同的clientPort。
  2. 第二步:启动ZooKeeper服务器
    启动ZooKeeper服务器非常的简单,只需要执行ZooKeeper提供的脚本即可。

    bin/zkServer.sh start

    执行以上脚本,将在后台启动ZooKeeper服务器。此外,还可以使用start-foreground参数,用于在前台启动ZooKeeper服务器,此时我们将看到ZooKeeper的控制台,随后可以在控制台中看到许多重要的日志。
    实际上,我们可以直接执行zkServer.sh脚本来获取相关的使用帮助。

    bin/zkServer.sh

  3. 第三步,验证ZooKeeper服务是否有效
    可以执行如下脚本来获取ZooKeeper的服务状态:

    bin/skServer.sh status

使用ZooKeeper搭建集群环境

我们以三个节点为例,搭建一个ZooKeeper集群环境。通过客户端链接任意一个节点,随后可做一些数据变更,并观察节点之间是否会进行数据同步。
节点可以分布在不同的机器上,当然也可以在本地搭建一个集群环境,只是需要使用不用的端口,以防止端口被占用而导致冲突。像这类在本地搭建的集群环境,并非真正意义上的“集群模式”,称为“伪集群模式”,其本质还是集群模模式,只不过在单机下单件而已。
为了搭建方便,下面我们在本地搭建一个为集群模式的ZooKeeper环境。

  1. 第一步:修改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>

    下面我们对以上格式进行详细解释:

    • id:表示节点编号,表示该节点在集群中的唯一编号,取值范围是1~255之间的整数。需要注意的是,我们必须在dataDir目录下创建一个名为myid的文件,其内容为该节点的编号。例如,针对第一个节点而言,我们需要创建一个/tmp/zookeeper1/myid文件,该文件内容为1,其他节点也需要同样的操作。
    • ip:表示该节点所在的IP地址,本地为127.0.0.0或localhost
    • port1:表示Leader节点与Follower节点进心跳检测与数据同步时所使用的端口。
    • port2:表示进行领导选举过程中,用于投票通讯的端口。

    需要主要的是,在真正的集群环境中,clientPort,Port1,Port2可以配置的完全一样,因为集群中的每个节点都分布在不同的机器上,每个机器都拥有自己的IP地址,端口也不会被其他几点占用。
    参照上面的做法,对其他两个结点也做同样的配置后,一个“伪集群”环境就搭建完毕了,它拥有“真集群”的所有特性。需要注意的是,对于“伪集群”环境而言,在每个ZooKeeper节点中,zoo.cfg配置文件中的dataDir与clientPort需要保持不同,对于“真集群”而言,dataDir、clientPort以及Port1和Port2都可以相同。
    所有的结点都配置完毕了之后,我们即可以启动ZooKeeper集群。

  2. 第二步:启动ZooKeeper集群
    与单机模式启动的方法相同,只需要一次启动所有的ZooKeeper节点即可启动整个集群。我们可以一个个手动去启动,当然,也可以写一个脚本去一次性启动。

  3. 第三步:检验ZooKeeper集群环境是否有效
    同样可以通过zkServer.sh脚本与telnet命令来查看每个节点的状态,此时会看到“Mode:leader”或者“Mode:Follower”的信息,表明该节点是Leader还是Follower。
    ZooKeeper提供了一系列的脚本程序,他们全部存放在bin目录下,例如:

    • zkServer.sh用于启动ZooKeeper服务器。
    • zkCli.sh用于链接ZooKeeper服务器的命令行客户端。
    • zkCleanup.sh用于清理ZooKeeper的历史数据,包括事务日志文件与快照数据文件。
    • zkEnv.sh用于设置ZooKeeper的环境变量。

    我们可以使用zkCli.sh脚本轻松链接ZooKeeper服务器,实际上它就是一个命令行客户端。下面我们来学习如何使用zkCli.sh链接ZooKeeper服务器,并使用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相关客户端命令的帮助。

使用java客户端链接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);

  • connectString:表示链接的字符串,需要传入链接ZooKeeper的ip与port,格式为ip:port。当链接ZooKeeper集群时,可同时添加集群中多个节点并用逗号分隔,格式为:ip1:port1,ip2:port2。。。
  • sessionTimeout:表示会话的超时时间,以毫秒为单位。在一个会话期内,ZooKeeper客户端与服务器之间通过“心跳检测”来维持会话的有效性,也就是说,一旦在sessionTimeout时间内没有进行有效的心跳检测,会话将会失效。
  • watcher:表示监视器接口,我们需要实现该接口的process()回调方法,并监视SyncConnected事件,在java中正是用匿名内部类的方式来实现异步回调过程的。

由于建立ZooKeeper会话的过程是异步的,也就是说,当构造完成zk对象之后,线程将继续执行后续的代码,但此时会话可能尚未建立完毕。因此,我们需要使用CountDownLatch工具,当创建zk对象完毕后,立即调用latch.await()方法(关闭阀门),当使用线程处于等待状态,等待SyncConnected事件到来时,在执行latch.countDown()方法(打开阀门),此时会话已经创建完毕,接下来当前线程就可以继续执行后续的代码了。
现在zk对象已经创建完毕,已经成功链接ZooKeeper服务器,下面所有的操作将会在该会话中进行,我们只需要调用zk对象的相关API即可。

使用Node.js客户端链接ZooKeeper

我们在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是什么

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)上。
这里有一些概念,做一下解释:

  1. Docker引擎(Docker Engine)
    Docker镜像可以理解为一个运行在服务器上的后台进程,也称之为Docker Daemon,还有很多人称他为Docker服务,因为他本质就是一个服务,只要我们启动该服务,我们就能随时使用它。我们可以通过Docker命令客户端发送相关的Docker命令,并与Docker引擎进行通信。
  2. Docker客户端(Docker Client)
    实际上Docker客户端有两种,一种是我们刚刚提到的Docker命令行客户端,只要我们打开命令终端窗口,输入相关的Docker 命令,就能操作Docker引擎。另一种Docker客户端是REST API客户端,我们一般会在程序中通过REST API与Docker引擎发生交互。
  3. Docker镜像(Docker Images)
    Docker镜像有点类似于我们曾经使用的光盘,光盘上刻录了数据,我们只需要将光盘放入光驱中,就能读取光盘中的数据。同样,我们只需要获取Docker镜像(光盘),就能载入到Docker引擎(光驱)中。并运行Docker镜像中的程序。一般情况下我们首先要将程序打包到Docker镜像中,随后才能将Docker镜像交给其他人使用。
  4. Docker容器(Docker Containers)
    当我们获取到Docker镜像后,可随时运行Docker镜像,此时便会启动一个Docker容器,该容器中将运行镜像中封装的程序。如果我们将Docker镜像理解为java类的话,那么Docker容器就相当于java实例。在同一个Docker镜像上理论上是可以运行无数个Docker容器的。
  5. Docker镜像注册中心(Docker Registry)
    Docker官方提供了一个叫做DockerHub的镜像注册中心(Docker Registry),用于放公开和私有的Docker镜像仓库(Docker Responsitory)。也就是说,我们随时可以通过Docker Hub拉去(下载)Docker镜像,也可以自由的将自己创建的Docker镜像推送(上传)到DockerHub上去。

虚拟机与Docker对比

Docker本质上为我们提供了一个“沙箱(Sandbox)”环境,它能将应用程序进行封装,并提供了与虚拟机相似的隔离性,但这种隔离性是想当轻量的。那么虚拟机与Docker有什么区别呢?一起来讨论下。
当我们需要在宿主机上运行一个虚拟操作系统时,首先需要安装一个虚拟机软件,常用的虚拟机软件比如Oracle VirtualBox或者VMware等,随后我们可以使用虚拟镜像文件,在虚拟机上安装虚拟操作系统。此时,虚拟软件需要模拟硬件与网络资源,会占用大量的系统开销。一般情况下,在一台普通的服务器上,最多只能启动十几个虚拟机,而且虚拟机的启动一般要几分钟甚至更长时间。
若我们使用Docker虚拟化技术,则只需要在宿主机上安装一个Docker引擎,随后可以从Docker镜像仓库中下载所需的Docker镜像,并启动相应的Docker容器。此时,Docker引擎完全利用宿主机硬件与网络资源,占用的系统开销较少。一般情况下,在一台普通的服务器上,可以启动上千个Docker容器。

Docker的特点

Docker是通过在底层上封装了Linux容器技术(LXC)来实现的,换句话说,Docker没有创造出任何新的技术,仅仅只是“新瓶装老酒”而已。下面归纳了Docker的四大特点:

  1. 快速运行
    启动虚拟机需要几分钟,而启动Docker只需要几秒钟。
  2. 节省资源
    Docker直接运行在Docker引擎上,可以直接利用宿主硬件资源,无需占用过多的系统资源。
  3. 便于交付
    传统软件交付物是程序,而在Docker时代的交付物却是镜像,镜像不仅封装了程序,还包含运行程序所需的相关环境。
  4. 容易管理
    可以通过Docker客户端直接操作Docker引擎,非常方便的管理Docker镜像与容器。

微服务的部署

我们使用Git管理代码,使用Maven构建项目,使用Docker封装服务,这些事情都需要手工的方式去一步步的执行,能否有快捷的方式呢?Jenkins就是这中快捷方式

Jenkins简介

在软件行业发展中,持续集成(Continuous Integration,简称CI)是利用一系列的工具、方法与规则,快速的构建代码,并自动的进行测试,从而完成代码开发的效率和质量。Jenkins是一款持续集成软件,拥有简单的安装、开箱即用、易于管理、易于维护、插件扩展等特性。只需要一个java的运行环境,就能将Jenkins跑起来,可以通过图形化界面为每个项目创建对应的构建任务(也称为“构建作业”)。Jenkins可以连接我们的代码仓库系统,从中获取源码并自动完成构建,当创建完毕后,还能执行一些后续的任务,比如:生成单元测试报告、归档程序包、部署程序包到Maven仓库、记录文件电子指纹、发送邮件通知等一系列的操作。为提高持续集成的执行效率,Jenkins支持主从(Master-Slave)运行模式,一台Master机器可以控制多台Slave机器,构件任务可并行在多台Slave机器上执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值