在微服务的概念成型以前,绝大部分基于WEB的应用都是使用单体的风格来进行构建的。在单体架构中,应用程序做为单个可部署的软件制品交付,
全部的UI(用户接口)、业务、数据库访问逻辑都被打包在一个应用程序中而且部署在一个应用服务器上。
随着单体应用的规模和复杂度的增加,在该应用上进行开发的团队的沟通与合做成本没有减小。
当各个团队须要修改代码时,整个应用程序都要从新构建、从新测试和部署。
微服务的概念最初是在2014年先后蔓延到软件开发社区当中[马丁福勒]
微服务是一个小的、松耦合的分布式服务。微服务容许将一个大型应用分解为具备严格职责定义的便于管理的组件。
信奉的概念:分解分离的应用程序的功能,使他们彻底彼此独立。
1.应用程序逻辑分解为具备明肯定义了职责范围的细粒度组件、这些组件互相协调提供解决方案 2.每一个组件都有一个小的职责领域、而且彻底独立部署。可跨多个应用程序复用。
3.微服务通讯基于一些基本的原则、采用HTTP和JSON轻量级通讯协议,在消费者和服务者间进行数据交换。
4.构建在微服务上的应用程序可以使用多种编程语言和技术进行构建
5.小、独立、分布书、松耦合
1.其软件的复杂度上升 2.客户期待更快的交付 3.性能和可伸缩性
基础设施即服务(Infrastructure as a Service ) IaaS
平台其服务(Platform as a Service ) PaaS
软件即服务 (Software as a Service ) SasS
容器即服务 (Container as a CaaS ) CaaS
参考 html
1.物理服务器 2.虚拟机镜像 3.虚拟容器
1.大小适中 2.位置透明 3.有弹性 4.可重复 5.可伸缩
服务粒度:java
将业务划分为微服务
通讯协议:git
xml
json
thrift
接口设计:github
如何设计实际的接口服务,便于开发人员进行服务调用
服务的配置管理:web
如何管理微服务的配置,以便在不一样的云环境之间移动时,没必要要跟改核心应用程序代码或配置
服务之间的事件处理:redis
如何使用事件解藕微服务,以便最小化服务之间的硬编码依赖关系 ,并提升应用程序的弹性。
服务发现算法
如何使微服务变得能够被发现,以便将客户端应用程序在不须要将服务位置硬编码到应用程序中
服务路由spring
如何为全部的服务提供单个入口点,以便将安全策略和路由规则统一应用于微服务应用程序中的多个服务和服务实例
客户端负载均衡sql
如何在服务器客户端上缓存服务实例的位置,以便对微服务的多个实例调用负载均衡到该微服务的全部健康实例
断路器模式docker
如何阻止客户继续调用出现故障的或遭遇性能问题的服务
后备模式
当服务调用失败,如何提供“插件”机制,容许服务的客户端尝试经过调用微服务以外的其余方法来执行工做
舱壁模式
微服务应用程序采用多个分布式资源来执行工做
验证
如何肯定调用服务的客户端就是他们声称的那个主体
受权
如何肯定调用微服务的客户端是否容许执行他们正在进行的操做
凭证管理和传播
如何避免客户端每次都要提供凭据信息才能访问事物中涉及的服务调用
日志关联
一个用户会调用多个服务,如何将这些服务所产生的日志关联到一块儿
日志聚合
将微服务(及其各个实例)生成的全部日志合并到一个可查询的数据库中以及使用关联ID来协助搜索聚合日志
微服务跟踪
将全部服务中的可视化客户端事物流程,事物所涉及的服务的性能特性
构建和部署管道
如何构建一个可重复的构建和部署过程,只需一键便可构建和部署到组织中的任何环境
基础设施即代码
如何将服务的基础设施做为可在源代码下执行和管理的代码去对待
不可变服务
一旦建立了微服务镜像,如何确保它在部署以后永远不会更改
凤凰服务器
服务器运行时间越长就愈加生配置漂移。如何确保微服务的服务器按期被拆卸,并从新建立一个不可变的镜像
spring cloud config 经过集中式服务来处理应用程序配置数据的管理,所以应用程序配置数据(特别是环境特定配置数据)与部署的微服务彻底分离。这确保了不管启动多少个微服务实例,这些微服务实例始终都有相同的配置。spring cloud config 拥有本身的属性管理存储库,也能够与如下开源项目集成 Consul------是一个开源的服务发现工具,容许服务实例向该服务注册本身。服务客户端能够向Consul 咨询服务实例的位置。consul 还包括能够被spring cloud config使用的基于键值存储的数据库,可以用来存储应用程序的配置数据。 Eureka-----=是一个开源的Netflix项目,像Consul同样,提供相似的服务发现功能。Eureka一样有一个能够被Spring cloud config 使用的键值数据库。
经过spring cloud 服务发现,开发人员能够从客户端消费的服务中抽象出部署服务器的物理位置(IP或者服务器名称)服务消费者经过逻辑名称而不是物理位置来调用服务器的业务逻辑。
spring cloud 服务发现也处理服务实例的注册和注销(在服务实例启动和关闭时)Spring cloud 服务法系可使用Consul 和 Eureka做为服务发现引擎
spirng cloud 与 Netflix 的开源项目进行大量的整合,对于微服务客户端弹性模式,spring cloud 封装了Netflix Hystrix 库和Netflix Ribbon 项目,开发人员能够轻松的在微服务中使用他们。 Netflix Hystrix库 ----> 可快速实现服务客户端弹性模式,如断路器模式,舱壁模式 Netflix Ribbo ----> 项目简化了与诸如Eureka这样的服务发现代理的集成,但他也为服务消费者提供了客户端对服务器的调用的负载均衡。即便在服务代理暂时不可用的状况下,客户端也能够进行服务调用。
spring cloud 使用 Netflix Zuul 项目做为微服务应用程序提供服务路由功能。Zuul是代理服务请求的服务网关,确保在调用目标以前,对微服务的全部调用都通过一个前门,经过集中的服务调用,开发人员能够强制执行标准服务策略,如安全受权验证,内容过滤和路由规则
spring cloud stream 是一种可以让开发人员轻松的将轻量级消息处理集成到微服务中的支持技术。
开发人员能够构建智能的微服务,它可使用在应用程序中出现的异步事件,此外使用spirng cloud steam 能够快速将微服务与消息代理进行整合,如RabbitMQ、Kafka
spring cloud sleuth 容许将惟一跟踪标识符集成到应用程序所使用的HTTP调用和消息通道(RabbitMQ、Kafka)之中。这些跟踪ID可以让开发人员在事物流经应用程序中的不一样服务时跟踪事务。有了spring cloud sleuth,这些跟踪ID将子自动添加到微服务生成的任何日志记录当中
spring cloud sleuth 与日志聚合技术工具(如:Parpertrail)和跟踪工具(如:Zipkin)结合时可以展示出真正的威力。
Papertail 是一个基于云的日志记录平台,用于将日志从不一样的微服务实时聚合到一个可查询的数据库中。
Zipkin能够获取spring cloud sleuth 生成的数据,并容许开发人员可视化单个事物涉及的服务调用流程
spirng cloud security 是一个验证和受权框架,能够控制哪些人能够服务服务,以及他们能够用服务作什么。
spring cloud security 是基于令牌的,容许服务经过验证服务器发出的令牌彼此进行通讯。接收调用的每一个服务能够检查HTTP调用中提供的令牌,以确认用户的身份以及用户对该服务的访问权限。
此外、spring cloud security 支持JWT , JWT 框架标准化了建立OAuth2令牌的格式,并为建立的令牌进行数字签名提供了标准。
package com.mikey.microservicedemo; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @SpringBootApplication @RestController @RequestMapping("hello") @EnableCircuitBreaker//TODO:使服务可以使用 Hystrix 和 Ribbon 库 @EnableEurekaClient //TODO: 告诉服务,他因该使用Eureka服务发现来代理注册自身,而且服务调用是使用服务发现来“查找”远程服务的位置 public class MicroservicedemoApplication { public static void main(String[] args) { SpringApplication.run(MicroservicedemoApplication.class, args); } @HystrixCommand(threadPoolKey = "helloThreadPool") public String helloRemoteServiceCall(String fistName,String lastName) {//TODO:包装器使用Hystrix断路器调用helloRemoteServiceCall方法 RestTemplate restTemplate = new RestTemplate(); ResponseEntity<String> restExchange = restTemplate.exchange( "http://logical-service-id/name/[ca]{firstName}/{lastName}", //TODO:使用一个装饰好的RestTemplate类来获取一个“逻辑”服务ID,Eureka在幕后查找服务的物理位置 HttpMethod.GET, null, String.class, fistName,lastName); return restExchange.getBody(); } }
1.微服务是很是小的功能部件,负责一个特定的范围领域
2.微服务并无行业标准,与其余早期的web服务协议不一样,微服务采用原则导向的方法,并与REST和JSON的概念相一致!
3.编写微型服务很容易,但彻底能够将其用于生产环境则须要额外的深谋远虑,微服务开发模式:核心开发模式,路由模式,客户端弹性模式,安全模式,日志记录和跟踪模式以及构建和部署模式。
4.虽然微服务和语言无关,两个Spring框架,spring boot 和 spring cloud 有助于构建微服务。
5.spring boot 用于简化基于REST的JSON微服务的构建,其目标是让用户只须要少许的注解,就能快速构建微服务。
6.spirng cloud 是Netflix 和 HashiCorp等公司开源技术的集合,他们已经用spring 注解进行了包装,从而显著简化了这些服务 的设置和配置。
1.紧耦合 2.有漏洞 3.单体的
1.有约束的 2.松耦合的 3.抽象的 4.独立的
1.拥有庞大而多样化的用户群体 2.极高的运行时间要求 3.不均匀的容量需求
1.开始的时候可让微服务涉及的范围跟普遍一些,而后将其重构到更小的服务 2.重点关注服务如何相互交互 3.随着对问题域的理解不断增加,服务的职责将随着时间的推移而改变
1.服务承担过多的职责 2.该服务正在跨大量表来管理数据 3.测试用例过多 4.问题域的一部分像兔子同样繁殖 5.微服务彼此间严重相互依赖 6.微服务成为简单的CRUD
1.拥抱REST的理念 2.使用URI来传达意图 3.请求和响应使用JSON 4.使用HTTP状态码来传达结果
1.构建分布式系统的复杂性 2.虚拟服务器/容器散乱 3.应用程序的类型 4.数据事务和一致性
1.要想经过微服务得到成功,须要综合架构师、软件开发人员、DevOps的视角 2.微服务是一种强大的架构范型、他有缺点和优势。并不是全部的应用程序都应该所微服务应用程序。 3.从架构师的角度来看,微服务是小型的、独立的和分布式的。微服务应具备狭窄的边界,并管理一小组数据。 4.从开发人员的角度来看,微服务一般使用REST风格的设计构建,JSON做为服务发送和接收数据的净荷 5.Spring Boot是构建微服务的理想框架,由于它容许开发人员使用简单的注解便可构建基于REST的JSON服务 6.从DevOps的角度来看。微服务如何打包、部署和监控相当重要 7.开箱即用。Spring boot容许用户单个可执行JAR文件交付服务。JAR文件中的嵌入式Tomcat服务器承载该服务 8.Spring boot框架附带的Spring Actuator会公开有关服务运行健康情况信息以及有关服务运行时的信息
原则: 1.分离------>咱们但愿将服务配置信息与服务的实际物理部署彻底分开。应用程序配置不该该与服务实例一块儿部署。相反,配置信息应该做为环境变量传递给正在启动的服务,或者在服务启动时从集中式存储库中读取。 2.抽象------>将访问配置数据的功能抽象到一个服务接口中。应用程序使用基于REST的JSON服务来检索配置数据,而不是编写直接访问服务存储库的代码(也就是从文件或者JDBC从数据库获取。 3.集中------>由于基于云的应用程序可能会有数百个服务,因此最小化用于保存配置信息的不一样存储库的数量是相当重要的。将应用程序配置集中在尽量少的存储库中。 4.稳定------>由于应用程序的配置信息与部署的访问彻底隔离并集中存放,因此无论采用何种方案实现。相当重要的一点就是保证其高可用和英语冗余 关键点:将配置信息与实际代码分开以后,开发人员将建立一个须要进行管理和版本控制的外部依赖项。
1.当一个微服务实例出现时,它将调用一个服务点来读取其所在环境的特定配置信息。配置管理的链接信息(链接凭证,服务端点)将在微服务启动时被传递给微服务
2.实际的配置信息驻留在存储库中,基于配置存储库的实现,能够选择使用不一样的实现来保存配置数据。配置存储库的实现选择能够包括源代码控制下的文件,关系型数据库或键值数据库
3.应用程序配置数据的实际管理与应用程序的部署方式无关。配置管理的更改一般经过构建和部署管道来处理,其中配置的更改能够经过版本信息进行标记,并经过不一样的环境进行部署
4.进行配置管理更改时,必须通知使用该应用程序配置数据的服务,并刷新应用程序数据的副本。
Project Name | Descripton | Characteristics |
Etcd | Open source project written in Go. Used |
Very fast and scalable |
Eureka | Written by Netflix. Extremely battle-tested. |
Distribute key-value store. |
Consul | Written by Hashicorp. Similar to Etcd and |
Fast |
ZooKeeper | An Apache project that offers distributed |
Oldest, most battle-tested of the solutions |
Spring Cloud configuration server | An open source project that offers a |
Non-distributed key/value store |
须要完成的内容: 1.建立一个Spring Cloud配置服务器,并演示两种不一样的机制来提供应用程序配置数据,一种使用文件系统,另外一种使用Git存储库。 2.继续构建许可证服务以从数据库中检索数据 3.将Spring Cloud 配置服务挂钩(hook)到许可证服务,以提供应用程序配置数据。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.thoughtmechanix</groupId> <artifactId>licensing-service</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>Eagle Eye Licensing Service</name> <description>Licensing Service</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.4.RELEASE</version> </parent> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Camden.SR5</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> <dependency> <groupId>postgresql</groupId> <artifactId>postgresql</artifactId> <version>9.1-901.jdbc4</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-rsa</artifactId> </dependency> </dependencies> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <start-class>com.thoughtmechanix.licenses.Application</start-class> <docker.image.name>johncarnell/tmx-licensing-service</docker.image.name> <docker.image.tag>chapter3</docker.image.tag> </properties> <build> <plugins> <!-- We use the Resources plugin to filer Dockerfile and run.sh, it inserts actual JAR filename --> <!-- The final Dockerfile will be created in target/dockerfile/Dockerfile --> <plugin> <artifactId>maven-resources-plugin</artifactId> <executions> <execution> <id>copy-resources</id> <!-- here the phase you need --> <phase>validate</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <outputDirectory>${basedir}/target/dockerfile</outputDirectory> <resources> <resource> <directory>src/main/docker</directory> <filtering>true</filtering> </resource> </resources> </configuration> </execution> </executions> </plugin> <plugin> <groupId>com.spotify</groupId> <artifactId>docker-maven-plugin</artifactId> <version>0.4.10</version> <configuration> <imageName>${docker.image.name}:${docker.image.tag}</imageName> <dockerDirectory>${basedir}/target/dockerfile</dockerDirectory> <resources> <resource> <targetPath>/</targetPath> <directory>${project.build.directory}</directory> <include>${project.build.finalName}.jar</include> </resource> </resources> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
编写配置文件:
resources
--licensingservice
example.property: "I AM IN THE DEFAULT"
spring.jpa.database: "POSTGRESQL"
spring.datasource.platform: "postgres"
spring.jpa.show-sql: "true"
spring.database.driverClassName: "org.postgresql.Driver"
spring.datasource.url: "jdbc:postgresql://database:5432/eagle_eye_local"
spring.datasource.username: "postgres"
spring.datasource.password: "{cipher}4788dfe1ccbe6485934aec2ffeddb06163ea3d616df5fd75be96aadd4df1da91"
spring.datasource.testWhileIdle: "true"
spring.datasource.validationQuery: "SELECT 1"
spring.jpa.properties.hibernate.dialect: "org.hibernate.dialect.PostgreSQLDialect"
redis.server: "redis"
redis.port: "6379"
signing.key: "345345fsdfsf5345"
--organizationservice
example.organization.property: "I AM THE DEFAULT ORGANIZATION SERVICE"
值的注意的是:
在构建配置服务时,它将成为在环境中运行的另外一个微服务。一旦创建配置服务,服务的内容就能够经过基于HTTP的REST端点进行访问。
The naming convention for the application configuration files are appname-env.yml.
for example :
licensingservice.yml
licensingservice-dev.yml
licensingservice-prod.yml
package com.thoughtmechanix.confsvr; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.config.server.EnableConfigServer; @SpringBootApplication @EnableConfigServer //TODO: 使服务成为Spring Cloud Config 服务 public class ConfigServerApplication { public static void main(String[] args) { SpringApplication.run(ConfigServerApplication.class, args); } }
### Classpath and file-based solution ### server: port: 8888 spring: profiles: active: native cloud: config: server: native: searchLocations: file://<chapter 3>/confsvr/src/main/resources/config/licensingservice, file://<chapter 3>confsvr/src/main/resources/config/organizationservice ## #searchLocations: classpath:config/,classpath:config/licensingservice
访问:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.thoughtmechanix</groupId> <artifactId>licensing-service</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>Eagle Eye Licensing Service</name> <description>Licensing Service</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.4.RELEASE</version> </parent> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Camden.SR5</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- 告诉Springboot将要在服务中使用Java Persistence Api (jpa)--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <!-- 告诉springboot拉取spring cloud config 客户端所需的全部依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> <!-- 告诉springboot拉取Postgres Jdbc驱动器--> <dependency> <groupId>postgresql</groupId> <artifactId>postgresql</artifactId> <version>9.1-901.jdbc4</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-rsa</artifactId> </dependency> </dependencies> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <start-class>com.thoughtmechanix.licenses.Application</start-class> <docker.image.name>johncarnell/tmx-licensing-service</docker.image.name> <docker.image.tag>chapter3</docker.image.tag> </properties> <build> <plugins> <!-- We use the Resources plugin to filer Dockerfile and run.sh, it inserts actual JAR filename --> <!-- The final Dockerfile will be created in target/dockerfile/Dockerfile --> <plugin> <artifactId>maven-resources-plugin</artifactId> <executions> <execution> <id>copy-resources</id> <!-- here the phase you need --> <phase>validate</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <outputDirectory>${basedir}/target/dockerfile</outputDirectory> <resources> <resource> <directory>src/main/docker</directory> <filtering>true</filtering> </resource> </resources> </configuration> </execution> </executions> </plugin> <plugin> <groupId>com.spotify</groupId> <artifactId>docker-maven-plugin</artifactId> <version>0.4.10</version> <configuration> <imageName>${docker.image.name}:${docker.image.tag}</imageName> <dockerDirectory>${basedir}/target/dockerfile</dockerDirectory> <resources> <resource> <targetPath>/</targetPath> <directory>${project.build.directory}</directory> <include>${project.build.finalName}.jar</include> </resource> </resources> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
spring: #三个必要配置的属性
application:
name: licensingservice #指定许可证服务的名称,以便Spring Cloud Config 客户端知道正在查找哪一个服务
profiles:
active:
default #指定服务应运行的默认profile oprofile映射到环境
cloud:
config:
uri: http://localhost:8888 #指定Spring cloud config 服务器的位置
https://github.com/carnellj/spmia-chapter3
package com.thoughtmechanix.licenses.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class ServiceConfig{
@Value("${example.property}")
private String exampleProperty;
public String getExampleProperty(){
return exampleProperty;
}
}
server:
port: 8888
spring:
cloud:
config:
server:
encrypt.enabled: false
git:
uri: https://github.com/carnellj/config-repo/
searchPaths: licensingservice,organizationservice
username: native-cloud-apps
password: 0ffended
package com.thoughtmechanix.licenses; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.context.config.annotation.RefreshScope; @SpringBootApplication @RefreshScope //TODO:配置服务器刷新属性 public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
默认状况下,spring cloud 配置服务器在应用程序配置文件中以纯文本格式存储全部属性,包括像数据库凭据这样的敏感信息。
Spring Cloud Config 支持使用对称加密(共享密钥)和非对称加密(公钥/私钥)
适用java8 JCE 下载地址: https://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html
解压:unzip -O CP936 jce_policy-8.zip
查看安装教程
cat README.txt
---------------------------------------------------------------------- Installation ---------------------------------------------------------------------- Notes: o Unix (Solaris/Linux/Mac OS X) and Windows use different pathname separators, so please use the appropriate one ("\", "/") for your environment. o <java-home> (below) refers to the directory where the JRE was installed. It is determined based on whether you are running JCE on a JRE or a JRE contained within the Java Development Kit, or JDK(TM). The JDK contains the JRE, but at a different level in the file hierarchy. For example, if the JDK is installed in /home/user1/jdk1.8.0 on Unix or in C:\jdk1.8.0 on Windows, then <java-home> is: /home/user1/jdk1.8.0/jre [Unix] C:\jdk1.8.0\jre [Windows] If on the other hand the JRE is installed in /home/user1/jre1.8.0 on Unix or in C:\jre1.8.0 on Windows, and the JDK is not installed, then <java-home> is: /home/user1/jre1.8.0 [Unix] C:\jre1.8.0 [Windows] o On Windows, for each JDK installation, there may be additional JREs installed under the "Program Files" directory. Please make sure that you install the unlimited strength policy JAR files for all JREs that you plan to use. Here are the installation instructions: 1) Download the unlimited strength JCE policy files. 2) Uncompress and extract the downloaded file. This will create a subdirectory called jce. This directory contains the following files: README.txt This file local_policy.jar Unlimited strength local policy file US_export_policy.jar Unlimited strength US export policy file 3) Install the unlimited strength policy JAR files. In case you later decide to revert to the original "strong" but limited policy versions, first make a copy of the original JCE policy files (US_export_policy.jar and local_policy.jar). Then replace the strong policy files with the unlimited strength versions extracted in the previous step. The standard place for JCE jurisdiction policy JAR files is: <java-home>/lib/security [Unix] <java-home>\lib\security [Windows]
关于对称密钥,要注意如下几点: 1.对称密钥的长度应该是12个或更多字符,最好是一个随机的字符集。 2.不要丢失对称密钥。一旦使用加密密钥加密某些东西,若是没有对称密钥就没法解密。
对称加密密钥 环境变量
export ENCRYPT_KEY = IMSYMMETRIC
在启动Spring Cloud Config 实例时,Spring Cloud Config 将检测到环境变量ENVRYPT_KEY 已设置,并自动将两个新端点(/ecrypt和/decrypt)添加到Spring Cloud Config 服务。咱们将使用/encrypr端点进行加密。
要解密这个值,可使用/decrypt端点,在调用中传递已加密的字符串。
如今可使用如下语法将已加密的属性添加到Github或基于文件系统的许可证服务的配置文件中
spring cloud config 服务器要求全部已加密的属性前加上{cipher}。{cipher}告诉spirng cloud config 它正在处理已加密的值,启动Spring cloud config 服务器,并使用GET方法访问: http://loaclhost:8888/licensingservice/default
可是访问default时仍是以明文的形式传递
要让客户端对属性进行加密,须要作如下三件事情: 1.配置Spring Cloud Config 不要在服务器端解密属性 2.在许可证服务器上设置对称密钥 3.将spring-security-rsa JAR 添加到许可证服务的pom.xml文件中
spring: cloud: config: server: encrypt.enabled: false #配置Spring Cloud Config 不要在服务器端解密属性
由于许可证服务如今负责解密已经加密的属性,因此须要在许可证服务上设置对称加密,方法是确保ENCRYPT_KEY 环境变量与Spring Cloud Config 服务器使用的对称密钥相同(如IMSYMMETRIC)
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-rsa</artifactId> </dependency>
1.Spring Cloud 配置服务器容许使用特定值建立应用程序属性 2.Spring 使用 Spring profile 来启动服务,以肯定要从 Spring Cloud Config 服务检索哪些环境变量属性。 3.Spring Cloud 配置服务可使用基于文件或者Git的应用程序配置存储库来存储应用程序属性。 4.Spring Cloud 配置服务运行使用对称加密和非对称加密对敏感属性文件进行加密。
这种模型适用于在企业数据中心内部运行的应用程序,以及在一组静态服务器上运行少许服务的状况,但对基于云的微服务应用程序来讲,这种模型并不适用
缘由: 1.单点故障------虽然负载均衡能够实现高可用,但这是整个基础设施的单点故障 2.有限的水平可伸缩------在服务器集群中到单个负载均衡集群的状况下,跨多个服务器水平伸缩负载均衡基础设施的能力有限。 3.静态管理------大多数传统的负载均衡器不是为快速注册和注销服务设计的,他们使用集中式数据库来存储规则的路由,添加新路由的惟一方法一般是经过供应商专有API(Application Programming Interface 应用程序编程接口)来进行添加。 4.复杂------因为负载均衡器充当服务的代理,它必须将服务消费者的请求映射到物理服务。
特色
1.高可用------服务发现须要可以支持“热”集群环境,在服务发现集群中能够跨多个节点共享服务查找。若是一个节点变得不可用,集群的其余节点应该可以接管工做 2.点对点------服务发现集群中的每一个节点共享服务实例的状态 3.负载均衡------服务发现须要在全部的服务实例之间动态的对请求进行负载均衡,以确保服务调用分布在由它管理的全部服务实例上。在许多方面,服务发现取代了许多早期Web应用程序实现中使用的跟静态的、手动管理的负载均衡器
4.有弹性------服务发现的客户端应该在本地“缓存”服务信息。本地缓存容许服务发现功能逐步降级,这样若是服务发现服务变得不可用,应用程序仍然能够基于本地缓存中维护的信息来运行和定位服务。
5.容错------服务发现须要检测出服务实例何时是不健康的,并从能够接收客户端请求的可用服务列表中移除该实例。服务发现应该在没有人为干预的状况下,对这些故障进行检测,并采起行动。
6.了解基于云的服务发现代理的工做方式的概念架构
7.展现即便在服务发现代理不可用时,客户端缓存和负载均衡如何使服务能继续发挥做用
8.了解而后使用Spring Cloud 和 Netflix的Eureka服务发现代理实现服务发现功能
服务注册
服务地址的客户端查找
信息共享
健康检测
服务一般只在一个服务发现实例中进行注册。大多数服务发现的实现使用数据传播的点对点模型,每一个服务实例的数据都被传递到服务发现集群中的全部其余节点。
每一个服务实例将经过服务发现服务去推送服务实例状态,或者服务发现服务从服务实例拉取状态。任何未能返回良好的健康检查信息的服务都将从服务实例池中删除。
分析:
服务在向服务发现服务进行注册以后,这个服务就能够被须要使用这项服务功能的应用程序或其余服务使用,客户端可使用不一样的模型来发现服务,在每次调用服务时,
客户端能够只依赖于服务发现引擎来解析服务位置,使用这种方法,每次调用注册的微服务实例时,服务发现引擎就会被调用。可是,这种方法很脆弱,由于服务客户端
彻底依赖服务发现引擎来查找和调用服务。
一种更健壮的方法是使用所谓的客户端负载均衡,以下
在这个模型中,当服务消费者须要调用一个服务时: 1.它将联系服务发现服务,获取它请求的全部服务实例,而后在服务消费者的机器上本地缓存数据。 2.每当客户端须要调用该服务时,服务消费者将从缓存中查找该服务的位置信息。一般客户端缓存将使用简单负载均衡算法,如:轮询负载均衡算法,以确保服务调用分布在多个服务实例之间。 3.而后,客户端将按期与服务发现服务进行联系,并刷新服务实例的缓存。客户端缓存最终会一致,可是始终存在这样的风险: 客户端联系服务发现实例以进行刷新和调用时,调用可能胡会被定向到不健康的服务器上。 若是在调用服务的过程当中,服务调用失败,那么本地的服务发现缓存将失效,服务发现客户端,将尝试从服务发现代理刷新数据
建立一个服务发现代理来实现服务发现,而后经过代理注册两个服务。接着经过使用服务发现检索到的信息,让一个服务调用另外一个服务。
服务发现:spring cloud 和 Netflix 的Eureka
负载均衡:spirng cloud 和 Netflix 的Ribbon
过程图:
1.随着服务的启动,许可证和组织服务将经过Eureka服务进行注册。这个注册过程将告诉Eureka每一个服务实例的物理位置和端口号,以及正在启动的服务的服务ID。 2.当许可证服务调用组织服务时,许可证服务将使用Netflix Ribbon 库来提供客户端负载均衡。Ribbon将联系Eureka服务去检索服务位置信息,而后在本地进行缓存。 3.Netflix Ribbon库将按期对Eureka服务进行ping操做,并刷新服务位置的本地缓存。任何新的组织服务实例如今都将在本地对许可证服务可见,而任何不健康实例都将从本地缓存中移除。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.thoughtmechanix</groupId> <artifactId>eurekasvr</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>Eureka Server</name> <description>Eureka Server demo project</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.4.RELEASE</version> </parent> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Camden.SR5</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> </dependency> </dependencies> <!--Docker build Config--> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <start-class>com.thoughtmechanix.eurekasvr.EurekaServerApplication</start-class> <java.version>1.8</java.version> <docker.image.name>johncarnell/tmx-eurekasvr</docker.image.name> <docker.image.tag>chapter4</docker.image.tag> </properties> <build> <plugins> <!-- We use the Resources plugin to filer Dockerfile and run.sh, it inserts actual JAR filename --> <!-- The final Dockerfile will be created in target/dockerfile/Dockerfile --> <plugin> <artifactId>maven-resources-plugin</artifactId> <executions> <execution> <id>copy-resources</id> <!-- here the phase you need --> <phase>validate</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <outputDirectory>${basedir}/target/dockerfile</outputDirectory> <resources> <resource> <directory>src/main/docker</directory> <filtering>true</filtering> </resource> </resources> </configuration> </execution> </executions> </plugin> <plugin> <groupId>com.spotify</groupId> <artifactId>docker-maven-plugin</artifactId> <version>0.4.10</version> <configuration> <imageName>${docker.image.name}:${docker.image.tag}</imageName> <dockerDirectory>${basedir}/target/dockerfile</dockerDirectory> <resources> <resource> <targetPath>/</targetPath> <directory>${project.build.directory}</directory> <include>${project.build.finalName}.jar</include> </resource> </resources> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
#Default port is 8761 server: port: 8761 #Eureka 服务器将要监听的端口 eureka: client: registerWithEureka: false #不要使用Eureka服务进行注册 fetchRegistry: false #不要在本地缓存注册表信息 server: waitTimeInMsWhenSyncEmpty: 5 #在服务器接收请求以前等待的初始时间 serviceUrl: defaultZone: http://localhost:8761
package com.thoughtmechanix.eurekasvr; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication @EnableEurekaServer public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } }
每次服务注册须要30s的时间才能显示在Eureka服务中,由于Eureka须要从服务器接收三次连续心跳包ping,每次心跳包ping间隔10s,而后才能使用这个服务。
引入Eureka库,以即可以使用Eureka注册服务
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency>
eureka: instance: preferIpAddress: true #注册服务的IP而不是服务名称 client: registerWithEureka: true #向Eureka注册服务 fetchRegistry: true serviceUrl: #拉取注册表的本地副本 defaultZone: http://localhost:8761/eureka/ #Eureka服务的位置
spring: application: name: organizationservice #将使用Eureka注册的服务的逻辑名称 profiles: active: default cloud: config: enabled: true
为何偏向于IP地址
在默认状况下,Eureka在尝试注册服务时,将会使用主机名让外界与它进行联系。这种方式,在基于服务器的环境中运行良好,在这样的环境中,服务将被分配一个DNS支持的主机名。可是在基于容器的部署中(如Docker),容器将以随机生成的主机名启动,而且该容器没有DNS记录。
若是没有将eureka.instance.preperIpAddress设置为true,那么客户端将没法正确的解析主机名的位置,由于该容器不存在DNS记录,设置preferIpAddress属性将通知Eureka服务,客户端想要经过IP地址进行通告。
eureka.client.registerWithEureka属性是一个触发器,它能够告诉组织服务经过Eureka注册自己。
这个属性用于告知Spring Eureka客户端以获取注册表的本地副本。将此属性设置为true将在本地缓存注册表,而不是每次查找服务都调用Eureka服务。每隔30s,客户端软件就会从新联系Eureka服务以便查看注册表是否有任何变化
eureka.serviceUrl.defaultZone包含客户端用于解析服务位置的Eureka服务的列表,该列表以逗号进行分隔
Eureka高可用
创建多个URL服务并不足以实现高可用。eureka.serviceUrl.defaultZone属性仅为客户端提供一个进行通讯的Eureka服务列表。除此以外还须要创建多个Eureka服务,以便相互复制注册表的内容。
一组Eureka注册表相互之间使用点对点通讯模型进行通讯,在这种模型中,必须对每一个Eureka服务进行配置,以了解集群中的其余节点。
查看服务的全部实例:
http://<eureka service>:8761/eureka/apps/<APPID>
For instance, to see the organization service in the registry you can call
http://localhost:8761/eureka/apps/organizationservice
//在Eureka和服务启动时要保持耐心: 当服务经过Eureka注册时,Eureka将在30s内等待3次连续的健康检查,而后才能经过Eureka获取该服务。这个热身过程让开发者们感到疑惑,由于若是他们在服务启动后当即调用他们的服务,他们会认为Eureka尚未注册他们的服务。
这一点在Docker 环境运行的代码示例中很明显,由于Eureka服务和应用程序服务(许可证服务和组织服务)都是在同一时间启动的。在生产环境中,Eureka服务已经在运行,若是读者正在部署现有的服务,那么旧服务仍然能够用于接收请求。
研究3个不一样的Spring/Netflix客户端库,服务消费者可使用他们来和Ribbon进行交互。从最低级别到高级别,这些库包含了不一样的与Ribbon进行交互的抽象层次:
Spring DiscoveryClient
启用了RestTemplate的Spring DiscoveryClient
Netflix Feign客户端
package com.thoughtmechanix.licenses.controllers; import com.thoughtmechanix.licenses.model.License; import com.thoughtmechanix.licenses.services.LicenseService; import com.thoughtmechanix.licenses.config.ServiceConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.ResponseStatus; import java.util.List; @RestController @RequestMapping(value="v1/organizations/{organizationId}/licenses") public class LicenseServiceController { @Autowired private LicenseService licenseService; @Autowired private ServiceConfig serviceConfig; @RequestMapping(value="/",method = RequestMethod.GET) public List<License> getLicenses( @PathVariable("organizationId") String organizationId) { return licenseService.getLicensesByOrg(organizationId); } @RequestMapping(value="/{licenseId}",method = RequestMethod.GET) public License getLicenses( @PathVariable("organizationId") String organizationId, @PathVariable("licenseId") String licenseId) { return licenseService.getLicense(organizationId, licenseId, ""); } @RequestMapping(value="/{licenseId}/{clientType}",method = RequestMethod.GET) //TODO:clientType肯定Spring REST要使用的客户端的类型 public License getLicensesWithClient( @PathVariable("organizationId") String organizationId, @PathVariable("licenseId") String licenseId, @PathVariable("clientType") String clientType) { return licenseService.getLicense(organizationId,licenseId, clientType); } @RequestMapping(value="{licenseId}",method = RequestMethod.PUT) public void updateLicenses( @PathVariable("licenseId") String licenseId, @RequestBody License license) { licenseService.updateLicense(license); } @RequestMapping(value="/",method = RequestMethod.POST) public void saveLicenses(@RequestBody License license) { licenseService.saveLicense(license); } @RequestMapping(value="{licenseId}",method = RequestMethod.DELETE) @ResponseStatus(HttpStatus.NO_CONTENT) public void deleteLicenses( @PathVariable("licenseId") String licenseId, @RequestBody License license) { licenseService.deleteLicense(license); } }
package com.thoughtmechanix.licenses.services; import com.thoughtmechanix.licenses.clients.OrganizationDiscoveryClient; import com.thoughtmechanix.licenses.clients.OrganizationFeignClient; import com.thoughtmechanix.licenses.clients.OrganizationRestTemplateClient; import com.thoughtmechanix.licenses.config.ServiceConfig; import com.thoughtmechanix.licenses.model.License; import com.thoughtmechanix.licenses.model.Organization; import com.thoughtmechanix.licenses.repository.LicenseRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; import java.util.UUID; @Service public class LicenseService { @Autowired private LicenseRepository licenseRepository; @Autowired ServiceConfig config; @Autowired OrganizationFeignClient organizationFeignClient; @Autowired OrganizationRestTemplateClient organizationRestClient; @Autowired OrganizationDiscoveryClient organizationDiscoveryClient; private Organization retrieveOrgInfo(String organizationId, String clientType){ Organization organization = null; switch (clientType) { case "feign": System.out.println("I am using the feign client"); organization = organizationFeignClient.getOrganization(organizationId); break; case "rest": System.out.println("I am using the rest client"); organization = organizationRestClient.getOrganization(organizationId); break; case "discovery": System.out.println("I am using the discovery client"); organization = organizationDiscoveryClient.getOrganization(organizationId); break; default: organization = organizationRestClient.getOrganization(organizationId); } return organization; } /** * 服务方法 * @param organizationId * @param licenseId * @param clientType * @return */ public License getLicense(String organizationId,String licenseId, String clientType) { License license = licenseRepository.findByOrganizationIdAndLicenseId(organizationId, licenseId); Organization org = retrieveOrgInfo(organizationId, clientType); return license .withOrganizationName( org.getName()) .withContactName( org.getContactName()) .withContactEmail( org.getContactEmail() ) .withContactPhone( org.getContactPhone() ) .withComment(config.getExampleProperty()); } public List<License> getLicensesByOrg(String organizationId){ return licenseRepository.findByOrganizationId( organizationId ); } public void saveLicense(License license){ license.withId( UUID.randomUUID().toString()); licenseRepository.save(license); } public void updateLicense(License license){ licenseRepository.save(license); } public void deleteLicense(License license){ licenseRepository.delete( license.getLicenseId()); } }
使用Spring DiscoveryClient查找服务实例
/* *Spring DiscoveryClient 提供了对Ribbon和Ribbon中缓存的注册服务的最底层访问。使用DiscoveryClient能够查询Ribbon注册的全部服务以及这些服务对应的URL */
package com.thoughtmechanix.licenses; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; import java.util.Collections; import java.util.List; @SpringBootApplication @EnableDiscoveryClient //TODO:激活Spring DiscoveryClient /// @EnableDiscoveryClient注解是Spring cloud的触发器,其做用是使应用程序可以使DiscoveryClient和Ribbon库 @EnableFeignClients public class Application { @LoadBalanced @Bean public RestTemplate getRestTemplate(){ return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
package com.thoughtmechanix.licenses.clients; import com.thoughtmechanix.licenses.model.Organization; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import java.util.List; @Component public class OrganizationDiscoveryClient { @Autowired private DiscoveryClient discoveryClient; public Organization getOrganization(String organizationId) { RestTemplate restTemplate = new RestTemplate(); //获取组织服务的全部实例的列表 List<ServiceInstance> instances = discoveryClient.getInstances("organizationservice"); if (instances.size()==0) return null; //检索要调用的服务端点 String serviceUri = String.format("%s/v1/organizations/%s",instances.get(0).getUri().toString(), organizationId); ResponseEntity< Organization > restExchange = restTemplate.exchange( serviceUri, HttpMethod.GET, null, Organization.class, organizationId); return restExchange.getBody(); } }
只有在服务须要查询Ribbon以了解哪些服务器和服务实例已经经过它注册时,才应该直接使用DiscoveryClient 上述代码存在如下问题 1.没有利用Ribbon的客户端负载均衡------尽管经过直接调用DiscoveryClient能够获取服务列表,可是要调用哪些返回的服务实例就成了开发人员的责任。 2.开发人员作了太多工做------如今开发人员必须构建一个用来调用服务的URL。尽管这是一件小事,可是编写的代码越少意味着须要调试的代码越少。
/* *一旦应用程序类中经过@EnableDiscoveryClient注解启用了Spring Discovery由Spirng 框架管理的全部RestTemplate都将注入一个启用Ribbon的拦截器,这个拦截器将改变使用RestTemplate类建URL的行为。直接实例化RestTemplate类能够避免这种行为 */
使用带有Ribbon功能的Sping RestTemplate调用服务
package com.thoughtmechanix.licenses; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; import java.util.Collections; import java.util.List; @SpringBootApplication @EnableDiscoveryClient //TODO:激活Spring DiscoveryClient /// @EnableDiscoveryClient注解是Spring cloud的触发器,其做用是使应用程序可以使DiscoveryClient和Ribbon库 @EnableFeignClients public class Application { @LoadBalanced //TODO:告诉Spring Cloud建立一个支持Ribbon的RestTemplate类 @Bean public RestTemplate getRestTemplate(){ return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
package com.thoughtmechanix.licenses.clients; import com.thoughtmechanix.licenses.model.Organization; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; @Component public class OrganizationRestTemplateClient { @Autowired RestTemplate restTemplate; public Organization getOrganization(String organizationId){ ResponseEntity<Organization> restExchange = restTemplate.exchange( //使用支持Ribbon的RestTemplate时,使用Eureka服务ID来构建目标URL "http://organizationservice/v1/organizations/{organizationId}", HttpMethod.GET, null, Organization.class, organizationId); return restExchange.getBody(); } }
URL中的服务器名称与经过Eureka注册的服务组织服务的应用程序ID-------organiaztionservice相匹配 http://{applicationid}/v1/organizations/{organizationId}
启动Ribbon的RestTemplate类,将解析传递给他的URL,并使用传递的内容做为服务器名称,该服务器名称做为从Ribbon查询服务实例的键,实际的服务位置和端口与开发人员彻底抽象隔离。
此外,经过使用RestTemplate类,Ribbon将在全部服务实例之间轮询负载均衡全部请求。
使用Netflix Feign客户端调用服务
Netflix 的Feign客户端是Spring启用Ribbon的RestTemplate类的替代方案。Feign库将采用不一样的方法来调用REST服务,
方法是让开发人员首先定义一个Java接口,而后使用Spirng Cloud注解来标注接口,以映射Ribbon将要调用的基于Eureka的服务。
Spirng Cloud框架将动态生成一个代理类,用于调用目标REST服务。
package com.thoughtmechanix.licenses; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; import java.util.Collections; import java.util.List; @SpringBootApplication @EnableDiscoveryClient //TODO:激活Spring DiscoveryClient /// @EnableDiscoveryClient注解是Spring cloud的触发器,其做用是使应用程序可以使DiscoveryClient和Ribbon库 @EnableFeignClients //TODO:Feign客户端 public class Application { @LoadBalanced //TODO:告诉Spring Cloud建立一个支持Ribbon的RestTemplate类 @Bean public RestTemplate getRestTemplate(){ return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
package com.thoughtmechanix.licenses.clients; import com.thoughtmechanix.licenses.model.Organization; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @FeignClient("organizationservice") public interface OrganizationFeignClient { @RequestMapping( method= RequestMethod.GET, value="/v1/organizations/{organizationId}", consumes="application/json") Organization getOrganization(@PathVariable("organizationId") String organizationId); }
要使用OrganizationFeignClient类,开发人员须要作的只是自动装配并使用它。Feign客户端代码将为开发人员承担全部的编码工做
错误处理
使用标准的Spring RestTemplate类时,全部的服务调用的HTTP状态码都将经过ResponseEntity类的getStatusCode()方法返回。经过Feign客户端,任何被调用的服务返回的HTTP状态码4xxx-5xx都将映射为FeignException。FeignException包含能够被解析为特定的错误消息的JSON体
1.服务发现模式用于抽象服务的物理位置。 2.诸如Eureka这样的服务发现引擎能够在不影响客户端的状况下,无缝的向环境中添加和从环境中移除服务实例。 3.经过在进行服务调用的客户端缓存服务的物理位置,客户端负载均衡就能够提供额外的性能和弹性 4.Eureka是Netflix项目,在与Spirng Cloud一块儿使用时,很容易对Eureka进行创建和配置 5.在Spring cloud 、Netflix Eureka 、Netflix Ribbon中使用了3种不一样机制来调用服务: 1使用Spring Cloud 服务DiscoveryClient 2使用Spring Cloud 和支持Ribbon 的RestTemplate 3使用Spring Cloud 和Netflix的Feign客户端
在远程服务发生错误或表现不佳时保护远程资源(另外一个微服务的调用或数据库查询)的客户端免于崩溃。这些模式的目标是让客户端的“快速失败”,而不消耗诸如数据库链接和线程池之类的宝贵资源。
客户端服务器负载均衡器位于服务消费者之间,因此负载均衡器能够检测服务实例是否抛出错误或表现不佳,若是客户端负载均衡器检测到问题,它能够从服务位置池中移除该服务实例,并防止未来的服务调用访问该服务实例
断路器模式是模仿电路断路器的客户端弹性模式,在电气系统中,断路器将检测是否有过多电流流过电线,若是断路器检测到问题,它将断开与电气系统的其他部分的链接,并保护下游部件不被烧毁。 //有了软件断路器,当远程服务被调用时,断路器将监视这个调用,若是调用时间太长,断路器将会介入并中断调用。此外,断路器将监视全部远程资源的调用,若是对某一个远程资源的调用失败次数太多,那么断路器就会出现并采起快速失败,阻止未来调用失败的远程资源。
当远程服务调用失败时,服务消费者将执行替代代码路径,并尝试经过其余方式执行操做,而不是生成一个异常。这一般涉及从另外一个数据源查找数据或将用户的请求进行排队来处理。用户的调用结果不会显示为提示问题的异常,但用户可能被告知,他们的请求在晚些时候被知足。
舱壁模式是创建在造船的概念上的,采用舱壁设计,一艘船被划分为彻底隔离和防水的隔间,这称为舱壁。即便船的船体被击穿,因为船体被划分为水密舱(舱壁),舱壁将水限制在被击穿的船的区域内,防止整艘船灌水被并沉没。 //一样的概念能够将应用于必须与多个远程资源交互的服务。经过使用舱壁模式,能够把远程资源的调用分到线程池中,并下降一个缓慢的远程资源调用拖垮整个应用程序的风险,线程池充当服务的“舱壁”每一个远程资源都是隔离的,
并分配给线程池。若是一个服务响应缓慢,那么这种服务调用的线程池就会饱和并中止处理请求,而对其余服务的服务调用则不会变得饱和,由于他们被分配给了其余线程池。
为何客户端负载均衡很重要?
断路跳闸
//1.快速失败------当远程服务处于降级状态时,应用程序将会快速失败,并防止一般会拖垮整个应用程序的资源耗尽问题的出现。在大多数中断状况下,最好是部分服务关闭而不是彻底关闭 //2.优雅的失败------经过超时和快速失败,断路器模式使应用程序开发人员有能力优雅的失败,或寻求替代机制来执行用户的意图。例如,若是用户尝试从一个数据源检索数据,而且该数据源正在经历服务降级,那么应用程序开发人员能够尝试从其余地方检索该数据。 //3.有了断路器模式做为中介,断路器能够按期检查所请求的资源是否从新上线,并在没有人为干预的状况下从新容许对该资源进行访问。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.thoughtmechanix</groupId> <artifactId>licensing-service</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>Eagle Eye Licensing Service</name> <description>Licensing Service</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.4.RELEASE</version> </parent> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Camden.SR5</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> <dependency> <groupId>postgresql</groupId> <artifactId>postgresql</artifactId> <version>9.1-901.jdbc4</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-rsa</artifactId> </dependency> <!-- <dependency> <groupId>com.netflix.hystrix</groupId> <artifactId>hystrix-javanica</artifactId> <version>1.5.9</version> </dependency> --> </dependencies> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <start-class>com.thoughtmechanix.licenses.Application</start-class> <docker.image.name>johncarnell/tmx-licensing-service</docker.image.name> <docker.image.tag>chapter6</docker.image.tag> </properties> <build> <plugins> <!-- We use the Resources plugin to filer Dockerfile and run.sh, it inserts actual JAR filename --> <!-- The final Dockerfile will be created in target/dockerfile/Dockerfile --> <plugin> <artifactId>maven-resources-plugin</artifactId> <executions> <execution> <id>copy-resources</id> <!-- here the phase you need --> <phase>validate</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <outputDirectory>${basedir}/target/dockerfile</outputDirectory> <resources> <resource> <directory>src/main/docker</directory> <filtering>true</filtering> </resource> </resources> </configuration> </execution> </executions> </plugin> <plugin> <groupId>com.spotify</groupId> <artifactId>docker-maven-plugin</artifactId> <version>0.4.10</version> <configuration> <imageName>${docker.image.name}:${docker.image.tag}</imageName> <dockerDirectory>${basedir}/target/dockerfile</dockerDirectory> <resources> <resource> <targetPath>/</targetPath> <directory>${project.build.directory}</directory> <include>${project.build.finalName}.jar</include> </resource> </resources> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
package com.thoughtmechanix.licenses; import com.thoughtmechanix.licenses.utils.UserContextInterceptor; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; import java.util.Collections; import java.util.List; @SpringBootApplication @EnableEurekaClient @EnableCircuitBreaker // TODO:告诉Spring cloud服务将要使用Hystrix public class Application { @LoadBalanced @Bean public RestTemplate getRestTemplate(){ RestTemplate template = new RestTemplate(); List interceptors = template.getInterceptors(); if (interceptors==null){ template.setInterceptors(Collections.singletonList(new UserContextInterceptor())); } else{ interceptors.add(new UserContextInterceptor()); template.setInterceptors(interceptors); } return template; } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Hystrix和spirng cloud使用@HystrixCommand注解来将Java类方法标记为由Hystrix断路器进行管理。当spring框架看到@HystrixCommand时,它将动态生成一个代理,该代理将包装该方法,并经过专门用于处理远程的线程池来管理对该方法的全部调用。
package com.thoughtmechanix.licenses.services; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; import com.thoughtmechanix.licenses.clients.OrganizationRestTemplateClient; import com.thoughtmechanix.licenses.config.ServiceConfig; import com.thoughtmechanix.licenses.model.License; import com.thoughtmechanix.licenses.model.Organization; import com.thoughtmechanix.licenses.repository.LicenseRepository; import com.thoughtmechanix.licenses.utils.UserContext; import com.thoughtmechanix.licenses.utils.UserContextHolder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.UUID; @Service public class LicenseService { @Autowired private LicenseRepository licenseRepository; @Autowired ServiceConfig config; @Autowired OrganizationRestTemplateClient organizationRestClient; private static final Logger logger = LoggerFactory.getLogger(LicenseService.class); @HystrixCommand public License getLicense(String organizationId,String licenseId) throws InterruptedException { License license = licenseRepository.findByOrganizationIdAndLicenseId(organizationId, licenseId); Organization org = getOrganization(organizationId); return license .withOrganizationName( org.getName()) .withContactName( org.getContactName()) .withContactEmail( org.getContactEmail() ) .withContactPhone( org.getContactPhone() ) .withComment(config.getExampleProperty()); } @HystrixCommand private Organization getOrganization(String organizationId) { return organizationRestClient.getOrganization(organizationId); } private void randomlyRunLong(){ Random rand = new Random(); int randomNum = rand.nextInt((3 - 1) + 1) + 1; if (randomNum==3) sleep(); } private void sleep(){ try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } @HystrixCommand(fallbackMethod = "buildFallbackLicenseList", threadPoolKey = "licenseByOrgThreadPool", threadPoolProperties = {@HystrixProperty(name = "coreSize",value="30"), @HystrixProperty(name="maxQueueSize", value="10"), }, commandProperties={ @HystrixProperty(name="circuitBreaker.requestVolumeThreshold", value="10"), @HystrixProperty(name="circuitBreaker.errorThresholdPercentage", value="75"), @HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds", value="7000"), @HystrixProperty(name="metrics.rollingStats.timeInMilliseconds", value="15000"), @HystrixProperty(name="metrics.rollingStats.numBuckets", value="5")} ) public List<License> getLicensesByOrg(String organizationId){ randomlyRunLong(); return licenseRepository.findByOrganizationId(organizationId); } private List<License> buildFallbackLicenseList(String organizationId){ List<License> fallbackList = new ArrayList<>(); License license = new License() .withId("0000000-00-00000") .withOrganizationId( organizationId ) .withProductName("Sorry no licensing information currently available"); fallbackList.add(license); return fallbackList; } public void saveLicense(License license){ license.withId( UUID.randomUUID().toString()); licenseRepository.save(license); } public void updateLicense(License license){ licenseRepository.save(license); } public void deleteLicense(License license){ licenseRepository.delete( license.getLicenseId()); } }
对组织微服务的调用超时
咱们可使用方法级注解使被标记的调用拥有断路器功能,其优势在于不管是访问数据库仍是调用微服务,它都是相同的注解。
for instance:在许可证服务中咱们须要查找与许可证关联的组织的名称。若是要使用断路器来包装对组织服务的调用的话
一个简单的方法就是将RestTemplate调用分解到本身的方法,并使用@HystrixCommand注解进行标注:
@HystrixCommand
private Organization getOrganization(String organizationId) {
return organizationRestClient.getOrganization(organizationId);
}
//注意:在默认状况下,在指定不带属性的@HystrixCommand注解时,这个注解会将全部的远程服务调用都放在同一线程池下。
定制断路器的超时时间
@HystrixCommand( commandProperties= {@HystrixProperty( name="execution.isolation.thread.timeoutInMilliseconds", value="12000")}) public List<License> getLicensesByOrg(String organizationId){ randomlyRunLong(); return licenseRepository.findByOrganizationId(organizationId); }
fallbackMethod定义了一个方法,若是来自Hystrix的调用失败,那么就会调用该方法。
注意://1.后备方法必须在同一个类中 2.后备方法与原方法必须具备相同的方法签名。
(1)后备是一种在资源超时或失败提供行动方案的机制,若是发现本身使用后备来捕获超时异常,而后只是作日志记录,就应该在try...catch...上
(2)注意使用后备方法所执行的操做。若是在后备服务中调用另外一个分布式服务,就可能须要使用@HystrixCommand注解来包装后备方法。记住在主要
动方案中经历的相同的失败有可能也会影响次要的后备方案。
@HystrixCommand(fallbackMethod = "buildFallbackLicenseList") public List<License> getLicensesByOrg(String organizationId){ randomlyRunLong(); return licenseRepository.findByOrganizationId(organizationId); } private List<License> buildFallbackLicenseList(String organizationId){ List<License> fallbackList = new ArrayList<>(); License license = new License() .withId("0000000-00-00000") .withOrganizationId( organizationId ) .withProductName( "Sorry no licensing information currently available"); fallbackList.add(license); return fallbackList; }
结果
//舱壁模式将远程资源调用隔离在他们的本身的线程中,以便控制单个表现不佳的服务,而不会使该容器崩溃。
Hystrix使用线程池来委派全部对远程服务的请求,在默认状况下,全部的Hystrix命令都将共享同一个线程池来处理请求
多种资源类型共享默认的Hystrix线程池:
Hystrix命令绑定到隔离的线程池:
//(1)为getLicensesByOrg()调用创建一个单独的线程池 //(2)设置线程池的线程数 //(3)设置单个线程繁忙时可排队的请求数的队列大小
// 线程池大小 Netflix推荐如下公式:
// 服务在健康状态时每秒支撑的最大请求数X第99百分位延迟时间(以秒为单位)+用于缓冲的少许额外线程
@HystrixCommand(fallbackMethod = "buildFallbackLicenseList", threadPoolKey = "licenseByOrgThreadPool", threadPoolProperties = {@HystrixProperty(name = "coreSize",value="30"), @HystrixProperty(name="maxQueueSize", value="10")} ) public List<License> getLicensesByOrg(String organizationId){ return licenseRepository.findByOrganizationId(organizationId); )
//Hystrix不只能超时长时间运行的调用,它还会监控调用失败的次数,若是调用失败的次数足够多,那么Hystrix会在请求发送到远程资源以前,经过调用失败来阻止将来的调用到达服务。
package com.thoughtmechanix.licenses.services; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; import com.thoughtmechanix.licenses.clients.OrganizationRestTemplateClient; import com.thoughtmechanix.licenses.config.ServiceConfig; import com.thoughtmechanix.licenses.model.License; import com.thoughtmechanix.licenses.model.Organization; import com.thoughtmechanix.licenses.repository.LicenseRepository; import com.thoughtmechanix.licenses.utils.UserContext; import com.thoughtmechanix.licenses.utils.UserContextHolder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.UUID; @Service public class LicenseService { @Autowired private LicenseRepository licenseRepository; @Autowired ServiceConfig config; @Autowired OrganizationRestTemplateClient organizationRestClient; private static final Logger logger = LoggerFactory.getLogger(LicenseService.class); @HystrixCommand public License getLicense(String organizationId,String licenseId) throws InterruptedException { License license = licenseRepository.findByOrganizationIdAndLicenseId(organizationId, licenseId); Organization org = getOrganization(organizationId); return license .withOrganizationName( org.getName()) .withContactName( org.getContactName()) .withContactEmail( org.getContactEmail() ) .withContactPhone( org.getContactPhone() ) .withComment(config.getExampleProperty()); } @HystrixCommand private Organization getOrganization(String organizationId) { return organizationRestClient.getOrganization(organizationId); } private void randomlyRunLong(){ Random rand = new Random(); int randomNum = rand.nextInt((3 - 1) + 1) + 1; if (randomNum==3) sleep(); } private void sleep(){ try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } @HystrixCommand(fallbackMethod = "buildFallbackLicenseList", threadPoolKey = "licenseByOrgThreadPool", threadPoolProperties = {@HystrixProperty(name = "coreSize",value="30"), @HystrixProperty(name="maxQueueSize", value="10"), }, commandProperties={ @HystrixProperty(name="circuitBreaker.requestVolumeThreshold", value="10"), @HystrixProperty(name="circuitBreaker.errorThresholdPercentage", value="75"), @HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds", value="7000"), @HystrixProperty(name="metrics.rollingStats.timeInMilliseconds", value="15000"), @HystrixProperty(name="metrics.rollingStats.numBuckets", value="5")} ) public List<License> getLicensesByOrg(String organizationId){ randomlyRunLong(); return licenseRepository.findByOrganizationId(organizationId); } private List<License> buildFallbackLicenseList(String organizationId){ List<License> fallbackList = new ArrayList<>(); License license = new License() .withId("0000000-00-00000") .withOrganizationId( organizationId ) .withProductName("Sorry no licensing information currently available"); fallbackList.add(license); return fallbackList; } public void saveLicense(License license){ license.withId( UUID.randomUUID().toString()); licenseRepository.save(license); } public void updateLicense(License license){ licenseRepository.save(license); } public void deleteLicense(License license){ licenseRepository.delete( license.getLicenseId()); } }
Hystrix库是高度可配的,可让开发人员严格控制它定义的断路器模式和舱壁模式的行为。开发人员能够经过修改Hystrix断路器的配置,控制Hystrix在超时远程调用以前须要等待的时间。开发人员还能够控制Hystrix断路器什么时候跳匝以及重置断路器。
三个配置级别:
1.整个应用程序级别的默认值
2.类级别的默认值
3.在类中定义的线程池级别
@HystrixCommond注解的配置值
属性名称 | 默认值 | 描述 |
fallbackMethod | None | Identifies the method within the class that will be called if |
threadPoolKey | None | Gives the @HystrixCommand a unique name and creates |
threadPoolProperties | None | Core Hystrix annotation attribute that’s used to configure |
coreSize | 10 | Sets the size of the thread pool. |
maxQueueSize | -1 | Maximum queue size that will set in front of the thread |
circuitBreaker.request- |
20 | Sets the minimum number of requests that must be pro- |
circuitBreaker.error- |
50 | The percentage of failures that must occur within the roll- |
circuitBreaker.sleep- |
5000 | The number of milliseconds Hystrix will wait before trying a |
metricsRollingStats. |
10,000 | The number of milliseconds Hystrix will collect and monitor |
metricsRollingStats |
10 | The number of metrics buckets Hystrix will maintain within |
当一个@HystrixCommand被执行时,它可使用两种不一样的隔离策略------THREAD(线程)(默认)和SEMAPHORE(信号量)来运行。
当执行@hystrixcommand时,可使用两种不一样的隔离来运行它策略:线程和信号量。默认状况下,hystrix使用线程隔离运行。用于保护调用的每一个hystrix命令都运行在一个独立的线程池中不与进行调用的父线程共享其上下文。这意味着海星能够中断在其控制下的线程的执行,而没必要担忧 中断与执行原始任务的父线程相关联的任何其余活动召唤。经过基于信号量的隔离,hystrix管理受保护的分布式调用。在不启动新线程的状况下经过@hystrixcommand注释中断调用超时时的父线程。在同步容器服务器环境中(tomcat),中断父线程将致使引起异常没法被开发人员捕获。这可能会致使 开发人员编写代码,由于他们没法捕获抛出的异常或执行任何资源清理或错误处理。要控制命令池的隔离设置,能够设置命令-@hystrixcommand注释的properties属性。例如,若是你想在hystrix命令上设置隔离级别以使用信号量隔离, 你会用:
@HystrixCommand(
commandProperties = {
@HystrixProperty(
name="execution.isolation.strategy", value="SEMAPHORE")})
在默认状况下,Hystrix不会将父线程的上下文传播到由Hystrix命令管理的线程中。例如,在默认状况下,对被父线程调用并由@HystrixCommand保护的方法而言,在父线程中设置ThreadLoacl值的值都是不可用的(THREAD隔离级别)
让咱们看一个具体的例子。一般以REST为基础要将上下文信息传递给服务调用的环境这将帮助您在操做上管理服务。例如,您能够经过rest调用的http头中的相关id或身份验证令牌而后被传播到任何下游服务调用。相关ID容许您具备惟一的标识符,能够在单个服务调用中跨多个服务调用进行跟踪交易。
要使此值在服务调用中的任何位置均可用,可使用spring 过滤器类来拦截进入rest服务的每一个调用,并从传入的http请求并将此上下文信息存储在自定义用户中-内容对象。而后,只要您的代码须要在rest服务中访问这个值调用,代码能够从threadlocal存储变量检索usercontext
并读取值。下面的清单显示了一个示例spring过滤器,您能够在您的受权服务中使用。
package com.thoughtmechanix.licenses.utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import java.io.IOException; @Component public class UserContextFilter implements Filter { private static final Logger logger = LoggerFactory.getLogger(UserContextFilter.class); @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; /** * 检索调用的HTTP首部中设置的值,将这些值赋给存储在UserContextHolder中的UserContent */ UserContextHolder.getContext().setCorrelationId( httpServletRequest.getHeader(UserContext.CORRELATION_ID) ); UserContextHolder.getContext().setUserId(httpServletRequest.getHeader(UserContext.USER_ID)); UserContextHolder.getContext().setAuthToken(httpServletRequest.getHeader(UserContext.AUTH_TOKEN)); UserContextHolder.getContext().setOrgId(httpServletRequest.getHeader(UserContext.ORG_ID)); logger.debug("License Service Incoming Correlation id: {}", UserContextHolder.getContext().getCorrelationId()); filterChain.doFilter(httpServletRequest, servletResponse); } @Override public void init(FilterConfig filterConfig) throws ServletException {} @Override public void destroy() {} }
package com.thoughtmechanix.licenses.utils; import org.springframework.util.Assert; public class UserContextHolder { //当前线程 private static final ThreadLocal<UserContext> userContext = new ThreadLocal<UserContext>(); public static final UserContext getContext(){ UserContext context = userContext.get(); if (context == null) { context = createEmptyContext(); userContext.set(context); } return userContext.get(); } public static final void setContext(UserContext context) { Assert.notNull(context, "Only non-null UserContext instances are permitted"); userContext.set(context); } public static final UserContext createEmptyContext(){ return new UserContext(); } }
//一旦提交了这个调用,当它流经UserContent、LicenseServiceController、LicenseServer类时,咱们能够看到3条日志消息记录传入的关联ID: UserContext Correlation id: TEST-CORRELATION-ID LicenseServiceController Correlation id: TEST-CORRELATION-ID LicenseService.getLicenseByOrg Correlation:
//正如预期的那样,一旦这个调用使用了Hystrix保护的LicenseService.getLicenseByOrg()方法,就没法获得关联ID的值,幸运的是,Hystrix和Spring Cloud提供了一种机制,能够将父线程的上下文传播到有Hystrix线程池管理的线程。这种机制称为“HystrixConcurrencyStrategy”
HystrixConcurrencyStrategy实战
自定义HystrixConcurrentcyStrategy须要执行如下3个操做: 1.定义自定义的Hystrix并发策略 2.定义一个Callable类,将UserContext注入Hystrix命令中 3.配置Spring Cloud以使用自定义Hystrix策略
1.定义自定义的Hystrix并发策略
package com.thoughtmechanix.licenses.hystrix; import com.netflix.hystrix.HystrixThreadPoolKey; import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariable; import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableLifecycle; import com.netflix.hystrix.strategy.properties.HystrixProperty; import com.thoughtmechanix.licenses.utils.UserContext; import com.thoughtmechanix.licenses.utils.UserContextHolder; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class ThreadLocalAwareStrategy extends HystrixConcurrencyStrategy{ private HystrixConcurrencyStrategy existingConcurrencyStrategy; /** * Spring cloud已经定义了一个并发类。将已存在的并发策略传入自定义的HystrixConcurrencyStrategy的类的构造器中 * @param existingConcurrencyStrategy */ public ThreadLocalAwareStrategy( HystrixConcurrencyStrategy existingConcurrencyStrategy) { this.existingConcurrencyStrategy = existingConcurrencyStrategy; } @Override public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) { return existingConcurrencyStrategy != null ? existingConcurrencyStrategy.getBlockingQueue(maxQueueSize) : super.getBlockingQueue(maxQueueSize); } @Override public <T> HystrixRequestVariable<T> getRequestVariable( HystrixRequestVariableLifecycle<T> rv) { return existingConcurrencyStrategy != null ? existingConcurrencyStrategy.getRequestVariable(rv) : super.getRequestVariable(rv); } @Override public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixProperty<Integer> corePoolSize, HystrixProperty<Integer> maximumPoolSize, HystrixProperty<Integer> keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { return existingConcurrencyStrategy != null ? existingConcurrencyStrategy.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue) : super.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); } @Override public <T> Callable<T> wrapCallable(Callable<T> callable) { return existingConcurrencyStrategy != null ? existingConcurrencyStrategy .wrapCallable(new DelegatingUserContextCallable<T>(callable, UserContextHolder.getContext())) : super.wrapCallable(new DelegatingUserContextCallable<T>(callable, UserContextHolder.getContext())); } }
2.定义一个Callable类,将UserContext注入Hystrix命令中
package com.thoughtmechanix.licenses.hystrix; import com.thoughtmechanix.licenses.utils.UserContext; import com.thoughtmechanix.licenses.utils.UserContextHolder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.Assert; import java.util.concurrent.Callable; public final class DelegatingUserContextCallable<V> implements Callable<V> { private static final Logger logger = LoggerFactory.getLogger(DelegatingUserContextCallable.class); private final Callable<V> delegate; //private final UserContext delegateUserContext; private UserContext originalUserContext; /** * 原始Callable类将被传递到自定义的Callable类,自定义Callable将调用Hystrix保护的代码和来自父线程的UserContent * @param delegate * @param userContext */ public DelegatingUserContextCallable(Callable<V> delegate, UserContext userContext) { Assert.notNull(delegate, "delegate cannot be null"); Assert.notNull(userContext, "userContext cannot be null"); this.delegate = delegate; this.originalUserContext = userContext; } public DelegatingUserContextCallable(Callable<V> delegate) { this(delegate, UserContextHolder.getContext()); } /** * call方法在被@HystriCommand注解保护的方法以前调用 * @return * @throws Exception */ public V call() throws Exception { /** * 已设置UserContent.存储UserContent的ThreadLocal变量与运行受Hystrix保护的方法的线程相关联 */ UserContextHolder.setContext( originalUserContext ); try { /** * UserContent设置后,在Hystrix保护的方法上调用call方法,如LicenseServer.getLicenseByOrg方法 */ return delegate.call(); } finally { this.originalUserContext = null; } } public String toString() { return delegate.toString(); } public static <V> Callable<V> create(Callable<V> delegate, UserContext userContext) { return new DelegatingUserContextCallable<V>(delegate, userContext); } }
3.配置Spring Cloud以使用自定义Hystrix策略
package com.thoughtmechanix.licenses.hystrix; import com.netflix.hystrix.strategy.HystrixPlugins; import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier; import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook; import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher; import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import javax.annotation.PostConstruct; @Configuration public class ThreadLocalConfiguration { /** * 当构造配置对象时,它将自动装配在现有的HystrixConcurrencyStrategy中 */ @Autowired(required = false) private HystrixConcurrencyStrategy existingConcurrencyStrategy; @PostConstruct public void init() { // Keeps references of existing Hystrix plugins. //由于要注册一个新的并发策略,因此要获取全部其余的Hystrix组件,而后从新设置Hystrix插件 HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance() .getEventNotifier(); HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance() .getMetricsPublisher(); HystrixPropertiesStrategy propertiesStrategy = HystrixPlugins.getInstance() .getPropertiesStrategy(); HystrixCommandExecutionHook commandExecutionHook = HystrixPlugins.getInstance() .getCommandExecutionHook(); HystrixPlugins.reset(); /** * 使用Hystrix插件注册自定义的Hystrix并发策略(ThreadConcurrencyStrategy) */ HystrixPlugins.getInstance().registerConcurrencyStrategy(new ThreadLocalAwareStrategy(existingConcurrencyStrategy)); /** * 而后从新注册Hystrix插件使用全部Hystrix插件 */ HystrixPlugins.getInstance().registerEventNotifier(eventNotifier); HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher); HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy); HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook); } }
//记住:Hystrix只容许有一个HystrixConcurrencyStrategy..spring将尝试自动装配在现有的任何HystrixConcurrencyStrategy(若是它存在)中。最后完成全部的工做后,咱们使用Hystrix插件把在init()方法获取的原始Hystrix组件从新注册回来。
1.在设计高分布式应用程序(如:基于微服务的应用程序)时,必须考虑客户端弹性。 2.服务的完全故障(如:服务器崩溃)是很容易检测和处理的。 3.一个性能不佳的服务可能会引发资源耗尽的连锁效应,由于调用客户端的线程被阻塞,以等待服务完成。 4.三种核心客户端弹性模式:断路器模式、后备模式、舱壁模式。 5.断路器模式试图杀死运行缓慢和降级的系统调用,这样的调用就会快速失败,并防止资源耗尽。 6.后备模式容许开发人员在远程服务调用失败或断路器跳匝的状况下,定义替代代码路径。 7.舱壁模式经过将对远程服务的调用隔离到他们的线程池中,使远程资源调用彼此分离。就算一组服务调用失败,这些失败也不会致使应用程序容器中的全部资源耗尽。 8.Spring cloud 和 Netflix Hystrix库提供断路器、后备模式、舱壁模式的实现。 9.Hystrix库是高度可配置的,能够在全局、类和线程池级别配置。 10.Hystrix支持两种隔离模型:THREAD和SEMAPHONRE。 11.Hystrix默认隔离模型THREAD彻底隔离Hystrix保护的调用,但不会将父线程的上下文传播到Hystrix管理的线程。 12.Hystrixd 另外一种隔离模型SEMAPHORE不使用单独的线程进行Hystrix调用。虽然这更有效率,但若是Hystrix中断了调用,它也会让服务变得不可预测。 13.Hystrix容许经过自定义HystrixConcurrencyStrategy实现,将线程上下文注入Hystrix管理的线程中。
像微服务架构这样的分布式架构中,须要确保跨多个服务调用的关键行为的正常运行,如安全、日志记录和用户跟踪。要实现此功能,开发人员须要在全部服务中始终如一的强制这些特性,而不要每一个开发团队都构建本身的解决方案。虽然可使用公共库或框架来帮助在单个服务中直接构建这些功能,但这样会形成如下影响: 1.在构建每一个服务中很难始终实现这些功能。 2.正确的实现这些功能是一个挑战。 3.这会在全部的服务中建立一个顽固的依赖。
使用Spring Cloud和Zuul来完成如下操做
1.将全部的服务调用放在一个URL后面,并使用服务发现将这些调用映射到实际的服务实例。 2.将关联ID注入流经服务网关的每一个服务调用中 3.在从客户端发回的HTTP响应中注入关联ID 4.构建一个动态路由机制,将各个具体的组织路由到服务实例端点,该端点与其余人使用的服务实例不一样。
无服务网关:服务客户端将为每一个服务调用不一样的端点。
有服务网关:服务网关分离调用的URL,并将路径映射到服务网关后面的服务。
静态路由------服务网关将全部的服务调用放在单个URL和API路由的后面。这简化了开发,由于开发人员只需知道全部的服务的一个服务端点就能够。 动态路由------服务网关能够检查传入的服务请求,根据来自传入请求的数据和服务调用者的身份执行智能路由。 验证和受权-----因为全部服务调用都要通过服务网关进行路由,因此服务网关是检查服务调用者是否已经进行了验证并被受权进行服务调用的天然场所。 度量数据收集和日志记录------当服务调用经过服务网关时,可使用服务网关来收集数据和日志信息,还可使用服务网关确保在用户请求上提供关键信息以确保日志统一。
这并不意味着不该该从单个服务中收集度量数据,而是经过服务网关能够集中收集许多基本度量数据,如服务调用次数和服务响应时间。
难道服务网关不是单点故障和潜在瓶颈吗?
在单独的服务组面前,负载均衡仍然颇有用,在这种状况下将负载均衡器放到多个服务网关实例前面的是一个恰当的设计,它确保服务网关实现能够伸缩。将负载均衡器置于全部服务实例的前面并非一个好主意,由于它将会成为瓶颈。
要保持服务网关编写的代码是无状态的。不要在内存中为服务网关存储任何信息。若是不当心就有可能限制网关的可伸缩性,致使不得不确保数据在全部服务网关实例中被复制。
要保持为服务网关编写的代码是轻量的。服务网关是服务调用“阻塞点”,具备多个数据库调用的复杂代码多是服务服务网关中难以追踪的性能问题的根源。
Spring cloud 和 Netfilx Zuul简介
Spring cloud 集成了Netflix 开源项目Zuul。Zuul是一个服务网关,它很是容易经过Spirng cloud注解进行建立和使用。Zuul提供了许多功能,具体包括如下: 1.将应用程序中的全部服务的路由映射到一个URL------Zuul不限于一个URL。在Zuul中,开发人员能够定义多个路由条目,使路由映射很是细粒度(每一个服务端点都有本身的路由映射)。而后Zuul最多见的用例是构建一个单一的入口点,全部服务客户端调用都要通过这个入口。
2.构建能够对经过网关的请求进行检查和操做的过滤器------这些过滤器容许开发人员在代码中注入策略执行点,以一致的方式对全部服务调用执行大量操做。
//1.创建一个Zuul Springboot项目,并配置适当的Maven依赖项 //2.使用Spring Cloud 注解修改这个Spring Boot 项目,将其声明为Zuul服务 //3.配置Zuul以便Eureka进行通讯(可选)
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> </dependency>
package com.thoughtmechanix.zuulsvr; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication @EnableZuulProxy //TODO:Zuul服务器 public class ZuulServerApplication { @LoadBalanced @Bean public RestTemplate getRestTemplate(){ return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(ZuulServerApplication.class, args); } }
注意:
@EnableZuulServer和EnableZuulProxy区别
EnableZuulServer:此注解将建立一个Zuul服务器,它不会加载任何Zuul反向代理过滤器,也不会使用Netflix Eureka进行服务发现。开发人员想要构建本身的路由服务,而不使用任何Zuul预置的功能时会使用@EnableZuulServer举例来说,
当开发人员须要使用Zuul与Eureka以外的其余服务发现引擎(如:Consul)进行集成的时候。
spring: application: name: zuulservice profiles: active: default cloud: config: enabled: true server: port: 5555 #Setting logging levels logging: level: com.netflix: WARN org.springframework.web: WARN com.thoughtmechanix: DEBUG eureka: instance: preferIpAddress: true client: registerWithEureka: true fetchRegistry: true serviceUrl: defaultZone: http://localhost:8761/eureka/ # # # debug: # request: true # #zuul: # prefix: /api # routes: # organizationservice: /organization/** # licensingservice: /licensing/**
Zuul的核心是一个反向代理。反向代理是一个中间件服务器,它位于尝试访问资源的客户端和资源自己之间。客户端甚至不知道它正在与代理以外的服务器进行通讯。反向代理负责捕获客户端的请求,而后表明客户端调用远程资源。在微服务架构的状况下,Zuul(反向代理)从客户端接收微服务调用并将其转发给下游服务。服务
客户端认为它只与Zuul通讯。Zuul要与下游服务进行沟通,Zuul必须知道如何将进来的调用映射到下游路由。Zuul有几种机制来作到这一点,包括:
1.经过服务发现自动映射路由
2.使用服务发现手动映射路由
3.使用静态URL手动映射路由
Zuul的全部映射都是经过application.yal文件中定义路由来完成的。可是,zuul能够根据其服务ID自动路由请求,而不须要配置。若是没有指定任何路由,zuul将自动使用正在调用的服务的Eureka服务ID,并将其映射到下游服务实例,
例如若是要调用organizationserice并经过Zuul使用自动路由,则可使用如下URL做为端点,让客户端调用Zuul服务实例: http://localhost:5555/organizationservice/v1/organizations/e254f8c-c442-4ebe-a82a-e2fc1d1ff78a
server: port: 5555 #Setting logging levels logging: level: com.netflix: WARN org.springframework.web: WARN com.thoughtmechanix: DEBUG eureka: instance: preferIpAddress: true client: registerWithEureka: true fetchRegistry: true serviceUrl: defaultZone: http://localhost:8761/eureka/ zuul: prefix: /api routes: organizationservice: /organization/**
使用Eureka的Zuul的优势在于,开发人员不只能够拥有一个能够发出调用的单个端点,有了Eureka开发人员还能够添加和删除服务的实例,而无需修改Zuul,例如能够向Eureka添加新服务,Zuul将自动路由到该服务,由于Zuul会与Eureka进行通讯,了解实际服务端点的位置。 访问:http://local-host:5555/routes
Zuul容许开发人员更细粒度的明肯定义路由映射,而不是单纯依赖服务的Eureka服务ID建立的自动路由。
Component | describe |
spring-cloud-aws | |
spring-cloud-bus | |
spring-cloud-cli | |
spring-cloud-commons | |
spring-cloud-contract | |
spring-cloud-config | |
spring-cloud-netflix | |
spring-cloud-security | |
spring-cloud-cloudfoundry | |
spring-cloud-consul | |
spring-cloud-sleuth | |
spring-cloud-stream | |
spring-cloud-zookeeper | |
spring-boot | |
spring-cloud-task | |
spring-cloud-vault | |
spring-cloud-gateway | |
spring-cloud-openfeign | |
spring-cloud-function | |
spring-cloud-function | |
spring-cloud-function | |
spring-cloud-function |