一起学习微服务和容器1-微服务的理解

前言

软件系统框架也叫软件体系结构,是一个比较大的概念,借用Conway’s law这哥们儿的话说:软件体系架构可以理解为一个包含各种组织的系统组织,这些组件包括 Web服务器, 应用服务器, 数据库,存储, 通讯层), 它们彼此或和环境存在关系,系统架构的目标是解决利益相关者的关注点。换句话说,跟我们现实社会中的企业或者政府,或者组织类似,把各种组件有机捏合起来,形成一个对外输出能力的统一的整体。

近几年随着国内互联网行业的大发展,软件体系在国内IT的演进也越来越快。从最初巨石结构(主备搭建高可用),到后来的集群模式(Cluster),再到后来的分布式(Corba, RPC, COM+),再到后来的分布式的集大成者SOA(个人更偏向于称之为一种架构思想,而非具体的技术),以及到现在流行的微服务的体系结构。

从技术的发展过程来看,也是和社会的发展相契合,信息的大爆炸,原有的单体的系统已满足不了大规模的业务和数据处理,才有了后面的分布式的思想,解决单体结构以及单体部署的各种限制和约束。
从技术本质上来说,通讯协议,语言逻辑,编程思想(面向过程,面向对象,脚本等思想层面)等基本没有太大的变化,基本上是对原有的技术的迭代升级,更方便工程方面的大规模的实施。
有关软件体系结构的演进,在此就不过多累述,以后会有单独的章节来单独描述。

微服务

微服务是指开发一个单个小型的但有业务功能的服务,每个服务都有自己的处理和轻量通讯机制,可以部署在单个或多个服务器上。微服务也指一种种松耦合的、有一定的有界上下文的面向服务架构。也就是说,如果每个服务都要同时修改,那么它们就不是微服务,因为它们紧耦合在一起;如果你需要掌握一个服务太多的上下文场景使用条件,那么它就是一个有上下文边界的服务,这个定义来自DDD领域驱动设计。

Triple D 设计模型

提到这个DDD领域驱动设计,不得不提一段伤心事,曾经面试一家外企,老外直接问“Triple D Design Model”,英文不太好,没听太明白老外的意思,结束后,自己查了下,恍然,原来就是领域驱动设计,借用我们业内的话说,就是"Domain-Driven-Design",这种思想也算是微服务的本源吧。以下几个小节将就领域设计模型展开下讨论,理解好了该模型之后,对后续系统结构的理解以及服务的领域规整以及服务的划分将有很大的益处。

领域(Domain)

领域和行业的关系

从大的范围来看,我们所说的领域,可以理解为一个某一个行业或者大行业下的细分行业。

一般情况下,我们要着手设计一个系统,首先我们要清楚的知道我们现在要做一个什么样的系统,这个系统需要解决什么问题(亦即我们常说的对业务模型的理解)。一个系统一般都会属于某个特定的领域,比如电商平台、普通电商系统,这种都属于网上电商领域,只要是这个领域的系统,那都有商品浏览、购物车、下单、减库存、付款交易等核心环节。同一个领域的系统都具有相同的核心业务,因为它们要解决的问题的本质是类似的。

因此,我们可以推断出,一个领域本质上可以理解为就是一个问题域,只要是同一个领域,那问题域就相同。所以,只要我们确定了系统所属的领域,那这个系统的核心业务,即要解决的关键问题、问题的范围边界就基本确定了。

通常我们说,要成为一个领域的专家,必须要在这个领域深入研究很多年才行。因为只有你研究了很多年,你才会遇到非常多的该领域的问题,同时你解决这个领域中的问题的经验也非常丰富。很多时候,领域专家比技术专家更加吃香,比如金融领域的专家。

为了避免大家进入误区,在此小节,岔开个话题,就是简单谈一下个人对专家(架构师)的理解,严格意义上来讲,体量比较大的公司一般会有CTO,下面划分多种架构师:业务架构师,技术架构师和运维架构师。每一个类型的架构师都有自己专业领域内的深刻理解。体量比较小的公司,一般只有一个综合性的架构师。在上述话中所说的领域专家,更多的是指业务架构师。

