目录html
在进行Spring Cloud 的具体内容介绍以前, 咱们先经过本章学习一些关于微服务架构以及Spring Cloud 的基础知识。对Spring Cloud 可以解决的具体问题有一个大体的了解,以帮助咱们更好地理解后续章节对各个组件的介绍。前端
“微服务”一词源于Martin Fowler 的名为Microservices 的博文, 能够在他的官方博客上找到:http://martinfowler.com/articles/microservices.html。java
简单地说, 微服务是系统架构上的一种设计风格,它的主旨是将一个本来独立的系统拆分红多个小型服务,这些小型服务都在各自独立的进程中运行,服务之间经过基于HTTP的RESTful API进行通讯协做。被拆分红的每个小型服务都围绕着系统中的某一项或一些耦合度较高的业务功能进行构建,而且每一个服务都维护着自身的数据存储、业务开发、自动化测试案例以及独立部署机制。由千有了轻量级的通讯协做基础, 因此这些微服务能够使用不一样的语言来编写。git
在以往传统的企业系统架构中,咱们针对一个复杂的业务需求一般使用对象或业务类型来构建一个单体项目。在项目中咱们一般将需求分为三个主要部分: 数据库、服务端处理、前端展示。在业务发展初期,因为全部的业务逻辑在一个应用中, 开发、测试、部署都还比较容易且方便。可是,随着企业的发展, 系统为了应对不一样的业务需求会不断为该单体项目增长不一样的业务模块; 同时随着移动端设备的进步,前端展示模块已经不只仅局限于Web的形式,这对千系统后端向前端的支持须要更多的接口模块。单体应用由千面对的业务需求更为宽泛,不断扩大的需求会使得单体应用变得愈来愈腕肿。单体应用的问题就逐渐凸显出来, 因为单体系统部署在一个进程内,每每咱们修改了一个很小的功能, 为了部署上线会影响其余功能的运行。而且, 单体应用中的这些功能模块的使用场景、并发量、消耗的资源类型都各有不一样, 对于资源的利用又互相影响, 这样使得咱们对各个业务模块的系统容量很难给出较为准确的评估。因此, 单体系统在初期虽然能够很是方便地进行开发和使用, 可是随着系统的发展, 维护成本会变得愈来愈大, 且难以控制。web
为了解决单体系统变得庞大脯肿以后产生的难以维护的问题, 微服务架构诞生了并被你们所关注。咱们将系统中的不一样功能模块拆分红多个不一样的服务,这些服务都可以独立部署和扩展。因为每一个服务都运行在本身的进程内, 在部署上有稳固的边界, 这样每一个服务的更新都不会影响其余服务的运行。同时, 由千是独立部署的, 咱们能够更准确地为每一个服务评估性能容量, 经过配合服务间的协做流程也能够更容易地发现系统的瓶颈位置,以及给出较为准确的系统级性能容量评估。算法
在实施微服务以前, 咱们必需要知道, 微服务虽然有很是多吸引人的优势, 可是也由于服务的拆分引起了诸多本来在单体应用中没有的问题。spring
尽管微服务架构有不少缺点和问题, 可是其实现的敏捷开发和自动化部署等优势依然被广大优秀架构师和开发者所青眯,因此解决这些问题就是这几年诸多架构大师努力的目标。数据库
在架构师对于一个大型系统架构的设计与实施的过程当中, 面对环境、资源、团队等各类因素的影响, 几乎不会出现彻底相同的架构设计。对于微服务架构而言更是如此, 因为并无一个标准或正式的定义, 每位架构师都根据自身理解与实际状况来进行设计, 并在发展的过程当中不断演化与完善。通过多年的发展, Martin Fowler 在Microservices 一文中,提炼出了微服务架构的九大特性, 用于指导你们设计架构。apache
组件, 是一个能够独立更换和升级的单元。就像PC 中的CPU、内存、显卡、硬盘同样, 独立且能够更换升级而不影响其余单元。编程
在微服务架构中, 须要咱们对服务进行组件化分解。服务, 是一种进程外的组件, 它经过HTTP 等通讯协议进行协做,而不是像传统组件那样以嵌入的方式协同工做。每个服务都独立开发、部署, 能够有效避免一个服务的修改引发整个系统的从新部署。
打一个不恰当的比喻, 若是咱们的PC 组件以服务的方式构建, 那么只维护主板和一些必要外设以后, 计算能力经过一组外部服务实现, 咱们只须要告诉PC 从哪一个地址来得到计算能力, 经过服务定义的计算接口来实现咱们使用过程当中的计算需求, 从而实现CPU组件的服务化。这样本来复杂的PC 服务获得了轻量化的实现, 咱们甚至只须要更换服务
地址就能升级PC 的计算能力。
当决定如何划分微服务时, 一般也意味着咱们要开始对团队进行从新规划与组织。按以往的方式, 咱们每每会从技术的层面将团队划分为多个,好比DBA团队、运维团队、后端团队、前端团队、设计师团队等。若咱们继续按这种方式组织团队来实施微服务架构开发, 当有一个服务出现问题须要更改时, 多是一个很是简单的变更, 好比对人物描述增长一个字段, 这须要从数据存储开始考虑一直到设计和前端, 虽然你们的修改都很是小,但这会引发跨团队的时间耗费和预算审批。
在实施微服务架构时, 须要采用不一样的团队分割方法。因为每个微服务都是针对特定业务的宽栈或是全栈实现, 既要负责数据的持久化存储, 又要负责用户的接口定义等各类跨专业领域的职能。所以,面对大型项目的时候, 对于微服务团队的拆分更加建议按业务线的方式进行拆分, 一方面能够有效减小服务内部修改所产生的内耗; 另外一方面, 团队边界能够变得更为清晰。
在实施微服务架构的团队中, 每一个小团队都应该以作产品的方式, 对其产品的整个生命周期负责。而不是以项目的模式,以完成开发与交付并将成果交接给维护者为最终目标。
开发团队经过了解服务在具体生产环境中的状况, 能够增长他们对具体业务的理解,好比, 不少时候, 一些业务中发生的特殊或异常状况, 极可能产品经理都并不知晓, 但细心的开发者很容易经过生产环境发现这些特殊的潜在问题或需求。
因此, 咱们须要用作“产品”的态度来对待每个微服务, 持续关注服务的运做状况,并不断分析以帮助用户来改善业务功能。
在单体应用中,组件间直接经过函数调用的方式进行交互协做。而在微服务架构中,因为服务不在一个进程中, 组件间的通讯模式发生了改变, 若仅仅将本来在进程内的方法调用改为RPC 方式的调用,会致使微服务之间产生烦琐的通讯, 使得系统表现更为糟糕,因此, 咱们须要更粗粒度的通讯协议。
在微服务架构中, 一般会使用如下两种服务调用方式:
在极度强调性能的状况下, 有些团队会使用二进制的消息发送协议, 例如protobuf。即便是这样, 这些系统仍然会呈现出“ 智能瑞点和哑管道” 的特色, 这是为了在易读性与高效性之间取得平衡。固然大多数Web 应用或企业系统并不须要在这二者间作出选择, 可以荻得易读性已是一个极大的胜利了。
一Martin Fowler
当咱们采用集中化的架构治理方案时, 一般在技术平台上都会制定统一的标准, 可是每一种技术平台都有其短板, 这会致使在碰到短板时, 不得不花费大力气去解决, 而且可能由于其底层缘由解决得不是很好, 最终成为系统的瓶颈。
在实施微服务架构时, 经过采用轻量级的契约定义接口, 使得咱们对于服务自己的具体技术平台再也不那么敏感,这样整个微服务架构系统中的各个组件就能针对其不一样的业务特色选择不一样的技术平台, 终千不会出现杀鸡用牛刀或是杀牛用指甲钳的尴尬处境了。
不是每个问题都是钉子, 不是每个解决方案都是锤子。
咱们在实施微服务架构时, 都但愿让每个服务来管理其自有的数据库, 这就是数据管理的去中心化。
在去中心化过程当中, 咱们除了将原数据库中的存储内容拆分到新的同平台的其余数据库实例中以外(如把本来存储在MySQL 中的表拆分后,存储到多个不一样的MySQL 实例中),也能够将一些具备特殊结构或业务特性的数据存储到一些其余技术的数据库实例中(如把日志信息存储到MongoDB 中或把用户登陆信息存储到Redis 中)。
虽然数据管理的去中心化可让数据管理更加细致化, 经过采用更合适的技术可以让数据存储和性能达到最优。可是, 因为数据存储于不一样的数据库实例中后, 数据一致性也成为微服务架构中亟待解决的问题之一。分布式事务自己的实现难度就很是大, 因此在微服务架构中, 咱们更强调在各服务之间进行“ 无事务” 的调用, 而对于数据一致性, 只要求数据在最后的处理状态是一致的便可;若在过程当中发现错误, 经过补偿机制来进行处理,使得错误数据可以达到最终的一致性。
近年来云计算服务与容器化技术的不断成熟, 运维基础设施的工做变得愈来愈容易。可是,当咱们实施微服务架构时,数据库、应用程序的个头虽然都变小了, 可是由于拆分的缘由, 数量成倍增加。这使得运维人员须要关注的内容也成倍增加, 而且操做性任务也会成倍增加, 这些问题若没有获得妥善解决, 必将成为运维人员的噩梦。
因此,在微服务架构中, 务必从一开始就构建起“待续交付”平台来支撑整个实施过程, 该平台须要两大内容, 缺一不可。
在单体应用中,通常不存在单个组件故障而其余部件还在运行的状况, 一般是一挂全挂。而在微服务架构中, 因为服务都运行在独立的进程中, 因此存在部分服务出现故障,而其余服务正常运行的状况。好比,当正常运做的服务B调用到故障服务A时, 因故障服务A 没有返回, 线程挂起开始等待, 直到超时才能释放, 而此时若触发服务B 调用服务A的请求来自服务C, 而服务C 频繁调用服务B 时, 由千其依赖服务A, 大量线程被挂起等待, 最后致使服务A也不能正常服务, 这时就会出现故障的荽延。
因此, 在微服务架构中,快速检测出故障源并尽量地自动恢复服务是必须被设计和考虑的。一般, 咱们都但愿在每一个服务中实现监控和日志记录的组件, 好比服务状态、断路器状态、吞吐量、网络延迟等关键数据的仪表盘等。
经过上面的几点特征, 咱们已经可以体会到, 要实施一个完美的微服务架构, 须要考虑的设计与成本并不小, 对于没有足够经验的团队来讲, 甚至要比单体应用付出更多的代价。
因此, 在不少状况下, 架构师都会以演进的方式进行系统的构建。在初期, 以单体系统的方式来设计和实施, 一方面系统体量初期并不会很大, 构建和维护成本都不高。另外一方面,初期的核心业务在后期一般也不会发生巨大的改变。随着系统的发展或者业务的须要, 架构师会将一些常常变更或是有必定时间效应的内容进行微服务处理, 并逐渐将原来在单体系统中多变的模块逐步拆分出来, 而稳定不太变化的模块就造成一个核心微服务存在于整个架构之中。
近几年不少入对于微服务架构的热情很是高, 可是回头看“微服务” 被说起也有不少年了。无数的架构师和开发者在实际项目中实践该设计理念并为此付出了诸多努力, 同时
也分享了他们在微服务架构中针对不一样应用场景出现的各类问题的各类解决方案和开源框架, 其中也不乏国内互联网企业的杰出贡献。
上面列举了一些在实施微服务架构初期, 就须要被咱们考虑进去的问题,以及针对这些间题的开源解决方案。能够看到国内、国外的技术公司都在贡献着他们的智慧。咱们搜索微服务架构的实施方案时会发现,几乎大部分的分享主要以理论或是一个粗轮廓框架为主, 整合了来自不一样公司或组织的诸多开源框架, 并加入针对自身业务的一些优化, 因此
找不到一个彻底相同的架构方案。
前面咱们介绍了一些关于微服务的理念以及特性, 分析了实施微服务的优势和缺点,而这些缺点一般就是这些框架出现的源头,你们都是为了解决或弥补业务拆分后所引出的诸多词题来设计出这些解决方案。而当咱们做为一个新手, 准备实施微服务架构时, 为了不踩前辈们踩过的坑, 咱们不得不在这些核心问题上作出选择, 而选择又是如此之多,这必然会致使在作技术选型的初期, 须要花费巨大的调研、分析与实验精力。
Spring Cloud的出现,能够说是对微服务架构的巨大支持和强有力的技术后盾。它不像咱们以前所列举的框架那样, 只是解决微服务中的某一个问题, 而是一个解决微服务架构实施的综合性解决框架, 它整合了诸多被普遍实践和证实过的框架做为实施的基础部件,又在该体系基础上建立了一些很是优秀的边缘组件。
打个不太恰当的比喻:咱们本身对各个问题选择框架来实施微服务架构就像在DIY电脑同样, 咱们对各环节的选择自由度很高, 可是最终结果颇有可能由于一条内存质量不行就点不亮了, 老是让人不怎么放心。固然, 若是你是一名高手, 这些天然都不是问题, 然而千军易得、良将难求。而使用Spring Cloud来实施就像直接购买品牌机同样, 在Spring社区的整合之下, 作了大量的兼容性测试, 保证了其拥有更好的稳定性, 若是要在Spring Cloud架构下使用非原装组件时, 就须要对其基础有足够的了解。
Spring Cloud也许对不少已经实施微服务并自成体系的团队不具有足够的吸引力,可是对于还未实施微服务或是未成体系的团队, 这必将是一个很是有吸引力的框架选择。不论其项目的发展目标, 仍是Spring的强大背景, 亦或其极高的社区活跃度, 都是将来企业架构师必须了解和接触的重要框架, 有一天成为微服务架构的标准解决方案也并不是不可能。
Spring Cloud是一个基千Spring Boot实现的微服务架构开发工具。它为微服务架构中涉及的配置管理、服务注册、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等操做提供了一种简单的开发方式。
Spring Cloud包含了多个子项目(针对分布式系统中涉及的多个不一样开源产品,还可能会新增), 以下所述。
Spring Cloud Config
: 配置管理工具, 支持使用Git
存储配置内容, 能够使用它实现应用配置的外部化存储, 并支持客户端配置信息刷新、加密/解密配置内容等。Spring CloudN etflix
: 核心组件,对多个Netflix OSS
开源套件进行整合。
Eureka
: 服务注册组件, 包含服务注册中心、服务注册与发现机制的实现。Hystrix
: 容错管理组件,实现断路器模式, 帮助服务依赖中出现的延迟和为故障提供强大的容错能力。Ribbon
: 客户端负载均衡的服务调用组件。Feign
: 基于伈bbon 和Hystrix 的声明式服务调用组件。Zuul
: 网关组件, 提供智能路由、访问过滤等功能。Archaius
: 外部化配置组件。Spring Cloud Bus
: 事件、消息总线, 用于传播集群中的状态变化或事件, 以触发后续的处理, 好比用来动态刷新配置等。Spring Cloud Cluster
: 针对ZooKeeper
、Redis
、Hazelcast
、Consul
的选举算法和通用状态模式的实现。Spring Cloud Cloudfoundry
: 与Pivotal Cloudfoundry 的整合支持。Spring Cloud Consul
: 服务发现与配置管理工具。Spring Cloud Stream
: 经过Redis、Rabbit 或者Kafka 实现的消费微服务, 能够经过Spring Cloud A WS
: 用千简化整合Amazon Web Service 的组件。Spring Cloud Security
: 安全工具包, 提供在Zuul 代理中对0Auth2 客户端请求的中Spring Cloud Sleuth
: Spring Cloud 应用的分布式跟踪实现, 能够完美整合Zip虹n。Spring Cloud ZooKeeper
: 基于ZooKeeper 的服务发现与配置管理组件。Spring Cloud Starters
: Spring Cloud 的基础组件, 它是基于Spring Boot 风格项目的Spring Cloud CLI
: 用于在Groovy 中快速建立Spring Cloud 应用的Spring Boot CLI本教程将对其中一些较为经常使用的组件进行介绍、分析, 并演示其使用方法。
当咱们经过搜索引擎查找一些Spring Cloud 的文章或示例时, 每每能够在依赖中看到不少不一样的版本名字, 好比Angel.SR六、Brix ton.SR5 等, 为何Spring Cloud 没有像其余Spring 的项目使用相似l.x.x 的版本命名规则呢?这些版本之间又有什么区别呢?在学习之
初,很是有必要弄清楚这些版本的意义和内容, 这样才能在咱们使用Spring Cloud 时, 指导咱们选择更为合适的版本进行架构与开发。
因为Spring Cloud 不像Spring 社区其余一些项目那样相对独立, 它是一个拥有诸多子项目的大型综合项目, 能够说是对微服务架构解决方案的综合套件组合, 其包含的各个子项目也都独立进行着内容更新与迭代,各自都维护着本身的发布版本号。所以每个Spring Cloud 的版本都会包含多个不一样版本的子项目, 为了管理每一个版本的子项目清单, 避免Spring Cloud的版本号与其子项目的版本号相混淆,没有采用版本号的方式,而是经过命名的方式。
这些版本的名字采用了伦敦地铁站的名字, 根据字母表的顺序来对应版本时间顺序,好比最先的Release版本为Angel, 第二个Release版本为Brixton……
通过上面的解释, 不难猜出, 以前所提到的AngelS.SR六、BrixtonS.SR5中的SR六、SR5就是版本号了。
当一个版本的Spring Cloud项目的发布内容积累到临界点或者一个严重bug解决可用后, 就会发布一个"service releases"版本,简称SRX版本, 其中X是一个递增的数字,
因此Brixton.SR5就是Brixton的第5个Release版本。
Spring Cloud Eureka 是Spring Cloud Netflix 微服务套件中的一部分,它基于Netflix Eureka作了二次封装,主要负责完成微服务架构中的服务注册功能。Spring Cloud 经过为Eureka 增长了Spring Boot 风格的自动化配置,咱们只需经过简单引入依赖和注解配置就能让Spring Boot 构建的微服务应用轻松地与Eureka 服务注册体系进行整合。
在本章中, 咱们将学习下面这些核心内容, 并构建起用于服务注册的基础设施。
在一开始,咱们须要先了解微服务中的参与者,他们分别是:服务提供者、服务消费者、服务注册中心。
使用微服务构建的是分布式系统,微服务之间经过网络进行通讯。咱们使用服务提供者与服务消费者来描述微服务之间的调用关系,下表解释了服务提供者与服务消费者。
名词 | 定义 |
---|---|
服务提供者 | 服务的被调用方(即:为其余服务提供服务的服务 |
服务消费者 | 服务的调用方,即依赖其余服务的服务 |
服务注册能够说是微服务架构中最为核心和基础的模块, 它主要用来实现各个微服务实例的自动化注册与发现。为何咱们在微服务架构中那么须要服务注册模块呢?微服务系统没有它会有什么很差的地方吗?
在最初开始构建微服务系统的时候可能服务并很少, 咱们能够经过作一些静态配置来完成服务的调用。好比,有两个服务A 和B, 其中服务A 须要调用服务B 来完成一个业务操做时, 为了实现服务B 的高可用, 不论采用服务端负载均衡仍是客户端负载均衡, 都须要手工维护服务B 的具体实例清单。可是随着业务的发展, 系统功能愈来愈复杂, 相应的微服务应用也不断增长, 咱们的静态配置就会变得愈来愈难以维护。而且面对不断发展的业务, 咱们的集群规模、服务的位置、服务的命名等都有可能发生变化, 若是仍是经过手工维护的方式,那么极易发生错误或是命名冲突等问题。同时,对于这类静态内容的维护
也必将消耗大量的人力。
为了解决微服务架构中的服务实例维护问题, 产生了大量的服务注册框架和产品。这些框架和产品的实现都围绕着服务注册与服务发现机制来完成对微服务应用实例的自动化管理。
使用服务注册中心后的架构以下图所示:
服务提供者、服务消费者、服务注册组件三者的关系大体以下:
综上,服务注册组件应该具有如下功能:
综上,使用服务注册组件的好处显而易见。spring cloud提供了多种服务注册组件的支持,例如:Eureka,Consul,Zookeeper等,在本教材内咱们均使用Eureka为例。
服务注册组件在市面上也可能叫作服务注册组件、服务发现组件、注册中心等名词。
Eureka是Netflix开发的服务发现组件,自己是一个基于REST的服务。Spring Cloud将它集成在其子项目spring-cloud-netflix中,以实现Spring Cloud的服务发现功能。目前Eureka 项目至关活跃,代码更新至关频繁。
Eureka的官方文档对regin、zone几乎没有说起,因为概念抽象,新手很难理解。所以,在分析Eureka原理以前,咱们先来了解一下region、zone、Eureka集群三者的关系,如图:
region和zone(或者Availability Zone)均是AWS的概念。在非AWS环境下,咱们能够简单地将region理解为Eureka集群,zone理解成机房。这样图4-2就很好理解了——一个Eureka集群被部署在了zone1机房和zone2机房中。
Eureka架构图以下
这是来自Eureka官方的架构图,大体描述了Eureka集群的工做过程。图中包含的组件很是多,可能比较难以理解,咱们用通俗易懂的语言解释一下:
由图可知,Eureka包含两个组件:Eureka Server 和 Eureka Client,它们的做用以下:
综上,Eureka经过心跳检测、健康检查和客户端缓存等机制,提升了系统的灵活性、可伸缩性和可用性。
在Spring Cloud实现一个Eureka Server是一件很是简单的事情。下面咱们来写一个Eureka Server 。
由于在spring cloud应用中会涉及到多个项目模块,接下来咱们使用IDEA工具在一个windows下实现多个项目。
第一步 建立一个普通的java项目,存储在一个空的目录下,这里具体步骤省略。
第二步 在上一步建立的项目的基础上,新建一个Module
file
-> new
-> Module
打开以下窗口,选择Spring Initilizr
,点击next
注意,在咱们的示例中采用目前最新的版本的spring cloud,其依赖的spring boot版本为2.0,对JDK的需求是必须JDK1.8或者1.9,再也不支持1.7及如下版本。
填写Module的相关信息,主要是group和Artifact,而后点击next
选择Cloud Discover
中的Eureka Server
,点击next
,而后点击finish
,以下图所示。
注意,尽可能不要改变Module的存储路径,直接将其放在第一步创建的普通java项目目录下。
创建好的Eureka Server目录结构以下图所示:
说明:
pom.xml
是本Module的maven配置文件,EurekaServerApplication.java
是入口程序,EurekaServerApplicationTests.java
是测试入口。
application.properties
是spring cloud的属性配置文件,其也能够是application.yml
,这里咱们用application.yml
。
其实细心的咱们已经发现了,spring cloud的项目结构和spring boot基本同样。区别是在
pom.xml
中增长了spring cloud的依赖管理以及spring cloud Eureka server依赖,具体以下:
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
在`EurekaServerApplication.java`入口类加上一个注解`@EnableEurekaServer`,声明这是一个Eureka Server。
@EnableEurekaServer @SpringBootApplication public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } }
server: port: 8761 eureka: client: registerWithEureka: false fetchRegistry: false
这样就完成了一个简单的Eureka Server。简要说明一下application.yml中的配置项:
eureka.client.registerWithEureka
:表示是否将本身注册到Eureka Server,默认为true。因为当前这个应用就是Eureka Server,故而设为false。
eureka.client.fetchRegistry
:表示是否从Eureka Server获取注册信息,默认为true。由于这是一个单点的Eureka Server,不须要同步其余的Eureka Server节点的数据,故而设为false。
在经过IDEA的Terminal,进入Eureka-Server目录,执行maven打包命令mvn clean package
,打包成功后,再进入target目录,经过java -jar命令执行
出现以下所示信息时,表示服务注册中心已经启动成功。
打开浏览器,输入地址:http://localhost:8761访问,咱们会发现此时尚未服务注册到Eureka上面,以下图:
该页面展现了Eureka的系统状态、当前注册到Eureka Server上的服务实例、通常信息、实例信息等。咱们能够看到,当前尚未任何服务被注册到Eureka Server上。
下面咱们建立提供服务的客户端,并向服务注册中心注册本身。
首先,和建立Eureka Server
应用基本同样,惟一不一样的地方在于,在Cloud Discovery
再也不选择Eureka Server
,而是选择Eureka Discover
。命名为eureka-provider
,在pom.xml
中,变动的配置信息是去掉了server的依赖,而增长了client依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
注意:在spring cloud的
Finchley
版本以前的版本,添加Eureka client依赖的artifactId为spring-cloud-starter-eureka
在启动类加上一个注解@EnableDiscoveryClient
,声明这是一个Eureka client。
@EnableDiscoveryClient @SpringBootApplication public class UserProviderApplication { public static void main(String[] args) { SpringApplication.run(UserProviderApplication.class, args); } }
server: port: 9000 spring: application: name: user-provider eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/
经过spring.application.name
属性,咱们能够指定微服务的名称后续在调用的时候只须要使用该名称就能够进行服务的访问。eureka.client.serviceUrl.defaultZone
属性对应服务注册中心的配置内容,指定服务注册中心的位置。为了在本机上测试区分服务提供方和服务注册中心,使用server.port
属性设置不一样的端口。
在spring cloud中,具体的服务表现为Rest服务,经过spring mvc实现,固然咱们彻底能够经过spring boot简化它,具体代码以下:
@RestController public class HelloProviderController { @Value("${server.port}") private String port; @GetMapping("/hello") public String say() { return String.format("你好,我是一个服务提供者。个人对外的端口是:%s",port); } }
能够经过IDE工具直接运行入口程序,也能够经过maven打包以后运行,在控制台中输出以下内容:
且在eureka-server
端的控制台上有日志消息以下
则表示服务提供者正确在服务注册中心注册了。如今咱们再次经过http://localhost:8761来访问服务注册中心,以下图所示:
若是在Eureka Server的首页看到如下这段提示,则说明Eureka已经进入了保护模式。
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.
保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,再也不删除服务注册表中的数据(也就是不会注销任何微服务)。
在开发过程当中,咱们经常但愿Eureka Server可以迅速有效地踢出已关停的节点,可是因为Eureka自我保护模式,以及心跳周期长的缘由,经常会遇到Eureka Server不踢出已关停的节点的问题。解决方法以下:
(1) Eureka Server端:配置关闭自我保护,并按需配置Eureka Server清理无效节点的时间间隔
eureka.server.enable-self-preservation # 设为false,关闭自我保护 eureka.server.eviction-interval-timer-in-ms # 清理间隔(单位毫秒,默认是60*1000)
(2) Eureka Client端:配置开启健康检查,并按需配置续约更新时间和到期时间。
eureka.client.healthcheck.enabled # 开启健康检查(须要spring-boot-starter-actuator依赖) eureka.instance.lease-renewal-interval-in-seconds # 续约更新时间间隔(默认30秒) eureka.instance.lease-expiration-duration-in-seconds # 续约到期时间(默认90秒)
示例:
服务器端配置:
eureka: server: enable-self-preservation: false eviction-interval-timer-in-ms: 4000
客户端配置:
eureka: client: healthcheck: enabled: true instance: lease-expiration-duration-in-seconds: 30 lease-renewal-interval-in-seconds: 10
注意:
更改Eureka更新频率将打破服务器的自我保护功能,生产环境下不建议自定义这些配置。
下面咱们建立消费服务的客户端,从服务中心查询一个服务,并调用该服务。
服务消费者项目和本质上也是一个Eureka server的客户端,因此和建立服务提供者的方式一致。
在启动类加上一个注解@EnableDiscoveryClient
,声明这是一个Eureka client。
@EnableDiscoveryClient @SpringBootApplication public class UserConsumerApplication { @Bean @LoadBalanced public RestTemplate create(){ return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(UserConsumerApplication.class, args); } }
在启动类中定义了一个RestTemplate的bean,并为它添加了@LoadBalanced
注解,该注解具体含义后面介绍
server: port: 8000 spring: application: name: user-consumer eureka: client: serviceUrl: myZone: http://localhost:8761/eureka/
@RestController public class HelloController { @Autowired private RestTemplate restTemplate; @GetMapping("/call") public String call(){ return restTemplate.getForEntity("http://user-provider/hello",String.class).getBody(); } }
说明:http://user-provider/hello 其中
user-provider
是服务提供者的应用名称,即spring.application.name
,它不区分大小写,而hello
为咱们要消费的具体服务。
这个应用能够理解为服务消费者在服务注册中心经过应用名称user-provider
找到具体提供服务的应用,而后再调用其具体的服务。
启动应用,经过http://localhost:8000/call 访问,结果以下:
在微服务架构这样的分布式环境中咱们须要充分考虑发生故障的状况, 因此在生产环境中必须对各个组件进行高可用部署, 对于微服务如此,对于服务注册中心也同样。可是到本节为止,咱们一直都在使用单节点的服务注册中心,这在生产环境中显然并不合适,咱们须要构建高可用的服务注册中心以加强系统的可用性。EurekaS erver的设计一开始就考虑了高可用问题, 在Eureka的服务注册设计中, 全部节点便是服务提供方, 也是服务消费方, 服务注册中心也不例外。是否还记得在单节点的配置中, 咱们设置过下面这两个参数, 让服务注册中心不注册本身:
eureka: client: registerWithEureka: false fetchRegistry: false
Eureka Server的高可用实际上就是将本身做为服务向其余服务注册中心注册本身,这样就能够造成一组互相注册的服务注册中心, 以实现服务清单的互相同步,达到高可用的效果。下面咱们就来尝试搭建高可用服务注册中心的集群。咱们在前面的服务注册中心的基础之上进行扩展, 构建一个双节点的服务注册中心集群。
注意:若须要实现eureka server集群,须要将以上两个参数设置为true,不然将形成不可用的服务分片
unavailable-replicas
。
spring: application: name: eureka-server #服务名称,Eureka server集群的服务名称必须一致 server: port: 1111 # 端口号 eureka: instance: hostname: peer1 #主机名称 client: serviceUrl: defaultZone: http://peer2:1112/eureka/ # 将本身做为一个微服务应用注册到另一个Eureka server registerWithEureka: true #必须设置为true fetchRegistry: true #必须设置为true server: enable-self-preservation: false eviction-interval-timer-in-ms: 4000
spring: application: name: eureka-server #服务名称,Eureka server集群的服务名称必须一致 server: port: 1112 # 端口号 eureka: instance: hostname: peer2 #主机名称 client: serviceUrl: defaultZone: http://peer1:1111/eureka/ # 将本身做为一个微服务应用注册到另一个Eureka server registerWithEureka: true #必须设置为true fetchRegistry: true #必须设置为true server: enable-self-preservation: false eviction-interval-timer-in-ms: 4000
C:\Windows\System32\drivers\etc\hosts
,Linux及Mac Os等系统的文件路径是/etc/hosts
,在这个文件的最后添加:127.0.0.1 peer1 127.0.0.1 peer2
此时访问peer1的注册中心:http://localhost:1111/,以下图所示,咱们能够看到registered-replicas中已经有peer2节点的eureka-server了。一样地,访问peer2的注册中心:http://localhost:1112/,能看到registered-replicas中已经有peer1节点,而且这些节点在可用分片(available-replicase)之中。咱们也能够尝试关闭peer1,刷新http://localhost:1112/,能够看到peer1的节点变为了避免可用分片(unavailable-replicas)。
在设置了多节点的服务注册中心以后,咱们只须要简单的服务配置,就能将服务注册到Eureka Server集群中。咱们之前面的user-provider为基础,修改application.yml配置文件:
server: port: 9000 spring: application: name: user-provider eureka: client: healthcheck: true serviceUrl: deafultZone: http://peer1:1111/eureka/,http://peer2:1112/eureka/
上面的配置主要对eureka.client.serviceUrl.defaultZone
属性作了改动,将注册中心指向了以前咱们搭建的peer1与peer2,中间以英文逗号分隔。
下面,咱们启动该服务,经过访问http://localhost:1111/和http://localhost:1112/,能够观察到user-provider同时被注册到了peer1和peer2上。若此时断开peer1,因为user-provider同时也向peer2注册,所以在peer2上其余服务依然能访问到compute-service,从而实现了高可用的服务注册中心。
说明:其实
eureka.client.serviceUrl.defaultZone
能够不用指定全部的Eureka Server节点,仅指peer1的话,Eureka Server集群会自动将该服务同步注册到peer2,可是不推荐这么作,由于这么作没法解决Eureka Server集群的单点故障。
虽然上面咱们以双节点做为例子,可是实际上因负载等缘由,咱们每每可能须要在生产环境构建多于两个的Eureka Server节点。那么对于如何配置serviceUrl来让集群中的服务进行同步,须要咱们更深刻的理解节点间的同步机制来作出决策。
Eureka Server的同步遵循着一个很是简单的原则:只要有一条边将节点链接,就能够进行信息传播与同步。什么意思呢?不妨咱们经过下面的实验来看看会发生什么。
访问http://localhost:1112/,能够看到3个注册中心组成了集群,user-provider服务经过peer1同步给了与之互相注册的peer2和peer3。
经过上面的实验,咱们能够得出下面的结论来指导咱们搭建服务注册中心的高可用集群:
两两注册的方式能够实现集群中节点彻底对等的效果,实现最高可用性集群,任何一台注册中心故障都不会影响服务的注册与发现
在上面的示例中,咱们经过Eureka server集群的方式实现了高可用的服务注册中心,接下来咱们须要实现高可用的服务提供者。
在生产环境下,单点的服务提供每每会存在性能不足、可用性不高等缺陷,在spring cloud中能够很是方便的实现高可用的服务提供者,即将某服务应用以集群的形式注册到服务注册中心,不用修改任何的user-provider的代码,仅仅经过下列方式启动多个user-provider的实例便可。
java -jar user-provider-0.0.1-SNAPSHOT.jar --server.port=2001 java -jar user-provider-0.0.1-SNAPSHOT.jar --server.port=2002 java -jar user-provider-0.0.1-SNAPSHOT.jar --server.port=2003
再次访问http://peer1:1111或者http://peer2:1112如图所示,可见注册了3个user-provider服务到Eureka server。
在前面,咱们部署了3个user-provider的实例,那么对于服务消费者来讲,是如何将强求分摊到多个服务提供者身上呢?
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。它是一个基于HTTP和TCP的客户端负载均衡器。当为Ribbon配置服务提供者地址列表后,Ribbon就能够基于某种负载均衡算法,自动地帮助服务消费者去请求。Ribbon默认为咱们提供了不少的负载均衡算法,例如轮询、随机等。固然咱们也能够为Ribbon实现自定义的负载均衡算法。
在spring cloud中,当ribbon与Eureka配合使用时,Ribbon能够自动从Eureka server获取服务提供者地址列表,并给予负载均衡算法,请求其中一个服务提供者实例。下图展现了Ribbon与Eureka配合使用时的大体架构。
Ribbon工做时分为两步:第一步先选择 Eureka Server, 它优先选择在同一个Zone且负载较少的Server;第二步再根据用户指定的策略,在从Server取到的服务注册列表中选择一个地址。其中Ribbon提供了多种策略,例如轮询round robin、随机Random、根据响应时间加权等。
其实咱们不用对服务消费者进行任何的变动,便可实现客户端基于Ribbon的负载均衡。核心缘由在:
spring-cloud-starter-netflix-eureka-client
依赖中已经包含有spring-cloud-starter-netflix-eureka-Ribbon
,全部不用再添加依赖@LoadBalanced
注解,即表明RestTemplate已经整合了Ribbon,无需咱们再进行其余的变动了。如今咱们再次启动user-consumer,并屡次访问http://localhost:3000/call,结果执行为如下一些数据
你好,我是一个服务提供者。个人对外的端口是:2000 你好,我是一个服务提供者。个人对外的端口是:2001 你好,我是一个服务提供者。个人对外的端口是:2002 你好,我是一个服务提供者。个人对外的端口是:2000 你好,我是一个服务提供者。个人对外的端口是:2001 你好,我是一个服务提供者。个人对外的端口是:2002 你好,我是一个服务提供者。个人对外的端口是:2000 ...
能够看到,此时请求均匀分布在3个微服务节点上,说明实现了负载均衡。
在默认状况下,Ribbon和Eureka server集成后,所采用的负载均衡算法为轮询算法。但在生成环境,轮询算法并不是适用于全部的场景,此时咱们就须要修改其负载均衡策略。固然除此以外还有不少Ribbon配置能够修改,此处不作讲解。
在微服务架构中,咱们将系统拆分红了一个个的服务单元,各单元间经过服务注册与订阅的方式互相依赖。因为每一个单元都在不一样的进程中运行,依赖经过远程调用的方式执行,这样就有可能由于网络缘由或是依赖服务自身问题出现调用故障或延迟,而这些问题会直接致使调用方的对外服务也出现延迟,若此时调用方的请求不断增长,最后就会出现因等待出现故障的依赖方响应而造成任务积压,最终致使自身服务的瘫痪。
举个例子,在一个电商网站中,咱们可能会将系统拆分红,用户、订单、库存、积分、评论等一系列的服务单元。用户建立一个订单的时候,在调用订单服务建立订单的时候,会向库存服务来请求出货(判断是否有足够库存来出货)。此时若库存服务因网络缘由没法被访问到,致使建立订单服务的线程进入等待库存申请服务的响应,在漫长的等待以后用户会由于请求库存失败而获得建立订单失败的结果。若是在高并发状况之下,因这些等待线程在等待库存服务的响应而未能释放,使得后续到来的建立订单请求被阻塞,最终致使订单服务也不可用。
在微服务架构中,存在着那么多的服务单元,若一个单元出现故障,就会因依赖关系造成故障蔓延,最终致使整个系统的瘫痪,这样的架构相较传统架构就更加的不稳定。为了解决这样的问题,所以产生了断路器模式。
经过前边的学习,服务注册中心、服务提供者和服务消费者都成功创建并运行起来,并且经过默认的配置RestTemplate
及@Loadbalanced
注解开启了负载均衡。
在默认的状况下,负载均衡策略是线性轮询的方式,也就是说在客户端获取到的服务列表中依次交替,例如开启了三个服务server一、server二、server3,那么在线性轮询时,就会按这个顺序来调用。
我以前是开启了三个服务,一个端口是2000,2001和2002,那么在以前的这种状况下,若是我关闭其中一个服务,就好比这里关闭2001端口的服务,当再次访问的时候,每访问三次,就会有一次是以下的error page,直到我挂掉的这个服务被服务注册中心剔除前均会存在。
若是服务提供者响应很是缓慢,name消费者对提供者的请求就会被强制等待,知道提供者响应或超时。在高负载场景下,若是不作任何处理,此类问题可能会致使服务消费者的资源耗尽甚至整个系统的崩溃。例如,曾经发生过一个案例----某电子商务网站在一个黑色星期五发生过过载。过多的并发请求,致使用户支付的请求延迟好久都没有响应,在等待很长时间后最终失败。支付失败又致使用户从新刷新页面并再次尝试支付,进一步增长了服务器的负载,最终致使整个系统都崩溃了。
当依赖的服务不可用时,服务自身会不会被拖垮,这是咱们在构建分布式应用时须要考虑的问题。
微服务架构的应用系统一般包含多个服务层。微服务之间经过网络进行通讯,从而支撑整个应用系统,所以,微服务之间不免存在依赖关系。咱们知道任何微服务都并不是100%可用,网络每每也很脆弱,所以不免有些请求会失败。
咱们常把“基础服务故障”致使“级联故障”的现象称为雪崩效应。雪崩效应描述的是提供者不可用致使消费者不可用,并将不可用逐渐放大的过程。
以下图,A做为服务提供者(基础服务),B为A的服务消费者,C和D都是B的消费者。当A不可用引发B的不可用,并将不可用像滚雪球同样放大到C和D时,雪崩效应就造成了。
要想防止雪崩效应,必须有一个强大的容错机制。该容错机制需事先如下两点:
hystrix是一个实现了超时机制和断路器模式的工具类库。
hystrix是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务、或者第三方库,防止级联失败,从而提高系统的可用性与容错性。
hystrix主要经过如下几点实现容错和延迟:
包裹请求
使用hystrixCommand(或hystrixObservableCommand)包裹对依赖的调用逻辑,每一个命令在独立线程中执行。这使用到了设计模式中的“命令模式”。
跳闸机制
当某服务的错误率超过必定阔值时,hystrix能够自动或手动跳闸,中止请求该服务一段时间。
资料隔离
hystrix为每一个依赖都维护了一个小型的线程池(或信号量)。若是该线程池已满,发往该依赖的请求就会被当即拒绝,而不是排队等待,从而加速失败断定。
监控
hystrix能够近乎实时的监控运行指标和配置的变化,例如成功、失败、超时以及被拒绝的请求等
回退机制
当请求失败、超时、被拒绝,或当断路器打开时,执行回退逻辑。回退逻辑可由开发人员自行提供,例如返回一个缺省值。
自我修复
断路器打开一段时间后,会自动进入“半开”状态。
第一步 : 在服务消费者的pom.xml
中增长hystrix的依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
注意,在springcloud的
Finchley
版本以前的starter是
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency>
第二步: 在服务消费者的启动类上加上注解@EnableHystrix
说明:
@EnableHystrix
注解能够使用@EnableCircuitBreaker
注解来替代,代码以下:
@EnableCircuitBreaker @EnableDiscoveryClient @SpringBootApplication public class UserConsumerApplication {...}
在springboot中提供了@SpringCloudApplication
来定义spring cloud应用,他整合了多个注解,主要包含服务发现和断路器这两个注解,代码以下:
@SpringCloudApplication public class UserConsumerApplication{...}
第三步: 修改Controller
@RestController public class HelloController { @Autowired private RestTemplate restTemplate; @GetMapping("/call") @HystrixCommand(fallbackMethod = "whenCallError") public String call(){ return restTemplate.getForEntity("http://user-provider/hello",String.class).getBody(); } public String whenCallError(){ return "远程服务发生错误了,该功能暂时不可用."; } }
代码中,call方法增长了注解
@HystrixCommand(fallbackMethod = "whenCallError")
,表示若远程微服务消费不成功,则执行fallbackMethod
所指定的方法,这叫服务回退,也叫服务的降级。
注意
fallbackMethod
所指定的方法的返回类型必须是call
方法的返回类型兼容。
第四步: 启动消费者应用,访问http://localhost:3000/call
发如今正常状况下未有任何影响,可是若服务提供者因某些缘由没法正常被消费,好比服务提供者宕机不可访问时,直接响应的是
咱们在使用Spring Cloud 伈bbon 时, 一般都会利用它对RestTemplate 的请求拦截来实现对依赖服务的接口调用, 而RestTemplate 已经实现了对HTTP 请求的封装处理, 造成了一套模板化的调用方法。在以前的例子中,咱们只是简单介绍了RestTemplate 调用的实现,可是在实际开发中,因为对服务依赖的调用可能不止于一处,每每一个接口会被多处调用,因此咱们一般都会针对各个微服务自行封装一些客户端类来包装这些依赖服务的调用。这个时候咱们会发现, 因为RestTemplate 的封装, 几乎每个调用都是简单的模板化内容。综合上述这些状况, Spring Cloud Feign 在此基础上作了进一步封装, 由它来帮助咱们定义和实现依赖服务接口的定义。在Spring Cloud Feign 的实现下, 咱们只需建立一个接口并用注解的方式来配置它, 便可完成对服务提供方的接口绑定, 简化了在使用Spring Cloud伈bbon 时自行封装服务调用客户端的开发量。
Spring Cloud Feign是一套基于Netflix Feign实现的声明式服务调用客户端。它使得编写Web服务客户端变得更加简单。咱们只须要经过建立接口并用注解来配置它就可完成对Web服务接口的绑定。它具有可插拔的注解支持,包括Feign注解、JAX-RS注解。它也支持可插拔的编码器和解码器。Spring Cloud Feign还扩展了对Spring MVC注解的支持,同时还整合了Ribbon和Eureka来提供均衡负载的HTTP客户端实现。
下面,咱们经过一个例子来展示Feign如何方便的声明对eureka-client服务的定义和调用。
下面的例子,咱们将利用以前构建的eureka-server
做为服务注册中心、user-provider
做为服务提供者做为基础。而基于Spring Cloud Ribbon实现的消费者,咱们能够根据user-consumer
实现的内容进行简单改在就能完成,具体步骤以下:
在user-consumer
中添加Feign依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
Finchley以前版本,Fenign的artifactId为:
<artifactId>spring-cloud-starter-feign</artifactId>
修改应用启动类。经过@EnableFeignClients
注解开启扫描Spring Cloud Feign客户端的功能,同时取消掉RestTEmplate的bean定义,固然若是在你的应用中仍然想用的话也能够继续保留。
@EnableFeignClients @SpringCloudApplication public class UserConsumerApplication { public static void main(String[] args) { SpringApplication.run(UserConsumerApplication.class, args); } }
建立一个Feign的客户端接口定义,使用@FeignClient
注解来指定这个接口所要调用的服务名称,接口中定义的各个函数使用Spring MVC的注解就能够来绑定服务提供方的REST接口,好比下面就是绑定user-provider服务的/hello接口的例子:
@FeignClient(name = "user-provider") public interface HelloClient { @GetMapping("/hello") String hello(); }
HelloController
修改Controller。经过定义的feign客户端来调用服务提供方的接口:
@RestController public class HelloController { @Autowired private HelloClient helloClient; @GetMapping("/call") @HystrixCommand(fallbackMethod = "whenCallError") public String call(){ return helloClient.hello(); } public String whenCallError(){ return "远程服务发生错误了,该功能暂时不可用."; } }
能够看到经过Spring Cloud Feign来实现服务调用的方式更加简单了,经过@FeignClient
定义的接口来统一的声明咱们须要依赖的微服务接口。而在具体使用的时候就跟调用本地方法一点的进行调用便可。因为Feign是基于Ribbon实现的,因此它自带了客户端负载均衡功能,也能够经过Ribbon的IRule进行策略扩展。另外,Feign还整合的Hystrix来实现服务的容错保护,在Finchley版本中,Feign的Hystrix默认是打开的的。可是在在Dalston版本中,Feign的Hystrix默认是关闭的。
在完成了上面的代码编写以后,读者能够将eureka-server、user-provider、user-consumer都启动起来,而后访问http://localhost:3000/call ,来跟踪观察user-consumer服务是如何消费user-provider服务的/hello接口的,而且也能够经过启动多个user-provider服务来观察其负载均衡的效果。
在前面的示例中,服务提供者都是提供的get
方式的请求,而且未携带参数,但在实际应用场景中,这是彻底不能知足业务需求的,接下来咱们经过一个对用户进行CRUD的示例来看看如何实现其余的请求方法,以及参数的传递。
由于在user-provider和user-consumer中,均须要使用到相同的POJO对象User,因此咱们创建一个公共的maven模块,在这里面定义User.java
,并在user-provider和user-consumer中进行引用。
其实不只是User.java
,在分布式微服务应用领域,有不少通用的java类,咱们均可以将其从各个子模块中抽象出来,创建成一个单独的模块,而后在须要使用的地方引入便可。
新建一个空的maven项目,其pom.xml
内容以下:
```XML
<groupId>com.dengcl</groupId> <artifactId>springcloud-base</artifactId> <version>1.0-SNAPSHOT</version>
User.java
,其代码以下:mvn install
发布springcloudbase到本地maven资源库<dependency> <groupId>com.dengcl</groupId> <artifactId>springcloud-base</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
在user-provider中添加UserController.java
,其代码以下:
package com.dengcl.userprovider.controller; import com.dengcl.springcloud.pojo.User; import org.springframework.web.bind.annotation.*; import java.util.*; @RestController @RequestMapping(value="/users") // 经过这里配置使下面的映射都在/users下 public class UserController { // 建立线程安全的Map static Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>()); @RequestMapping(value={""}, method=RequestMethod.GET) public List<User> getUserList() { List<User> r = new ArrayList<User>(users.values()); return r; } @RequestMapping(value="", method= RequestMethod.POST) public String postUser(@RequestBody User user) { users.put(user.getId(), user); return "success"; } @RequestMapping(value="/{id}", method=RequestMethod.GET) public User getUser(@PathVariable Long id) { return users.get(id); } @RequestMapping(value="/{id}", method=RequestMethod.PUT) public String putUser(@PathVariable Long id, @RequestBody User user) { User u = users.get(id); u.setName(user.getName()); u.setAge(user.getAge()); users.put(id, u); return "success"; } @RequestMapping(value="/{id}", method=RequestMethod.DELETE) public String deleteUser(@PathVariable Long id) { users.remove(id); return "success"; } }
在这个示例中,咱们经过一个静态的线程安全的Map来存储用户信息,这种存储方式仅适用于服务提供者不采用集群的时候。固然彻底能知足咱们如今要实现的目标。
@FeignClient("user-provider") public interface UserClient { @GetMapping("/users") List<User> getUserList() ; @PostMapping("/users") public String postUser(@RequestBody User user) ; @GetMapping("/users/{id}") public User getUser(@PathVariable("id") Long id) ; @PutMapping("/users/{id}") public String putUser(@PathVariable("id") Long id, @RequestBody User user) ; @DeleteMapping("/users/{id}") public String deleteUser(@PathVariable("id") Long id) ; }
特别注意:
@PathVariable
,必须指定其value,不能省略,必须指定。@RestController @RequestMapping("/userconsumer") public class UserConsumerController { @Autowired private UserClient userClient; @RequestMapping(value={""}, method= RequestMethod.GET) public List<User> getUserList() { return userClient.getUserList(); } @RequestMapping(value="", method= RequestMethod.POST) public String postUser(@RequestBody User user) { return userClient.postUser(user); } @RequestMapping(value="/{id}", method=RequestMethod.GET) public User getUser(@PathVariable Long id) { return userClient.getUser(id); } @RequestMapping(value="/{id}", method=RequestMethod.PUT) public String putUser(@PathVariable Long id, @RequestBody User user) { return userClient.putUser(id,user); } @RequestMapping(value="/{id}", method=RequestMethod.DELETE) public String deleteUser(@PathVariable Long id) { return userClient.deleteUser(id); } }
OK,接下来能够经过PostMan
来测试这些接口是否正确了。
在前面介绍Hystrix时,咱们经过@HystrixCommand(fallbackMethod = "whenCallError")
实现了服务的降级处理,可是若是用Feign客户端的话,那么又如何来实现服务降级呢?
代码以下:
@Component public class UserClientFallback implements UserClient { @Override public List<User> getUserList() { return new ArrayList<>(); } @Override public String postUser(User user) { return "error"; } @Override public User getUser(Long id) { User user = new User(); user.setId(id); user.setName("unknown"); return user; } @Override public String putUser(Long id, User user) { return "error"; } @Override public String deleteUser(Long id) { return "error"; } }
注意这个类须要
@Component
注解把它加入到spring 容器。
其实只须要修改这个接口的@FeignClient
注解就能够了。具体以下:
@FeignClient(name = "user-provider",fallback = UserClientFallback.class) public interface UserClient { }
在Spring Cloud Feign中,除了引入了用于客户端负载均衡的Spring Cloud Ribbon以外,还引入了服务保护与容错的工具Hystrix。默认状况下,Spring Cloud Feign会为将全部Feign客户端的方法都封装到Hystrix命令中进行服务保护。
默认状况下,Spring Cloud Feign会为将全部Feign客户端的方法都封装到Hystrix命令中进行服务保护。这个说法在Finchley版本以前没有错,可是在Finchley版本中正好相反,在该版本中,Feign客户端的Hystrix熔断器是默认关闭的,须要咱们手动开启。
开启的方式为在application.yml
中增长:
### 开启全局的hystrix熔断器,在Finchley版本中是默认关闭的,其余版本默认打开 feign: hystrix: enabled: true
如今咱们关闭user-provider应用,而后测试user-consumer的User相关接口,发现熔断器已经工做。
经过以前Spring Cloud中几个核心组件的介绍,咱们已经能够构建一个简略的(不够完善)微服务架构了。好比下图所示:
咱们使用Spring Cloud Netflix中的Eureka实现了服务注册中心以及服务注册与发现;而服务间经过Ribbon或Feign实现服务的消费以及均衡负载;经过Spring Cloud Config实现了应用多环境的外部化配置以及版本管理。为了使得服务集群更为健壮,使用Hystrix的融断机制来避免在微服务架构中个别服务出现异常时引发的故障蔓延。
在该架构中,咱们的服务集群包含:内部服务Service A和Service B,他们都会注册与订阅服务至Eureka Server,而Open Service是一个对外的服务,经过均衡负载公开至服务调用方。本文咱们把焦点汇集在对外服务这块,这样的实现是否合理,或者是否有更好的实现方式呢?
先来讲说这样架构须要作的一些事儿以及存在的不足:
面对相似上面的问题,咱们要如何解决呢?下面进入正题:服务网关!
为了解决上面这些问题,咱们须要将权限控制这样的东西从咱们的服务单元中抽离出去,而最适合这些逻辑的地方就是处于对外访问最前端的地方,咱们须要一个更强大一些的均衡负载器,它就是本文未来介绍的:服务网关。
服务网关是微服务架构中一个不可或缺的部分。经过服务网关统一贯外系统提供REST API的过程当中,除了具有服务路由、均衡负载功能以外,它还具有了权限控制等功能。Spring Cloud Netflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的做用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体可以具有更高的可复用性和可测试性。
下面咱们经过实例例子来使用一下Zuul来做为服务的路有功能。
在构建服务网关以前,咱们先准备一下网关内部的微服务,咱们直接使用前几篇编写的内容:
启动以上3个服务,其中eureka-server和user-provider以集群的方式启动,此处再也不累述。
全部的准备工做就以就绪,下面咱们来试试使用Spring Cloud Zuul来实现服务网关的功能。
第一步:使用Spring Cloud Zuul来构建服务网关的基础步骤很是简单,咱们能够直接使用IDEA工具的Spring Initilizr
向导创建,其余步骤省略,仅展现选取zuul
的截图以下:
第二步:确保pom.xml
中有关于zuul和eureka client的依赖,由于zuul自己也将做为一个微服务注册到服务注册中心。
<!--ZUUL 依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> <!--eureka client依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
第三步:修改启动类,将启动类定义为spring cloud的启动类,同时开启zuul代理支持,即在启动类中使用两个注解:@SpringCloudApplication
和@EnableZuulProxy
第四步:修改配置文件application.yml
,定义端口、应用名称、eureka server注册中心地址,内容以下:
spring: application: name: api-gateway server: port: 10000 eureka: client: serviceUrl: defaultZone: http://peer1:1111/eureka/,http://peer2:1112/eureka/
到这里,一个基于Spring Cloud Zuul服务网关就已经构建完毕。启动该应用,一个默认的服务网关就构建完毕了。因为Spring Cloud Zuul在整合了Eureka以后,具有默认的服务路由功能,即:当咱们这里构建的api-gateway
应用启动并注册到eureka以后,服务网关会发现上面咱们启动的两个服务user-provider
和user-consumer
,这时候Zuul就会建立两个路由规则。每一个路由规则都包含两部分,一部分是外部请求的匹配规则,另外一部分是路由的服务ID。针对当前示例的状况,Zuul会建立下面的两个路由规则:
user-provider
服务的请求规则为:/user-provider/**user-consumer
服务的请求规则为:/user-consumer/**最后,咱们能够经过访问10000端口的服务网关来验证上述路由的正确性:
经过上面的构建内容,咱们已经为全部内部服务提供了一个统一的对外入口,同时对于服务的路由都是自动建立了,减小了传统方式大量的运维配置工做。
经过前面的学习,咱们已经可以实现请求的路由功能,因此咱们的微服务应用提供的接口就能够经过统一的API网关入口被客户端访问到了。可是,每一个客户端用户请求微服务应用提供的接口时,它们的访问权限每每都须要有必定的限制,系统并不会将全部的微服务接口都对它们开放。然而,目前的服务路由并无限制权限这样的功能,全部请求都会被毫无保留地转发到具体的应用并返回结果,为了实现对客户端请求的安全校验和权限控制,最简单和粗暴的方法就是为每一个微服务应用都实现一套用于校验签名和鉴别权限的过滤器或拦截器。不过,这样的作法并不可取,它会增长往后的系统维护难度,由于同一个系统中的各类校验逻辑不少状况下都是大体相同或相似的,这样的实现方式会使得类似的校验逻辑代码被分散到了各个微服务中去,冗余代码的出现是咱们不但愿看到的。因此,比较好的作法是将这些校验逻辑剥离出去,构建出一个独立的鉴权服务。
在完成了剥离以后,有很多开发者会直接在微服务应用中经过调用鉴权服务来实现校验,可是这样的作法仅仅只是解决了鉴权逻辑的分离,并无在本质上将这部分不属于业余的逻辑拆分出原有的微服务应用,冗余的拦截器或过滤器依然会存在。
对于这样的问题,更好的作法是经过前置的网关服务来完成这些非业务性质的校验。因为网关服务的加入,外部客户端访问咱们的系统已经有了统一入口,既然这些校验与具体业务无关,那何不在请求到达的时候就完成校验和过滤,而不是转发后再过滤而致使更长的请求延迟。同时,经过在网关中完成校验和过滤,微服务应用端就能够去除各类复杂的过滤器和拦截器了,这使得微服务应用的接口开发和测试复杂度也获得了相应的下降。
为了在API网关中实现对客户端请求的校验,咱们将须要使用到Spring Cloud Zuul的另一个核心功能:过滤器。
Zuul容许开发者在API网关上经过定义过滤器来实现对请求的拦截与过滤,实现的方法很是简单,咱们只须要继承ZuulFilter
抽象类并实现它定义的四个抽象函数就能够完成对请求的拦截和过滤了。
好比下面的代码,咱们定义了一个简单的Zuul过滤器,它实现了在请求被路由以前检查HttpServletRequest
的请求头中是否有accessToken
参数,如有就进行路由,若没有就拒绝访问,返回401 Unauthorized错误。
@Component public class AccessFilter extends ZuulFilter { @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); String token = request.getHeader("accessToken"); if(StringUtils.isBlank(token)){ //令zuul过滤该请求,不对其进行路由 ctx.setSendZuulResponse(false); //设置了其返回的错误码 ctx.setResponseStatusCode(HttpServletResponse.SC_UNAUTHORIZED); } return null; } }
在上面实现的过滤器代码中,咱们经过继承ZuulFilter
抽象类并重写了下面的四个方法来实现自定义的过滤器。这四个方法分别定义了:
filterType
:过滤器的类型,它决定过滤器在请求的哪一个生命周期中执行。该函数须要返回一个字符串来表明过滤器的类型,而这个类型就是在HTTP请求过程当中定义的各个阶段。在Zuul中默认定义了四种不一样生命周期的过滤器类型,具体以下:
pre
:能够在请求被路由以前调用。routing
:在路由请求时候被调用。post
:在routing和error过滤器以后被调用。error
:处理请求时发生错误时被调用。filterOrder
:过滤器的执行顺序。当请求在一个阶段中存在多个过滤器时,须要根据该方法返回的值来依次执行。经过int值来定义过滤器的执行顺序,数值越小优先级越高。shouldFilter
:判断该过滤器是否须要被执行。这里咱们直接返回了true,所以该过滤器对全部请求都会生效。实际运用中咱们能够利用该函数来指定过滤器的有效范围。run
:过滤器的具体逻辑。这里咱们经过ctx.setSendZuulResponse(false)
令zuul过滤该请求,不对其进行路由,而后经过ctx.setResponseStatusCode(401)
设置了其返回的错误码,固然咱们也能够进一步优化咱们的返回,好比,经过ctx.setResponseBody(body)
对返回body内容进行编辑等。在实现了自定义过滤器以后,它并不会直接生效,咱们还须要为其建立具体的Bean才能启动该过滤器,此处直接在类上增长了注解@Component
。
在对api-gateway
服务完成了上面的改造以后,从新启动它,并发起下面的请求,对上面定义的过滤器作一个验证:
利用postMan构建一个url为http://localhost:10000/user-consumer/call的get请求,不带请求头accessToken时的结果以下图,可见返回了401错误。
接下来请求头中带上accessToken的结果以下图,可见正确的路由到了user-consume的/call接口,并返回告终果。
到这里,对于Spring Cloud Zuul过滤器的基本功能就以介绍完毕。能够根据本身的须要在服务网关上定义一些与业务无关的通用逻辑实现对请求的过滤和拦截,好比:签名校验、权限校验、请求限流等功能。
在微服务架构的系统中, 咱们一般会使用轻量级的消息代理来构建一个共用的消息主题让系统中全部微服务实例都链接上来, 因为该主题中产生的消息会被全部实例监听和消费, 因此咱们称它为消息总线。在总线上的各个实例均可以方便地广播一些须要让其余链接在该主题上的实例都知道的消息, 例如配置信息的变动或者其余一些管理操做等。
因为消息总线在微服务架构系统中被普遍使用, 因此它同配置中心同样, 几乎是微服务架构中的必备组件。Spring Cloud 做为微服务架构综合性的解决方案,对此天然也有本身的实现, 这就是本章咱们将要具体介绍的Spring Cloud Bus。经过使用Spring Cloud Bus,能够很是容易地搭建起消息总线,同时实现了一些消息总线中的经常使用功能,好比,配合Spring Cloud Config 实现微服务应用配置信息的动态更新等。
在本章中, 咱们将从消息代理的基础开始, 由浅入深地介绍如何使用Spring Cloud Bus构建微服务架构中的消息总线。
消息代理(Message Broker) 是一种消息验证、传输、路由的架构模式。它在应用程序之间起到通讯调度并最小化应用之间的依赖的做用, 使得应用程序能够高效地解耦通讯过程。消息代理是一个中间件产品, 它的核心是一个消息的路由程序, 用来实现接收和分发消息,并根据设定好的消息处理流来转发给正确的应用。它包括独立的通讯和消息传递协议, 可以实现组织内部和组织间的网络通讯。设计代理的目的就是为了可以从应用程序中传入消息, 并执行一些特别的操做,下面这些是在企业应用中, 咱们常常须要使用消息代理的场景:
目前已经有很是多的开源产品能够供你们使用, 好比:
当前版本的Spring Cloud Bus仅支待两款中间件产品: RabbitMQ和Kafka。在下面的章节中, 咱们将介绍如何使用RabbitMQ与Spring Cloud Bus配合实现消息总线。
RabbitMQ是实现了高级消息队列协议CAMQP)的开源消息代理软件, 也称为面向消息的中间件。RabbitMQ服务器是用高性能、可伸缩而闻名的Erlang语言编写而成的, 其
集群和故障转移是构建在开放电信平台框架上的。
AMQP是Advanced Message Queuing Protocol的简称,它是一个面向消息中间件的开放式标准应用层协议。它定义了如下这些特性:
- 消息方向
- 消息队列
- 消息路由(包括点到点和发布-订阅模式)
- 可靠性
- 安全性
AMQP要求消息的提供者和客户端接收者的行为要实现对不一样供应商能够用相同的方式(好比SMTP、HTTP、FTP等)进行互相操做。在以往的中间件标准中, 主要仍是创建在API级别, 好比JMS, 集中于经过不一样的中间件实现来创建标准化的程序间的互操做性, 而不是在多个中间件产品间实现互操做性。
AMQP与JMS不一样,JMS定义了一个API和一组消息收发必须实现的行为,而AMQP是一个线路级协议。线路级协议描述的是经过网络发送的数据传输格式。所以,任何符合该数据格式的消息发送和接收工具都能互相兼容和进行操做,这样就能轻易实现跨技木平台的架构方案。
RabbitMQ以AMQP协议实现, 因此它能够支持多种操做系统、多种编程语言, 几乎能够覆盖全部主流的企业级技术平台。在微服务架构消息中间件的选型中, 它是一个很是适合且优秀的选择。 所以, 在SpringCloudB us中包含了对Rabbit的自动化默认配置, 在下面的章节中, 咱们将先从RabbitMQ的基础安装和使用开始, 按部就班地学习如何与SprinCg loudB us进行整合实现消息总线。
在开始具体实践以前, 咱们先介绍一些关于RabbitMQ的基本概念,.
Broker
: 能够理解为消息队列服务器的实体, 它是一个中间件应用, 负责接收消息生产者的消息, 而后将消息发送至消息接收者或者其余的Broker
。Exchange
: 消息交换机, 是消息第一个到达的地方, 消息经过它指定的路由规则,分发到不一样的消息队列中去。Queue
: 消息队列, 消息经过发送和路由以后最终到达的地方, 到达Queue
的消息即进入逻辑上等待消费的状态。每一个消息都会被发送到一个或多个队列。Binding
: 绑定, 它的做用就是把Exchange
和Queue
按照路由规则绑定起来, 也就是Exchange
和Queue
之间的虚拟链接。Routing Key
: 路由关键字,Exchange
根据这个关键字进行消息投递。Virtual host
: 虚拟主机, 它是对Broker
的虚拟划分, 将消费者、生产者和它们依赖的AMQP相关结构进行隔离,通常都是为了安全考虚。好比,咱们能够在一个Broker
中设置多个虚拟主机, 对不一样用户进行权限的分离。Connection
: 链接, 表明生产者、消费者、Broker之间进行通讯的物理网络。Channel
: 消息通道,用千链接生产者和消费者的逻辑结构。在客户端的每一个链接里,可创建多个Channel
, 每一个Channel
表明一个会话任务, 经过Channel能够隔离同一链接中的不一样交互内容。Producer
: 消息生产者, 制造消息并发送消息的程序。Consumer
: 消息消费者, 接收消息并处理消息的程序。消息投递到队列的整个过程大体以下:
Channel
。Exchange
, 并设置相关属性。Queue
, 并设置相关属性。Routing Key
, 在Exchange
和Queue
之间创建好绑定关系。Exchange
。Exchange
接收到消息后,根据消息的Key
和已经设置的Binding
,进行消息路由,将消息投递到一个或多个Queue
里。Exchange也有几种类型。
Direct
交换机:彻底根据Key进行投递。好比,绑定时设置了Routing Key为abc,那么客户端提交的消息,只有设置了Key为abc 的才会被投递到队列。Topic
交换机:对Key进行模式匹配后进行投递,能够使用符号#匹配一个或多个词,符号*匹配正好一个词。好比,abc.#匹配abc.def.ghi,abc.*只匹配abc.def 。Fanout
交换机:不须要任何Key,它采起广播的模式,一个消息进来时,投递到与该交换机绑定的全部队列。RabbitMQ支持消息的待久化,也就是将数据写在磁盘上。为了数据安全考虑,大多数状况下都会选择持久化。消息队列持久化包括3个部分:
Exchange
持久化,在声明时指定durable => 1
。Queue
待久化,在声明时指定durable => 1
。delivery_mode => 2
(1是非持久化)。若是Exchange和Queue都是持久化的,那么它们之间的Binding也是持久化的。若是Exchange和Queue二者之间有一个是待久化的,一个是非持久化的,就不容许创建绑定。
因为Rabbit MQ 是创建在强大的Erlang OTP平台上,所以咱们须要先安装Erlang,而后在安装RabbitMQ.
在Windows的安装过程当中, 有时候会碰到服务启动失败的状况, 一般都是因为windows用户名为中文, 致使默认的db和log目录访问出现问题。要解决该问题, 须要先卸载RabbitMQ Server, 而后设置环境变量
RABBITMQ BASE
为一个不含中文的路径, 好比E:\server\rabbitmq
。最后, 从新安装RabbitMQ便可。
Rabbit管理
咱们能够直接经过访问配置文件进行管理, 也能够经过访问Web进行管理。下面将介绍如何经过Web进行管理。
在命令行执行rabbitmq-plugins enable rabbitmq management
命令, 开启Web管理插件, 这样就能够经过浏览器来进行管理了。
打开浏览器并访问http://localhost:15672/
, 并使用默认用户guest
登陆,密码也为guest
。能够看到以下图所示的管理页面:
从图中咱们能够看到以前提到的一些基本概念, 好比Connections、Channels、Exchanges、Queues 等。能够点开各项看看都有些什么内容, 熟悉一下RabbitMQ Server 的服务端。
dengcl
的用户。其中, Tags 标签是RabbitMQ 中的角色分类, 共有下面几种。
接下来,咱们经过在Spring Boot应用中整合RabbitMQ, 实现一个简单的发送、接收消息的例子来对RabbitMQ有一个直观的感觉和理解。
在SpringBoot中整合RabbitMQ是一件很是容易的事,由于以前咱们已经介绍过Starter
POMs, 其中的AMQP
模块就能够很好地支持RabbitMQ, 下面咱们就来详细说说整合过程。
rabbitmq-sender
。pom.xml
中引入以下依赖内容, 其中spring-boot-starter-amqp
用于支持RabbitMQ。<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
application.properties
中配置关于RabbitMQ的链接和用户信息,这里使用以前安装时建立的dengcl
。#服务名称 spring.application.name=rabbitmq-sender ############################### #### RabbitMQ服务器相关配置##### ############################### #rabbitmq server地址,默认localhost spring.rabbitmq.host=localhost #rabbitmq server端口,默认5672 spring.rabbitmq.port=5672 #rabbitmq server用户名,默认guest spring.rabbitmq.username=dengcl #rabbitmq server密码,默认guest spring.rabbitmq.password=123456
AmqpTemplate
接口的实例来实现消息的发送AmqpTemplate
接口定义了一套针对AMQP
协议的基础操做。在Spring Boot
中会根据配置来注入其具体实现。在该生产者中,咱们会产生一个字符串, 并发送到名为hello
的队列中。@Slf4j /// @Component public class Sender { //队列名称 public static final String QUEUE_NAME="hello"; @Autowired private AmqpTemplate amqpTemplate; public void sendMsg(String msg){ log.info("准备发送消息到RabbitMQ Server.消息是{}",msg); amqpTemplate.convertAndSend(QUEUE_NAME,msg); } }
@RunWith(SpringRunner.class) @SpringBootTest public class RabbitmqSenderApplicationTests { public static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Autowired private Sender sender; @Test public void testSend(){ sender.sendMsg("我发送了一个消息,如今的时间是:"+sdf.format(new Date())); } }
rabbitmq-receiver
。pom.xml
中引入以下依赖内容, 其中spring-boot-starter-amqp
用于支持RabbitMQ。<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
application.properties
中配置关于RabbitMQ的链接和用户信息,这里使用以前安装时建立的dengcl
。#服务名称 spring.application.name=rabbitmq-receiver ############################### #### RabbitMQ服务器相关配置##### ############################### #rabbitmq server地址,默认localhost spring.rabbitmq.host=localhost #rabbitmq server端口,默认5672 spring.rabbitmq.port=5672 #rabbitmq server用户名,默认guest spring.rabbitmq.username=dengcl #rabbitmq server密码,默认guest spring.rabbitmq.password=123456
以上步骤和建立消息发布者一致。
@RabbitListener
注解定义该类对hello
队列的监听, 并用@Rabb江Handler
注解来指定对消息的处理方法。因此,该消费者实现了对hello
队列的消费, 消费操做为输出消息的字符串内容。@Slf4j @Component @RabbitListener(queues = "hello") public class Receiver { @RabbitHandler public void process(String msg){ log.info("接收到RabbitMQ中的queue为hello的消息,消息的内容是:{}",msg); } }
经过启动类运行rabbitmq-receiver
;从控制台中,咱们可看到以下内容,程序建立了一个访问127.0.0.1:5672
中dengcl的链接。
2018-03-29 14:40:42.225 INFO 11444 --- [cTaskExecutor-1] o.s.a.r.c.CachingConnectionFactory : Created new connection: rabbitConnectionFactory#13d73fa:0/SimpleConnection@6ddac9a1 [delegate=amqp://dengcl@127.0.0.1:5672/, localPort= 61925]同时,咱们经过RabbitMQ 的控制面板,能够看到Connections 和Channels中包含当前链接的条目。
运行rabbitmq-sender
的测试类。咱们能够在控制台中看到下面的输出内容, 消息被发送到了RabbitMQ Server 的hello 队列中。
2018-03-29 14:41:02.266 INFO 16316 --- [ main] com.dengcl.rabbitmqsender.mq.Sender : 准备发送消息到RabbitMQ Server.消息是我发送了一个消息,如今的时间是:2018-03-29 14:41:02
切换到rabbitmq-receiver
应用主类的控制台,咱们能够看到相似以下的输出,消费者对hello 队列的监听程序执行了, 并输出了接收到的消息信息。
2018-03-29 14:41:02.424 INFO 11444 --- [cTaskExecutor-1] com.dengcl.rabbitmqreceiver.mq.Receiver : 接收到RabbitMQ中的queue为hello的消息,消息的内容是:我发送了一个消息,如今的时间是:2018-03-29 14:41:02
经过上面的示例,咱们在Spring Boot 应用中引入spring-boot-starter-amqp
模块, 进行简单配置就完成了对RabbitMQ 的消息生产和消费的开发内容。然而在实际应用中, 还有不少内容没有演示, 好比以前提到的一些概念: 交换机、路由关键字、绑定、虚拟主机等, 这里不作更多的讲解, 你们能够自行查阅RabbitMQ 的官方教程, 其中有更全面的讲解。 咱们须要重点理解的是, 在整个生产消费过程当中, 生产和消费是一个异步操做,这也是在分布式系统中要使用消息代理的重要缘由,以此咱们能够使用通讯来解耦业务逻辑。在这个例子中, 能够进一步作一些测试, 好比,不运行消费者,先运行生产者, 此时能够看到在RabbitMQServer管理页面的Queues选项卡下多了一些待处理的消息, 这时咱们再启动消费者, 它就会处理这些消息, 因此经过生产消费模式的异步操做, 系统间调用就没有同步调用须要那么高的实时性要求, 同时也更容易控制处理的吞吐量以保证系统的正常运行等。
在上一节中, 咱们已经介绍了关于消息代理、AMQP以及RabbitMQ的基础知识和使用方法,而且在spring boot中应用。在下面的内容中, 咱们开始具体介绍SpringCloud Bus的配置。
直接使用前面章节的eureka-server
做为服务发现中心。并启动服务发现中心。
建立消息生产者微服务项目microservice-msg-sender
,并添加相关依赖。依赖信息以下:
<!--eureka 客户端依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!--spring bus rabbitmq依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency> <!--为了使用@Slf4j注解--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
@EnableDiscoveryClient
注解配置application.properties
,固然你也能够用application.yml
,内容以下
```properties
#应用端口号
server.port=3000
#服务名称
spring.application.name=microservice-msg-sender
#注册到服务注册中心
eureka.client.service-url.defaultZone=http://localhost:1000/eureka/
###############################
#### RabbitMQ服务器相关配置#####
###############################
#rabbitmq server地址,默认localhost
spring.rabbitmq.host=localhost
#rabbitmq server端口,默认5672
spring.rabbitmq.port=5672
#rabbitmq server用户名,默认guest
spring.rabbitmq.username=dengcl
#rabbitmq server密码,默认guest
spring.rabbitmq.password=123456
###绑定spring cloud bus的rabbitmq消息通道的exchange名称
spring.cloud.stream.bindings.rabbitmq_channel_output.destination=exchangeName
```
建立spring cloud bus的通道配置接口RabbitSendChannel
,内容以下:
public interface RabbitSendChannel { String rabbitmqChannelName = "rabbitmq_channel_output"; @Output(rabbitmqChannelName) MessageChannel output(); }
特别说明:
接口中的rabbitmqChannelName
的值和application.properties
中定义的spring.cloud.stream.bindings.rabbitmq_channel_output.destination=exchangeName
中rabbitmq_channel_output内容必须一致。
建立消息生产业务类RabbitMQSendService
并经过@EnableBinding
注解绑定通道接口。
```JAVA
@Slf4j
@EnableBinding(RabbitSendChannel.class)
public class RabbitMQSendService {
@Autowired private RabbitSendChannel rabbitSendChannel; //发送消息的业务 public boolean sendMsg(String msg){ log.info("准备发送消息到rabbitmq server,消息内容是:{}",msg); return rabbitSendChannel.output().send(MessageBuilder.withPayload(msg).build()); }
}
```建立一个定时任务类SendMsgTask
,定时发送消息,固然在实际应用中具体发送消息的事件须要根据业务来定义。
```JAVA
@Slf4j
@Component
public class SendMsgTask {
private final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Autowired private RabbitMQSendService sendService; //每隔5秒发送一条消息 @Scheduled(fixedDelay = 5000L) public void process(){ String msg = String.format("我是消息的生产者,我在[%s]生成了一条信息。",sdf.format(new Date())); log.info("SEND :{}",msg); sendService.sendMsg(msg); }
}
``> **注意** > 应用主类中要增长注解
@EnableScheduling`来开启定时任务支持。
启动应用主类,能够在控制台上看到如下信息:
2018-03-29 15:41:38.585 INFO 16868 --- [ask-scheduler-4] c.d.m.mq.RabbitMQSendService : 准备发送消息到rabbitmq server,消息内容是:我是消息的生产者,我在[2018-03-29 15:41:38]生成了一条信息。 2018-03-29 15:41:43.587 INFO 16868 --- [ask-scheduler-4] c.d.m.task.SendMsgTask : SEND :我是消息的生产者,我在[2018-03-29 15:41:43]生成了一条信息。 2018-03-29 15:41:43.587 INFO 16868 --- [ask-scheduler-4] c.d.m.mq.RabbitMQSendService : 准备发送消息到rabbitmq server,消息内容是:我是消息的生产者,我在[2018-03-29 15:41:43]生成了一条信息。
此时在rabbitmq的控制面板能够看到:
[image_1c9ocl9odqu42qtrcs1h17lqn4j.png-119.5kB][31]
建立消息消费者微服务项目microservice-msg-receiver
,并添加相关依赖。依赖信息以下:
<!--eureka 客户端依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!--spring bus rabbitmq依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency> <!--为了使用@Slf4j注解--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
主类添加@EnableDiscoveryClient
注解
配置application.properties
,固然你也能够用application.yml
,内容以下
```properties
server.port=4000
#服务名称
spring.application.name=microservice-msg-reveciver
#注册到服务注册中心
eureka.client.service-url.defaultZone=http://localhost:1000/eureka/
###############################
#### RabbitMQ服务器相关配置#####
###############################
#rabbitmq server地址,默认localhost
spring.rabbitmq.host=localhost
#rabbitmq server端口,默认5672
spring.rabbitmq.port=5672
#rabbitmq server用户名,默认guest
spring.rabbitmq.username=dengcl
#rabbitmq server密码,默认guest
spring.rabbitmq.password=123456
###绑定spring cloud bus的rabbitmq消息通道的exchange名称
spring.cloud.stream.bindings.rabbitmq_channel_input.destination=exchangeName
```
建立spring cloud bus的通道配置接口RabbitReceiverChannel
,内容以下:
public interface RabbitMQReciveChannel { String rabbitmqChannelName = "rabbitmq_channel_input"; @Input(rabbitmqChannelName) SubscribableChannel input(); }
特别说明:
- 接口中的
rabbitmqChannelName
的值和application.properties
中定义的spring.cloud.stream.bindings.rabbitmq_channel_input.destination=exchangeName
中rabbitmq_channel_input内容必须一致。- 与消息生产者的
rabbitmqChannelName
的值不能同样。
建立消息生产业务类RabbitMQSendService
并经过@EnableBinding
注解绑定通道接口。而且提供一个方法,经过@StreamListener
注解监听指定通道的新的消息。
@Slf4j @EnableBinding(RabbitMQReciveChannel.class) public class RabbitMQReciveService { @Autowired private RabbitMQReciveChannel reciveChannel; @StreamListener(RabbitMQReciveChannel.rabbitmqChannelName) public void reciveMsg(Message<String> msg){ log.info("RECEIVER:{}",msg.getPayload()); } }
#### 测试结果
在这个示例中,仅仅是展现了spring cloud bus如何整合rabbitmq,更多关于rabbitmq的使用请参考rabbitmq的官方网站。
Spring Cloud Config是Spring Cloud团队建立的一个全新项目,用来为分布式系统中的基础设施和微服务应用提供集中化的外部配置支持,它分为服务端与客户端两个部分。其中服务端也称为分布式配置中心,它是一个独立的微服务应用,用来链接配置仓库并为客户端提供获取配置信息、加密/解密信息等访问接口;而客户端则是微服务架构中的各个微服务应用或基础设施,它们经过指定的配置中心来管理应用资源与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息。Spring Cloud Config实现了对服务端和客户端中环境变量和属性配置的抽象映射,因此它除了适用于Spring构建的应用程序以外,也能够在任何其余语言运行的应用程序中使用。因为Spring Cloud Config实现的配置中心默认采用Git来存储配置信息,因此使用Spring Cloud Config构建的配置服务器,自然就支持对微服务应用配置信息的版本管理,而且能够经过Git客户端工具来方便的管理和访问配置内容。固然它也提供了对其余存储方式的支持,好比:SVN仓库、本地化文件系统。
在本文中,咱们将学习如何构建一个基于Git存储的分布式配置中心,并对客户端进行改造,并让其可以从配置中心获取配置信息并绑定到代码中的整个过程
准备一个git仓库,能够在码云或Github上建立均可以。仓库示例:https://gitee.com/dengcl/spring-cloud-config-server/
经过Spring Cloud Config
构建一个分布式配置中心很是简单, 只须要如下三步:
Spring Boot
工程, 命名为config-server
, 并在pom.xml
中引入下面的依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>
Spring Boot
的程序主类, 并添加@EnableConfigServer
注解, 开启SpringCloud Config
的服务端功能。@EnableConfigServer @SpringBootApplication public class ConfigServerApplication { public static void main(String[] args) { SpringApplication.run(ConfigServerApplication.class, args); } }
在`application.properties中添加配置服务的基本信息以及Git 仓库的相关信息, 以下所示:
spring.cloud.config.server.git.uri=https://gitee.com/dengcl/spring-cloud-config-server spring.cloud.config.server.git.username=dengcl spring.cloud.config.server.git.password=dcl745120 spring.application.name=config-server server.port=7000
其中Git 的配置信息分别表示以下内容。
spring.cloud.config.server.git.uri
: 配置Git 仓库位置。spring.cloud.config.server.git.searchPaths
: 配置仓库路径下的相对搜索位置, 能够配置多个。spring.cloud.config.server.git.username
: 访问Git 仓库的用户名。spring.cloud.config.server.git.password
: 访问Git 仓库的用户密码。
到这里, 使用一个经过Spring Cloud Config
实现, 并使用Git
管理配置内容的分布式配置中心就完成了。咱们能够将该应用先启动起来, 确保没有错误产生, 而后进入下面的学习内容。
为了验证上面完成的分布式配置中心config-server
, 根据Git 配置信息中指定的仓库位置, 在https://gitee.com/dengcl/spring-cloud-config-server下建立了/config_repo
目录做为配置仓库, 并根据不一样环境新建下面4个配置文件:
config-client-dev.properties
config-client-test.properties
config-client-prod.properties
在这4个配置文件中均设置了一个username属性, 并为每一个配置文件分别设置了不一样的值, 以下所示:
为了测试版本控制,在该Git仓库的master 分支中,咱们为username属性加入1.0 的后缀, 同时建立一个config-label-test
分支, 并将各配置文件中的值用2.0 做为后缀。
完成了这些准备工做以后, 咱们就能够经过浏览器、POSTMAN或CURL等工具直接来访问咱们的配置内容了。访问配置信息的URL与配置文件的映射关系以下所示:
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
上面的url会映射{application}-{profile}.properties
对应的配置文件,其中{label}
对应Git上不一样的分支,默认为master。咱们能够尝试构造不一样的url来访问不一样的配置内容,好比,要访问master分支,config-client应用的dev环境,就能够访问这个url:http://localhost:7000/config-client/dev/master
,并得到以下返回:
{ "name": "config-client", "profiles": [ "dev" ], "label": "master", "version": "804509ea362b52de23ca06f627087301002aa6d0", "state": null, "propertySources": [ { "name": "https://gitee.com/dengcl/spring-cloud-config-server/config_repo/config-client-dev.properties", "source": { "username": "dengcl_dev_1.0" } }, { "name": "https://gitee.com/dengcl/spring-cloud-config-server/config_repo/config-client.properties", "source": { "username": "dengcl_1.0" } } ] }
咱们能够看到该Json中返回了应用名:config-client
,环境名:dev
,分支名:master
,以及default
环境和dev
环境的配置内容。
同时, 咱们能够看到config-server
的控制台中还输出了下面的内容,配置服务器在从Git 中获取配置信息后, 会存储一份在config-server
的文件系统中, 实质上config-server
是经过git clone
命令将配置内容复制了一份在本地存储, 而后读取这些内容并返回给微服务应用进行加载。
2018-03-30 11:05:00.663 INFO 4884 --- [nio-7000-exec-5] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@7e88bbb: startup date [Fri Mar 30 11:05:00 CST 2018]; root of context hierarchy 2018-03-30 11:05:00.677 INFO 4884 --- [nio-7000-exec-5] o.s.c.c.s.e.NativeEnvironmentRepository : Adding property source: file:/C:/Users/dengcl/AppData/Local/Temp/config-repo-8571982357257432495/config_repo/config-client-dev.properties 2018-03-30 11:05:00.677 INFO 4884 --- [nio-7000-exec-5] o.s.c.c.s.e.NativeEnvironmentRepository : Adding property source: file:/C:/Users/dengcl/AppData/Local/Temp/config-repo-8571982357257432495/config_repo/config-client.properties 2018-03-30 11:05:00.677 INFO 4884 --- [nio-7000-exec-5] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@7e88bbb: startup date [Fri Mar 30 11:05:00 CST 2018]; root of context hierarchy
config-server
经过Git 在本地仓库暂存,能够有效防止当Git 仓库出现故障而引发没法加载配置信息的状况。咱们能够经过断开网络, 再次发起http://localhost:7000/config-client/test/master请求,能够看到, config-server
提示没法从远程获取该分支内容的报错信息, 可是它依然会为该请求返回配置内容, 这些内容源于以前访问时存于config-server
本地文件系统中的配置内容。
在完成了上述验证以后,肯定配置服务中心已经正常运做,下面咱们尝试如何在微服务应用中获取上述的配置信息。
config-client
,并在pom.xml
中引入下述依赖:<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency>
bootstrap.yml
配置,来指定获取配置文件的config-server-git
位置,例如:spring: application: name: config-client cloud: config: uri: http://localhost:7000/ profile: default label: master server: port: 8000
上述配置参数与Git中存储的配置文件中各个部分的对应关系以下:
spring.application.name
:对应配置文件规则中的{application}
部分spring.cloud.config.profile
:对应配置文件规则中的{profile}
部分spring.cloud.config.label
:对应配置文件规则中的{label}
部分spring.cloud.config.uri
:配置中心config-server
的地址这里须要格外注意:上面这些属性必须配置在bootstrap.yml
中,固然也能够是bootstrap.properties
,这样config-server
中的配置信息才能被正确加载。
在完成了上面的代码编写以后,将config-server
、config-client
都启动起来,而后访问http://localhost:2001/info ,咱们能够看到该端点将会返回从git仓库中获取的配置信息:
@RunWith(SpringRunner.class) @SpringBootTest public class ConfigClientApplicationTests { @Value("${username}") private String username; @Test public void test(){ System.err.println(username); } }
一般在生产环境,Config Server
与服务注册中心同样,咱们也须要将其扩展为高可用的集群。在以前实现的config-server
基础上来实现高可用很是简单,不须要咱们为这些服务端作任何额外的配置,只须要遵照一个配置规则:将全部的Config Server
都指向同一个Git仓库,这样全部的配置内容就经过统一的共享文件系统来维护,而客户端在指定Config Server
位置时,只要配置Config Server
外的均衡负载便可,就像以下图所示的结构:
虽然经过服务端负载均衡已经可以实现,可是做为架构内的配置管理,自己其实也是能够看做架构中的一个微服务。因此,另一种方式更为简单的方法就是把config-server
也注册为服务,这样全部客户端就能以服务的方式进行访问。经过这种方法,只须要启动多个指向同一Git仓库位置的config-server
就能实现高可用了。
首先启动eureka实现的服务注册中心应用server-center
。
接下来实现配置管理服务端和客户端,配置过程很是简单,具体以下:
pom.xml
的dependencies
节点中引入以下依赖,相比以前的config-server
就加入了spring-cloud-starter-netflix-eureka-client
,用来注册服务。<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
application.properties
中配置参数eureka.client.serviceUrl.defaultZone
以指定服务注册中心的位置,详细内容以下:# 配置服务注册中心 eureka.client.serviceUrl.defaultZone=http://peer1:1111/eureka/
@EnableEurekaClient
注解,用来将config-server
注册到上面配置的服务注册中心上去。@EnableEurekaClient @EnableConfigServer @SpringBootApplication public class ConfigServerApplication { public static void main(String[] args) { SpringApplication.run(ConfigServerApplication.class, args); } }
http://localhost:1111/
,能够在Eureka Server的信息面板中看到config-server
已经被注册了。config-server
同样,在pom.xml
的dependencies
节点中新增spring-cloud-starter-netflix-eureka-client
依赖,用来注册服务:<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
bootstrap.properties
中,按以下配置:spring.application.name=config-client server.port=8000 eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/ spring.cloud.config.discovery.enabled=true spring.cloud.config.discovery.serviceId=config-server spring.cloud.config.profile=dev
其中,经过eureka.client.serviceUrl.defaultZone
参数指定服务注册中心,用于服务的注册与发现,再将spring.cloud.config.discovery.enabled
参数设置为true,开启经过服务来访问Config Server的功能,最后利用spring.cloud.config.discovery.serviceId
参数来指定Config Server注册的服务名。这里的spring.application.name
和spring.cloud.config.profile
如以前经过URI的方式访问时候同样,用来定位Git中的资源。
@EnableEurekaClient
注解,用来发现config-server服务,利用其来加载应用配置@EnableEurekaClient @SpringBootApplication public class ConfigClientApplication { public static void main(String[] args) { SpringApplication.run(ConfigClientApplication.class, args); } }
TestController
,其代码以下:@RestController public class TestController { @Value("${username}") private String userName; @GetMapping("/show") public String show(){ return this.userName; } }
完成了上述配置以后,咱们启动该客户端应用。若启动成功,访问http://localhost:1111/,能够在Eureka Server的信息面板中看到该应用已经被注册成功了。
访问客户端应用提供的服务:http://localhost:8000/show
,此时,咱们会返回在Git仓库中config-client.properties
文件配置的username
属性内容dengcl_1.0
。
有时候,咱们须要对配置内容作一些实时更新的场景,那么Spring Cloud Config是否能够实现呢?答案显然是能够的。下面,咱们看看如何进行改造来实现配置内容的实时更新。
在改造程序以前,咱们先将config-server
和config-client
都启动起来,并访问客户端提供的REST APIhttp://localhost:8000/show
来获取配置信息,能够得到返回内容为:dengcl_dev_1.0
。接着,咱们能够尝试使用Git工具修改当前配置的内容,好比,将config-repo/config-client-dev.properties
中的username的值从username=dengcl_dev_1.0
修改成username=dengcl_dev_1.0
,再访问http://localhost:8000/show
,能够看到其返回内容仍是username=dengcl_dev_1.0
。
下面,咱们将在config-client端增长一些内容和操做以实现配置的刷新:
pom.xml
中新增spring-boot-starter-actuator
监控模块,其中包含了/actuator/refresh
刷新API。<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
bootstrap.properties
中开启/actuator/refresh
端点。management.endpoints.web.exposure.include=refresh
/actuator/refresh
时须要刷新的bean添加注解@RefreshScope
,在本例中须要被刷新的是TestController
,由于咱们须要在这里面注入配置中心的属性。@RestController @RefreshScope public class TestController { @Value("${username}") private String userName; @GetMapping("/show") public String show(){ return this.userName; } }
http://localhost:8000/show
,能够看到当前的配置值config-repo/config-client-dev.properties
文件中username
的值http://localhost:8000/show
,能够看到配置值没有改变http://localhost:8000/actuator/refresh
,咱们能够看到返回内容以下,表明username
参数的配置内容被更新了[ "config.client.version", "username" ]
除此以外,咱们还能够经过git仓库的
web hook
来功能进行关联,当有Git提交变化时,就给对应的配置主机发送/actuator/refresh
请求来实现配置信息的实时更新。可是,这种方式不只对网络拓扑有需求,同时当咱们的系统发展壮大以后,维护这样的刷新清单也将成为一个很是大的负担,并且很容易犯错,那么有什么办法能够解决这个复杂度呢?其实能够经过Spring Cloud Bus来实现以消息总线的方式进行通知配置信息的变化,完成集群上的自动化更新。
在上一节的示例中,虽然咱们已经可以经过/actuator/refresh
接口和Git仓库的Web Hook来实现Git仓库中的内容修改触发应用程序的属性更新。可是, 若全部触发操做均须要咱们手工去维护Web Hook中的应用配置的话, 随着系统的不断扩展, 会变得愈来愈难以维护, 而消息代理中间件是解决该问题最为合适的方案。是否还记得咱们在介绍消息代理中的特色时提到过这样一个功能: 消息代理中间件能够将消息路由到一个或多个目的地。利用这个功能, 咱们就能完美地解决该问题, 下面来讲说SpringCloud Bus中的具体实现方案。
下面咱们来具体动手尝试整个配置过程。
准备工做: 这里咱们不建立新的应用, 但须要用到前面已经实现的关于SpringCloudConfig
的几个工程。
config-repo
: 定义在Git仓库中的一个目录,其中存储了应用名为中config-client
的多环境配置文件, 配置文件中有一个username
参数。eureka-server
: 基于eureka实现的服务注册中心。config-server
: 配置中心服务器,配置了Git仓库, 并注册到了Eureka的服务端。config-client
: 经过Eureka发现ConfigServer的客户端, 应用名为config-client
, 用来访问配置服务器以获取配置信息。该应用中提供了一个/show
接口, 它会获取config-repo/config-client-dev.properties
中的username
属性并返回。扩展config-client
应用
pom.xml
,添加spring-cloud-starter-bus-amqp
模块依赖,注意spring-boot-starter-actuator
也是必须的,用来添加提供刷新节点。<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
```
在配置文件中增长关千RabbitMQ的链接和用户信息,同时打开bus-refresh
端点。
#配置rabbitmq链接信息 spring.rabbitmq.host=localhost spring.rabbitmq.port=5672 spring.rabbitmq.username=dengcl spring.rabbitmq.password=123456 #打开/bus-refresh节点 management.endpoints.web.exposure.include=bus-refresh
启动config-server
, 再启动两个config-client
分别在不一样的端口上, 好比8000、8001)。咱们能够在config-client
中的控制台中看到以下内容, 在启动时, 客户端程序多了一个/actuator/bus-refresh
请求。
2018-03-30 16:41:16.895 INFO 15376 --- [ main] s.b.a.e.w.s.WebMvcEndpointHandlerMapping : Mapped "{[/actuator/bus-refresh],methods=[POST]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.web .servlet.AbstractWebMvcEndpointHandlerMapping$OperationHandler.handle(javax.servlet.http.HttpServletRequest,java.util.Map<java.lang.String, java.lang.String>)
先访问两个config-client
的/show
请求, 会返回当前config-repo/config-client-dev.properties
中的username
属性。
接着, 修改config-repo/config-client-dev.properties
中的username
属性值,
并发送POST
请求到config-client
中的一个/actuator/bus-refresh
。
最后, 再分别访问启动的两个config-client
的/show
请求, 此时这两个请求都会返回最新的username
属性。
到这里, 咱们已经可以经过SpringCloud Bus来实时更新总线上的属性配置了。而且能够有效的结合GIT仓库的web hook实现配置信息的全自动更新。
经过以前学习,实际上咱们已经可以经过使用它们搭建起一个基础的微服务架构系统来实现咱们的业务需求了。可是,随着业务的发展,咱们的系统规模也会变得愈来愈大,各微服务间的调用关系也变得愈来愈错综复杂。一般一个由客户端发起的请求在后端系统中会通过多个不一样的微服务调用来协同产生最后的请求结果,在复杂的微服务架构系统中,几乎每个前端请求都会造成一条复杂的分布式服务调用链路,在每条链路中任何一个依赖服务出现延迟太高或错误的时候都有可能引发请求最后的失败。这时候对于每一个请求全链路调用的跟踪就变得愈来愈重要,经过实现对请求调用的跟踪能够帮助咱们快速的发现错误根源以及监控分析每条请求链路上的性能瓶颈等好处。
针对上面所述的分布式服务跟踪问题,Spring Cloud Sleuth提供了一套完整的解决方案。在本章中,咱们将详细介绍如何使用Spring Cloud Sleuth来为咱们的微服务架构增长分布式服务跟踪的能力。