领域和子领域

按照我们上面小节的理解,如果把一个行业或者某个行业的细分行业称为一个领域,那我们在具体的设计一个软件体系结构的时候,就需要把领域进行细化形成一个一个的子领域。例如上文所讲的电商系统,其中的订单,支付,物流等就是一个一个的小的子领域。
子领域的划分从原则上有两个维度:业务和系统。

  • 一般情况下,一个子域就可以划分为一个服务,单个服务对外统一提供该子域的功能。如果遇到单个服务的某些关键点按照系统资源消耗的大小,可以单独剥离出来,方便依据业务流量进行动态的扩展(在后文中会展开讲)。
  • 从系统层面,业务模块属于一个层面的服务,系统层面可以提供系统支撑的服务,例如:系统基本服务(数据字典,系统结果码,异常码等),用户管理,流程管理,Job管理等。

设计(Design)

DDD是一种基于模型驱动开发的软件开发思想,强调领域模型是整个系统的核心,领域模型也是整个系统的核心价值所在。每一个领域,都有一个对应的领域模型,领域模型能够很好的帮我们解决复杂的业务问题。

从领域和代码实现的角度来理解,领域模型绑定了领域和代码实现,确保了最终的代码实现就一定是解决了领域中的核心问题的。

  • 领域驱动领域模型设计。我们只要保证领域模型的设计是正确的,就能确定领域模型可以解决领域中的核心问题
  • 领域模型驱动代码实现。我们只要保证代码实现是严格按照领域模型的意图来落地的,那就能保证最后出来的代码能够解决领域的核心问题的。

这个思路,和传统的分析、设计、编码这几个阶段被割裂(并且每个阶段的产物也不同)的软件开发方法学形成鲜明的对比,按照个人的理解,敏捷的思想也是来源于此吧。关于传统的软件开发方法学和敏捷方法学的理解以及该如何选择,将会有单独的章节进行描述,在此不做累述。

驱动(Driven)

在DDD思想世界里,我们总是以领域为边界,分析领域中的核心问题(核心关注点),然后设计对应的领域模型,再通过领域模型驱动代码实现。相较于传统的设计思路,像数据库设计、持久化技术等都会被稍微弱化了点。

组件化的服务

在我们进行软件体系结构的设计过程中,参考DDD设计思想,会将系统进行领域的划分形成各种组件(可独立更换和升级的软件单元),进而将组件整合在一起来构建系统,这与我们在现实世界中看待事物的方式非常相似。

微服务架构一样会用到各种库,但这种架构会把软件给拆分成各种不同的服务来实现组件化。介绍两个重要的概念:

  • 库(library) 指的是链接到程序的组件,通过本地函数调用来使用库提供的功能。
  • 服务 (service) 是进程外的组件,通过网络服务请求 (web service request) 或者远程函数调用之类的机制来使用里面的功能。

微服务的12原则

微服务从本质上来说,也算是一种体系结构的设计思想,从SOA演进到微服务,大量的工程实践,各路大牛探索总结,得出来的这12个特性吧,又叫12要素,也叫12原则。
借用网上的一张图来展示微服务的12原则(如有版权问题,还请留言)。


image.png

一般情况下,在给大家刚开始讲微服务的时候,很多时候只是讲到DDD模型设计,不太会在开始的时候就给大家阐述这些特性和概念。因为该部分内容,牵涉到具体的动手操作实践,12原则对于有初步实践过的同学来说,回头来理解更容易些,对于新入行的同学理解起来有点抽象。建议初学者可以初步了解下,在自己实践的过程中,回头多想想。

原则1:一份基准代码,多份部署

这个原则不管对微服务体系结构还是其它软件体系结构来说基本通用,该原则包括如下四个子原则

  • 使用代码库管理代码,一般是Git或者SVN(强烈推荐Git)。
  • 一份基准代码(即一个代码库)对应一个应用。如果通过一份基准代码可以编译出多个应用,那么应该考虑将该基准代码按应用拆分为多份;如果一个应用需要多份基准代码,那么要么考虑将多份基准代码合并,要么考虑将该应用按基准代码拆分为多个。
  • 不允许多个应用共享一份基准代码,如果确实需要共享,那就把需要共享的基准代码的稳定版本发布为类库,然后通过依赖管理策略进行加载。
  • 同一应用的多份部署可以使用同一份基准代码的不同版本,但是不可以使用不同的基准代码,类似原则2,使用不同基准代码的应用不应被视为同一应用。

违反子原则2和3,会给代码管理和编译工作带来麻烦:

  • 如果一份基准代码可以编译出多个应用,那么这几个应用之间必然会存在不清晰的依赖关系,随着时间的推移,这种依赖关系会变得愈加混乱,以至于修改一个应用的代码,会给其他应用带来不可预知的影响。这样的基准代码显然极难维护。
  • 基准代码的划分和应用的划分非常类似,也是系统边界的一种体现,如果一个应用需要从多份基准代码编译,那么多数情况下这个应用的内外部边界问题会存在问题。如果边界不存在问题,那么请将多份基准代码合并为一份,而不是维持这种古怪的设计。
  • 如果多个应用不是通过类库,而是直接共享一份基准代码,那么这份被共享的基准代码会很难维护,对这份基准代码的修改必须谨慎考虑对多个应用可能造成的影响。正确的方式是将这份基准代码发布为类库,保持清晰的边界和接口约定供其它应用调用。

原则2:显式声明依赖关系

这里的依赖指所有的依赖,我们假设运行环境中除了最基本的环境外什么都没有,都必须进行显示声明,并对版本做出明确的指定。

  • 应用程序本身的类库。
  • 微服务之间的依赖关系。
  • 操作系统层面被应用程序所使用的库文件。
  • 其他二进制文件。

以前我们往往不会对依赖做如此严格的管理,因为应用不会有太大规模的部署,也不会进行频繁的发布,如果发现运行环境里缺少某些依赖,那么就临时手工处理一下,也不是什么太大的问题。如今在微服务模式下,应用的部署规模大、发布频率高,还记得前文所说的“不可变服务器”吗?如果这个时候还是使用原有的模式,则会带来混乱。

特别是后续章节中所讲述的和容器结合的方式,如果使用容器方式进行部署,容器的基础镜像很可能是Busybox或者Alpine之类的迷你Linux或者Java,那么就几乎等于什么都没有。

声明依赖的方式有很多,常见的方式有:

  • 使用依赖清单,根据依赖清单进行依赖检查,同时使用依赖隔离工具保证应用不会调用系统中存在但是依赖清单中未声明的依赖项,例如我们常用的Maven/Gradle/Ant等;
  • 使用容器技术,将应用和依赖打包为容器镜像,依赖的声明和隔离就一并解决了。
    在本系列中,我们两种方式将会结合使用,使用Maven管控微服务自身所需要引用的包依赖,以及服务之间的包依赖。我们使用Docker镜像来管理运行基础环境所需要的lib的版本控制。

原则3:在环境中存储配置

首先需要明确的是,这里的配置指与部署运行/环境有关的配置,例如:数据库、消息代理、缓存系统等后端服务的连接配置和位置信息,如URL、用户名、密码等。

现下流行的研发模式,大家一般都会使用配置文件来单独配置系统运行过程中所需要使用到的参数,实现配置和代码的分离,但是这种方式仍然存在一些缺点,例如:

  • 敏感信息泄露。生产环境的参数也要配置在配置文件中,需要编译到系统中。开发人员,测试人员,运维人员很容易接触到这些信息,也很容易造成敏感信息泄露。
  • 更改配置需要重新编译环境。我们拿Java举例,编译好的运行包,如果牵涉到参数的修改,需要重新打包,这在发布和运维的过程中很容易造成生产事故。
  • 如果是多语言的研发模式(微服务不要求必须统一使用某一种语言),配置文件会分散在不同的目录中,并且有不同的格式(配置文件的格式往往与开发语言和框架相关),这会给配置的统一管理造成困难。

为了避免上述问题,本原则要求将在环境中存储配置。一种典型的方式是把配置存储在环境变量中,这会使配置和代码彻底的分离,格式上也与开发语言和框架再无瓜葛,并且也不会被误提交到代码库中。还可以使用Spring Cloud Config Server这类配置管理服务进行配置推送,并将配置的历史版本和变更原因也一起管理起来,后续章节会具体讲解。

原则4:把后端服务当作附加资源

一般来说,我们在设计微服务的时候,一般会设计为无状态服务,服务可以依据生产需要进行自由启停,这就需要将数据访问,对象存储,消息代理,缓存,日志收集等后端服务全部作为资源来处理,通过网络调用的资源。

该原则有如下几层含义:

  • 不要将这些服务放在应用本地:云原生应用要求应用本身无状态化,那么状态信息就应该存储在外部服务中(参见不可变服务器)。
  • 微服务模式要求应用责权单一以实现可靠性和扩展性,如果在应用本地放置数据库,那么微服务平台将无法通过更换应用的故障实例实现应用的高可用性,也无法通过自动化的横向伸缩实现扩展性,因为应用实例内包含两种性质完全不同的软件(应用和数据库),无法对两者使用同一种方式进行横向扩展。
  • 通过URL或者服务注册/认证中心访问这些后端服务,应用应该能够在不进行任何代码修改的情况下,在不同的目标环境中进行部署,应用不应该和后端服务的任何一种具体实现存在紧耦合关系。
  • 类似“显式声明依赖关系”原则,应用最好也能够对其使用的这些后端服务进行显示声明,以方便云平台对服务资源进行自动绑定,在后端服务出现故障的时候,云平台也能够对其进行自动恢复。

原则5:严格分离构建、发布和运行

在本原则中,构建、发布和运行这三个概念可能和从前有所不同,因此有必要首先对其进行明确:

  • 构建指的是将应用代码转化为执行体的过程:构建时会拉取特定版本的代码和依赖项,将其编译为二进制文件(针对编译型语言),并和资源文件一起打包。
  • 发布指的是将构建的结果和部署所需的配置相结合,并将其放置于运行环境之中。
  • 运行指的是将发布的结果启动为运行环境中的一个或多个进程。

本原则要求构建、发布和运行这三个步骤严格区分:

  • 禁止直接修改运行状态的代码或者对应用进行打补丁,因为这些修改很难再同步回构建步骤,这时运行状态的代码就成为了“孤本”。
  • 原则上不建议在运行期间修改应用的配置,配置的修改应该仅限于发布阶段(参见不可变服务器),实际上对于某些服务是可以热调整运行参数,比如日志级别,在线跟踪某个问题的时候,由于生产环境的日志级别相对较高,需要临时调整为INFO级别等,后文会讲到。
  • 运行这一步骤应该非常简单,仅限于启动进程,资源文件的关联应仅限于构建阶段,配置的结合应仅限于发布阶段。

同时,每一次发布都应该对应一个唯一的发布ID,发布的版本应当像一个只能追加的账本,一旦发布就不能修改。这么做的好处是:

  • 每一份运行状态的代码都可以在对应的发布和构建阶段找到它的来源,这是实现重新发布、故障实例的自动替换、发布出错后的版本回退等机制的基础。
  • 运行步骤非常简单,这样在硬件重启、实例故障和横向扩展等情况下,应用可以简单和快速的实现重启。

原则6:以一个或多个无状态的进程运行应用

何为无状态,简单来说就是运行进程内不保存状态信息,任何状态信息都应该被保存在数据库、缓存系统等外部服务中。应用实例之间的数据共享也要通过数据库和缓存系统等外部服务进行,直接的数据共享不但违反无状态原则,还引入了串行化的单点,这会为应用的横向扩展带来障碍。

在微服务模式下,应用不应该在自身进程内部缓存数据以供将来的请求使用,因为微服务模式以多实例方式运行应用,将来的请求多半会被路由到其他实例,此时虽然可以使用粘滞会话将请求保持在同一个实例上,但是无论是云原生应用还是微服务模式都极力反对使用粘滞会话,原因如下:

  • 很难对粘滞会话实现负载均衡,因为粘滞会话的均衡性不仅决定于负载均衡策略,还和会话本身的行为相关,例如,可能存在应用某些实例上的会话已经大量退出,而另一些实例上的会话依然处于活动状态,此时这两部分实例的负载处于不均衡状态,而负载均衡器无法将活动会话转移到空闲的应用实例。
  • 启动新的应用实例不会立即提高应用的整体处理能力,因为这些新实例只能承接新会话,旧的会话依旧粘滞在旧的应用实例上。
  • 应用实例退出会导致会话丢失,所以在实例发生故障时,即使云平台可以对故障实例进行自动替换,也会导致用户数据丢失。即使是对应用实例进行人工维护,也需要在维护之前对该实例上的会话进行转移,这往往意味着需要编写复杂的业务代码。

在传统模式下,可以通过在双机之间进行会话复制来实现对用户无感知的单机下线维护(虽然会付出处理能力减半的代价),但是在微服务模式下,应用的实例数量往往远不止两个,在大量的实例之间进行会话复制会使实例之间原本非常简单的逻辑关系复杂化,此时将无法通过云平台对其进行无差别的自动化维护。另外,在实例之间进行会话复制也意味着实例之间存在着直接的数据共享,这会为应用的横向扩展带来障碍。
所以,粘滞会话是应用实现可用性和扩展性的重要障碍,使用粘滞会话显然是种得不偿失的选择。更好的实现方式是将会话信息存储在缓存服务中。

原则7:通过端口绑定提供服务

服务端应用通过网络端口提供服务,这点毋庸置疑,但是本原则还有如下两个深层次的含义:

  • 无论是云原生应用还是微服务模式都要求应用应该完全自我包含,而不是依赖于外部的应用服务器,端口绑定指的是应用直接与端口绑定,而不是通过应用服务器进行端口绑定。
  • 如果一定要使用应用服务器,那就使用嵌入式应用服务器,无论是云原生应用还是微服务模式都极力反对将多个应用放置于同一个应用服务器上运行,因为在这种模式下,一个应用出错会对同一个应用服务器上的其他应用造成影响,也无法针对单一应用做横向扩展。

端口绑定工作应该由云平台/容器自动进行,云平台在实现应用到端口的绑定之外,还需要实现内部端口到外部端口的映射和外部端口到域名的映射。在应用的整个生命周期内,应用实例会经历多次的重新部署、重启或者横向扩展,端口会发生变化,但URL会保持不变。
在本系列中,我们注重服务自身的端口使用,在容器章节中,会详细讲解应用的端口映射,如果不使用云服务/容器,则只需关注服务自身的端口,保证每个服务有自己独占的端口即可,无须太多关注端口映射。

原则8:通过进程模型进行扩展

系统的扩展包括两个层面的扩展:线程层面和进程层面。也叫:纵向扩展和横向扩展。下面分别介绍下两种扩展模式。

线程层面

线程层面扩展一种相对较为传统的方式,典型的例子是Java应用。当我们启动一个Java进程的时候,通常会通过JVM参数为其设置各个内存区域的容量上下限,同时还可能会在应用层面为其设置一个或者多个线程池的容量上下限,当外部负载变化时,进程所占用的内存容量和进程内部的线程数量可以在这些预先设置好的上下限之间进行扩展,这种方式也被称为纵向扩展或者垂直扩展。

线程层面存在的问题:

  • 在进程的内存容量和线程数量提高时,应用的某些性能指标可能不会得到同步提高,甚至可能会下降(这往往是因为程序对某些无法扩展的资源进行争用所造成的),这种参差不齐的性能扩展对外部负载提高的承接能力会很不理想,有时甚至会适得其反;
  • 为了使进程本身可以完成纵向扩展,还需要在虚拟机层面或者容器层面为其预留内存资源和对应的CPU资源,这会造成大量的资源浪费(当然,也可以使虚拟机或者容器跟随进程一起进行纵向扩展,这在技术上是可行的,但是会为虚拟机或者容器管理平台的资源调度造成一些不必要的困难,例如频繁的虚拟机迁移或者容器重启)。

进程模式

在外部负载提高时,启动更多的“固定的”进程(对前面Java应用的例子来说,就是固定的内存容量和线程池容量),在外部负载降低时,停止一部分进程,这种方式就是本原则所说的通过进程模型进行扩展,有时候也被称为横向扩展或者水平扩展。

进程模式的好处有:

  • 在进程数量增加的时候,应用的各种性能指标会得到同步的提高,这种提高即使不是线性的,也会按照一种平滑和可预期的曲线展开,可以更为稳定的应对外部负载的变化。
  • 站在云应用层面或者说服务治理层面,推崇以进程模式作为可以操作的最小单元。云平台可以根据各个层面的监控数据,通过预设规则决定是否为应用增加或者减少进程,例如,当前端的负载均衡器检测到访问某个后端应用的并发用户数超过某个阈值时,可以立即为这个后端应用启动更多的进程,以承接更大的负载,同时还可以选择是否对该应用后端的数据库进行扩展。

在实际的应用中,线程模式的扩展更多的是专注在单个服务内,进程的扩展专注在服务的弹性伸缩,建议两者相辅相成,相得益彰,不要一刀切非此即彼。

原则9:快速启动和优雅终止可最大化健壮性

该原则要求应用可以瞬间(理想情况下是数秒或者更短)启动和停止,因为这将有利于应用快速进行横向扩展和变更或者故障后的重新部署,而这两者都是程序健壮性的体现。

在“原则5:严格分离构建、发布和运行”中我们还提到,应用的运行步骤应该非常简单,这里的“简单”也隐含着快速的意思,目的是为了在硬件重启、实例故障和横向扩展等情况下,应用可以快速的实现重启。除此之外,“原则6:以一个或多个无状态的进程运行应用”也与应用的快速启动有关,遵守无状态原则,使用云平台提供的缓存服务,而不是在应用内部加载缓存,可以避免在应用启动期间进行耗时的缓存预热。

比起应用的快速启动,优雅终止(Graceful Shutdown)需要考虑的问题会更为广泛一些。优雅终止需要尽可能降低应用终止对用户造成的不良影响(对于微服务应用,用户可能是人,也可能是其他微服务)。

对于短任务来说,这一般意味着拒绝所有新的请求,并将已经接收的请求处理完毕后再终止;对于长任务来说,这一般意味着应用重启后的客户端重连和为任务设置断点并在重启后继续执行。除此之外,优雅终止还需要释放所有被进程锁定的资源,并对事务的完整性和操作的幂等性做出完备的考虑。

最后,应用还必须应对突如其来的退出,在硬件出现故障时或者进程崩溃时,应用需要保证不会对其使用的数据造成损坏,遵守无状态原则、将数据交由后端服务处理的应用可以很容易的将应对突然退出的复杂度外部化。

原则10:开发环境与线上环境等价

本原则的浅层次含义是要求在开发环境和线上环境中使用相同的软件栈,并尽可能为这些软件栈使用相同的配置,以避免“It works on my machine.”这类问题。本原则反对在不同的环境中使用不同的后端服务,虽然可以使用适配器或者在代码中做出兼容性考虑以消除后端服务的差异,但是这将牵扯开发人员和测试人员大量的精力以保证这些适配器和代码确实可以按预期工作,在应用的整个开发周期中,这将积累极大的额外工作量,是一种非常不必要的资源浪费。

本原则的深层次含义是尽量缩小开发环境和线上环境中时间和人员的差异。开发环境中的代码每天都在更新,而这些更新往往会累积数周甚至数月才会被发布到线上环境,这是开发环境和线上环境在时间上的巨大差异;开发人员只关心开发环境,运维人员只关心线上环境,开发人员和运维人员在工作上鲜有交集,这是开发环境和线上环境在人员上的巨大差异。

对于前一个差异,本原则要求更为密集和频繁的向线上环境发布更新,要求建立机制以保障开发人员可以在数小时甚至数分钟内既可将更新发布到线上,这也正是本章理念部分中持续交付所提倡的;对于后一个差异,本原则要求开发人员不能只关心开发环境中自己的代码,更要密切关注代码的部署过程和代码在线上的运行情况,这也正是DevOps所提倡的。

原则11:把日志当作事件流

应用程序应该将其产生的事件以每个事件一行的格式按时间顺序输出,这点毋庸置疑,但是本原则想说的其实是:应用程序不要自行管理日志文件。而是要求应用程序将日志以事件流的方式输出到标准输出STDOUT和标准错误输出STDERR,然后由运行环境捕获这些事件流,并转发到专门的日志处理服务进行处理。这样做的原因是:

  • “原则6:以一个或多个无状态的进程运行应用”要求应用程序无状态,那么应用程序就不应该将日志文件这种价值信息存储在本地文件系统上。当然,可以在本地运行一个日志收集进程读取日志文件,并将其转发到专门的日志处理服务,以保证价值信息不被意外丢弃,但是这带来了如下问题:

    • 需要提供一种机制以保证日志收集进程可靠运行。
    • 需要通过配置文件告知日志收集进程去哪里读取日志文件。
    • 需要在应用程序所在的虚拟机或者容器上为日志收集进程开放一个网络端口以供其发送日志内容,这不仅增加了网络的复杂度,还给网络安全带来了隐患。
  • 在存在专门的日志处理服务时,由应用程序自行对日志进行分类显得死板和毫无必要;只需将日志以事件流方式发送给日志处理服务,日志处理服务可以对这些日志按不同视角进行灵活的分类,而不是受限于一种既定的分类规则。

  • “原则6:以一个或多个无状态的进程运行应用”中还提到“微服务模式以多实例方式运行应用,将来的请求多半会被路由到其他实例”,所以单个应用实例的日志无法描述完整的业务操作,不具备业务层面的价值。必须将应用所有实例的日志汇总到日志处理服务,由日志处理服务按特定规则(如按用户ID或者对象ID)对其进行聚合,才能完整展现应用在业务层面的操作过程。

应用在以多实例方式运行时,应用的单个实例可能会因为软硬件故障而重启,或者被横向扩展机制创建和销毁,所以必须将应用所有实例的日志汇总,才能完整的描述应用的运行情况。

原则12:后台管理任务当作一次性进程运行

在本原则中,通过SSH接入线上环境并使用脚本语言执行管理任务的做法已经不再被提倡,无论是云原生应用还是微服务模式都极力反对这种做法,原因可以参见“理念五:不可变服务器”和“理念六:提供声明式接口”。另外还有一个原因显而易见:你的应用有数个或者数十个实例,那么应该登录到哪个实例中执行管理任务呢?如果在管理任务执行的过程中,所在实例因为软硬件故障重启,或者被横向扩展机制销毁,那又该怎么办?
比较推荐的做法是,如果管理任务是修改应用配置,那么应该通过配置管理服务进行操作,参见“原则3:在环境中存储配置”;如果管理任务是批处理任务,例如数据的迁移、清洗或者检查,那么应该通过云平台的批处理机制进行操作,大多数的云平台都会提供这种机制,例如Kubernetes的Jobs。

截止到此,微服务的12原则已阐述,在本章结尾处,强烈建议大家跟随本系列的后续章节,亲身实践下,然后再回头来看这些原则,理解起来会更深刻些。

微服务和SOA

在前言中,已阐述过微服务很大程度是从SOA演进而来,也可以说微服务和SOA设计思想有千丝万缕的联系。

  • 从发展历程来说,SOA的体系结构常见于2014前的中大型企业中,特别是金融,电信等大型的传统企业,就是截止到现在依旧有很多的企业以SOA为核心框架。SOA的由来,一方面是由过去单体巨石结构的烟囱式粗放发展产物。一方面是由于业务复杂性提高后,按照大的业务域划分的产物。

  • 从架构思想来说,SOA主张服务的异构化以及分布式化,根据统一的协议发布服务。一般情况下会有企业服务总线(也就是大家所熟知的ESB)对所有的服务进行集中管理,跨域的服务之间的访问也全部通过ESB进行。当大多我们遇到名为“SOA”的东西的时候,它与我们在本文所描述的风格完全不同。尤其我们已经看到了很多面向服务的拙劣的实践——从在ESB中总是将复杂隐藏起来的趋势,到失败的花费数百万而没有价值的多年计划,再到总是抑制变化的集中管理模式,导致有时很难看到过去的这些问题。

  • 从技术实现来说,SOA的技术栈主要涵盖RPC,Corba,WebService等。由于RPC和Corba需要预先定义接口和对象类型,相较于WebService 的WSDL标签语言稍显笨重(其实WSDL对于夸语言夸平台的实现来说,确实比较便利,只是实现起来也挺麻烦,不过各种语言都有相应的封装的Lib包,极大的方便了WebService的开发),后来WebService流行于各种SOA体系结构中。

相较于SOA的思想和实现,微服务的体系结构倾向于:智能终端和无声管道。使用微服务搭建的应用旨在尽可能的分解和凝聚——他们拥有他们自己的业务逻辑,而且更像一个传统Unix印象中的过滤器——接收请求,应用合适的逻辑,并产生响应。它们使用简单REST协议而非复杂协议,就像WS-Choreography或者BPEL或者使用中央工具配置。

  • 从架构思想上来说,SOA更偏向于业务域的划分和实现。而微服务更侧重于按照领域和子领域的功能集的划分。话句话来说,微服务的更加细化,对某个子领域或者某个功能集进行封装,服务之间可以依据实际需要进行相对自由访问来实现对外提供服务的能力。
  • 从技术实现来说,又有太多的不同。
    • 相较于SOA普遍使用的稍显笨重的WebService,微服务更倾向于使用更加轻量级的HTTP Restful请求。严格意义上来说,微服务之间的通讯协议并没严格限制,也可以是Thrift,Hession,RPC等,在非特殊场景要求下,一般主张使用HTTP Restful接口来简化服务之间的访问。
    • 微服务体系结构放弃了SOA世界里ESB,而是主张服务之间可以直接交互,或者选择像RabbitMQ或ZeroMQ一样不仅仅是提供可靠的异步架构来简单实现。
    • 微服务由于放弃了ESB,启用了注册中心来对服务进行治理,服务可以将自己注册到注册中心,亦可从注册中心获取已注册的服务列表,对服务进行动态访问。
    • 微服务依据自身的特性,更适用于DevOps的思想,可以很方便的进行开发/集成/运维。

当然还有很多很多的不同点,在此就不再累述,在后续的章节中也会陆续的给大家揭晓。

相信能耐心的读到这里的同学已是少数,比较太过于理论,太枯燥,各位看官,我们接下来的章节,将逐步的带大家一起步入微服务的世界,你准备好了吗?

【如果大家要引用本文,还请注明出处。】

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,817评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,329评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,354评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,498评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,600评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,829评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,979评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,722评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,189评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,519评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,654评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,940评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,762评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,993评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,382评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,543评论 2 349