# springcloud **Repository Path**: fenghao1987/springcloud ## Basic Information - **Project Name**: springcloud - **Description**: springcloud学习代码 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2018-12-17 - **Last Updated**: 2020-12-20 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # SpringCloud微服务教程 ## 一、微服务概念介绍 ### 1.1 微服务架构优缺点 ​ 根据业务需求进行拆分成N个子系统,多个子系统相互协作才能完成业务流程子系统之间通讯使用RPC远程通讯技术。 **优点:** 1. 把模块拆分,使用接口通信,降低模块之间的耦合度。 2. 把项目拆分成若干个子项目,不同的团队负责不同的子项目。 3. 增加功能时只需要再增加一个子项目,调用其它系统的接口就可以。 4. 可以灵活的进行分布式部署。 **缺点:** 1. 系统之间交互需要使用远程通信,接口开发增加工作量。 2. 各个模块有一些通用的业务逻辑无法共用。 ### 1.2 SOA与微服务区别 ​ SOA架构主要针对企业级、采用ESB服务(ESB企业服务总线),非常重,需要序列化和反序列化,采用XML格式传输。 ​ 微服务架构主要互联网公司,轻量级、小巧,独立运行,基于Http+Rest+JSON格式传输。 ​ 首先SOA和微服务架构是一个层面的东西,而对于ESB和微服务网关是一个层面的东西,一个谈到是架构风格和方法,一个谈的是实现工具或组件。 ​ SOA(Service Oriented Architecture)“面向服务的架构”他是一种设计方法,其中包含多个服务, 服务之间通过相互依赖最终提供一系列的功能。一个服务 通常以独立的形式存在与操作系统进程中。各个服务之间 通过网络调用。 微服务架构其实和 SOA 架构类似,微服务是在 SOA 上做的升华,微服务架构强调的一个重点是“业务需要彻底的组件化和服务化”,原有的单个业务系统会拆分为多个可以独立开发、设计、运行的小应用。这些小应用之间通过服务完成交互和集成。 **ESB企业服务总线和微服务API网关:** 1. ESB(企业服务总线),简单 来说 ESB 就是一根管道,用来连接各个服务节点。为了集 成不同系统,不同协议的服务,ESB 做了消息的转化解释和路由工作,让不同的服务互联互通; ![img](https://img-blog.csdn.net/20180619101018799?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pwb2lzb24=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 2. API网关是一个服务器,是系统的唯一入口。从面向对象设计的角度看,它与外观模式类似。API网关封装了系统内部架构,为每个客户端提供一个定制的API。它可能还具有其它职责,如身份验证、监控、负载均衡、缓存、请求分片与管理、静态响应处理。API网关方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。通常,网关也是提供REST/HTTP的访问API。服务端通过API-GW注册和管理服务。 ![img](https://img-blog.csdn.net/20180619101145266?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3pwb2lzb24=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) **区别对比:** | 功能 | SOA | 微服务 | | -------- | -------------------- | ---------------------------- | | 组件大小 | 大块业务逻辑 | 单独任务或小块业务逻辑 | | 耦合 | 通常松耦合 | 总是松耦合 | | 公司架构 | 任何类型 | 小型、专注于功能交叉团队 | | 管理 | 着重中央管理 | 着重分散管理 | | 目标 | 确保应用能够交互操作 | 执行新功能、快速拓展开发团队 | ## 二、SpringCloud介绍 ### 2.1 springcloud是什么 ​ SpringCloud,**基于SpringBoot提供了一套微服务解决方案**,包括**服务注册与发现,配置中心,全链路监控,服务网关,负载均衡,熔断器**等组件,除了基于**NetFlix**的开源组件做高度抽象封装之外,还有一些选型中立的开源组件。 ​ SpringCloud是关注全局的微服务协调整理治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来,为各个微服务之间提供,**配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话**等等集成服务。 ​ **SpringBoot可以离开SpringCloud独立使用开发项目,但是SpringCloud离不开SpringBoot,属于依赖的关系。** ![img](http://spring.io/img/homepage/diagram-distributed-systems.svg) ### 2.2 springcloud与dubbo的区别 **最大区别:SpringCloud抛弃了Dubbo的RPC通信,采用的是基于HTTP的REST方式** ​ 严格来说,这两种方式各有优劣。虽然从一定程度上来说,后者牺牲了服务调用的性能,但也避免了上面提到的原生RPC带来的问题。而且REST相比RPC更为灵活,服务提供方和调用方的依赖只依靠一纸契约,不存在代码级别的强依赖,这在强调快速演化的微服务环境下,显得更加合适。 **品牌机与组装机的区别** ​ 很明显,Spring Cloud的功能比DUBBO更加强大,涵盖面更广,而且作为Spring的拳头项目,它也能够与Spring Framework、Spring Boot、Spring Data、Spring Batch等其他Spring项目完美融合,这些对于微服务而言是至关重要的。使用Dubbo构建的微服务架构就像组装电脑,各环节我们的选择自由度很高,但是最终结果很有可能因为一条内存质量不行就点不亮了,总是让人不怎么放心,但是如果你是一名高手,那这些都不是问题;而Spring Cloud就像品牌机,在Spring Source的整合下,做了大量的兼容性测试,保证了机器拥有更高的稳定性,但是如果要在使用非原装组件外的东西,就需要对其基础有足够的了解。 **社区支持与更新力度**(dubbo已成为Apache的顶级项目,后期市场潜力也是有的) ​ 最为重要的是,DUBBO停止了5年左右的更新,虽然2017.7重启了。对于技术发展的新需求,需要由开发者自行拓展升级(比如当当网弄出了DubboX),这对于很多想要采用微服务架构的中小软件组织,显然是不太合适的,中小公司没有这么强大的技术能力去修改Dubbo源码+周边的一整套解决方案,并不是每一个公司都有阿里的大牛+真实的线上生产环境测试过。 ### 2.3 springcloud参考文档 - 官方网站:http://projects.spring.io/spring-cloud/ - 中文API:https://springcloud.cc/ - 中文社区:http://springcloud.cn/ springboot与springcloud版本对应关系: | springcloud Version | springboot Version | | ------------------- | ------------------ | | Greenwich | 2.1.x | | Finchley | 2.0.x | | Edgware | 1.5.x | | Dalston | 1.5.x | 本次使用的版本说明:**springboot 2.0.7.RELEASE + springcloud Finchley.SR2** ### 2.4 教学大纲 1. Rest微服务构建案例工程模块(maven+rest远程调用) 2. 服务的注册与发现(Eureka) 3. 负载均衡(rest+Ribbon) 4. 面向接口的服务调用(Feign) 5. 断路器(Hystrix) 6. 断路器监控(Hystrix Dashboard) 7. 路由网关(Zuul) 8. 分布式配置中心(Spring Cloud Config) ## 三、Rest微服务构建案例工程模块 ### 3.1 构建说明 - springcloud:所有微服务的父工程(Project),下带着3个子模块(Module) - springcloud-common:封装的整体entity/接口/公共配置等 - springcloud-provider-8001:微服务的提供者 - springcloud-consumer-80:微服务的客户端消费者 ### 3.2 构建步骤 #### 3.2.1 创建springcloud父工程 1、新建父工程springcloud,切记工程的packaging是pom模式 2、修改pom文件 ```xml 4.0.0 org.springframework.boot spring-boot-starter-parent 2.0.7.RELEASE com.pwser.springcloud springcloud-parent 0.0.1-SNAPSHOT pom springcloud微服务-父工程 UTF-8 1.8 1.8 Finchley.SR2 1.1.0 org.springframework.cloud spring-cloud-dependencies ${springcloud.version} pom import com.alibaba druid ${druid.version} ${project.artifactId} org.springframework.boot spring-boot-maven-plugin ``` #### 3.2.2 创建springcloud-common公共模块 1、新建springcloud-common,创建完成后请回到父工程查看pom文件变化,如果父工程pom没有变化则手动添加以下内容 ```xml springcloud-common ``` 2、修改pom文件,内容如下: ```xml 4.0.0 com.pwser.springcloud springcloud-parent 0.0.1-SNAPSHOT springcloud-common springcloud微服务-公共模块 org.projectlombok lombok ``` 3、新建部门Entity且配合lombok使用 ```java package com.pwser.common.entities; import java.io.Serializable; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; @SuppressWarnings("serial") @NoArgsConstructor @AllArgsConstructor @Data @Accessors(chain = true) public class Dept implements Serializable { private Long deptNo; // 主键 private String deptName; // 部门名称 private String dbSource;// 来自那个数据库,因为微服务架构可以一个服务对应一个数据库,同一个信息被存储到不同数据库 } ``` 4、mvn clean install后给其它模块引用,达到通用目的 #### 3.2.3 创建springcloud-provider-8001微服务的提供者模块 1、新建springcloud-provider-8001,创建完成后请回到父工程查看pom文件变化 2、修改pom文件 ```xml 4.0.0 com.pwser.springcloud springcloud-parent 0.0.1-SNAPSHOT springcloud-provider-8001 springcloud微服务-生产者服务端模块 com.pwser.springcloud springcloud-common ${project.version} org.springframework.boot spring-boot-starter-web org.mybatis.spring.boot mybatis-spring-boot-starter 1.3.2 org.springframework.boot spring-boot-devtools runtime mysql mysql-connector-java com.alibaba druid org.springframework.boot spring-boot-starter-test test ${project.artifactId} org.springframework.boot spring-boot-maven-plugin ``` 3、修改yml配置文件 ```yaml server: port: 8001 mybatis: config-location: classpath:mybatis/mybatis.cfg.xml # mybatis配置文件所在路径 type-aliases-package: com.pwser.common.entities # 所有Entity别名类所在包 mapper-locations: - classpath:mybatis/mapper/**/*.xml # mapper映射文件 spring: application: name: springcloud-provider datasource: type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型 driver-class-name: com.mysql.jdbc.Driver # mysql驱动包 url: jdbc:mysql://localhost:3306/clouddb01?useUnicode=true&characterEncoding=UTF-8 # 数据库名称 username: root password: admin dbcp2: min-idle: 5 # 数据库连接池的最小维持连接数 initial-size: 5 # 初始化连接数 max-total: 5 # 最大连接数 max-wait-millis: 200 # 等待连接获取的最大超时时间 logging: level: com: pwser: provider: mapper: debug ``` 4、工程src/main/resources目录下新建mybatis文件夹后新建mybatis.cfg.xml文件 ```xml ``` 5、MySQL创建部门数据库脚本 ```sql CREATE TABLE `dept` ( `dept_no` bigint(20) NOT NULL AUTO_INCREMENT, `dept_name` varchar(60) DEFAULT NULL, `db_source` varchar(60) DEFAULT NULL, PRIMARY KEY (`dept_no`) ) DEFAULT CHARSET=utf8; INSERT INTO `dept` (dept_name, db_source) VALUES ('运检部', database()); INSERT INTO `dept` (dept_name, db_source) VALUES ('营销部', database()); INSERT INTO `dept` (dept_name, db_source) VALUES ('发策部', database()); INSERT INTO `dept` (dept_name, db_source) VALUES ('建设部', database()); INSERT INTO `dept` (dept_name, db_source) VALUES ('财务部', database()); ``` 6、DeptMapper部门接口 ```java package com.pwser.provider.mapper; import java.util.List; import org.apache.ibatis.annotations.Mapper; import com.pwser.common.entities.Dept; @Mapper public interface DeptMapper { public boolean addDept(Dept dept); public Dept findById(Long id); public List findAll(); } ``` 7、工程src/main/resources/mybatis目录下新建mapper文件夹后再建DeptMapper.xml ```xml INSERT INTO dept (dept_name, db_source) VALUES (#{deptName}, DATABASE()); ``` 8、DeptService部门服务层 ```java package com.pwser.provider.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.pwser.common.entities.Dept; import com.pwser.provider.mapper.DeptMapper; @Service public class DeptService { @Autowired private DeptMapper deptMapper; public boolean add(Dept dept) { return deptMapper.addDept(dept); } public Dept get(Long id) { return deptMapper.findById(id); } public List list() { return deptMapper.findAll(); } } ``` 9、DeptController部门微服务提供者REST ```java package com.pwser.provider.controller; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import com.pwser.common.entities.Dept; import com.pwser.provider.service.DeptService; @RestController public class DeptController { @Autowired private DeptService deptService; @Autowired private DiscoveryClient discoveryClient; @PostMapping(value = "/dept/add") public boolean add(@RequestBody Dept dept) { return deptService.add(dept); } @GetMapping(value = "/dept/get/{id}") public Dept get(@PathVariable("id") Long id) { return deptService.get(id); } @GetMapping(value = "/dept/list") public List list() { return deptService.list(); } } ``` 10、SpringcloudProvider8001Application主启动类 ```java package com.pwser.provider; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SpringcloudProvider8001Application { public static void main(String[] args) { SpringApplication.run(SpringcloudProvider8001Application.class, args); } } ``` 11、测试 http://localhost:8001/dept/get/2 http://localhost:8001/dept/list #### 3.2.4 创建springcloud-consumer-80微服务的消费者模块 1、新建springcloud-consumer-80,创建完成后请回到父工程查看pom文件变化 2、修改pom文件 ```xml 4.0.0 com.pwser.springcloud springcloud-parent 0.0.1-SNAPSHOT springcloud-consumer-80 springcloud微服务-消费者客户端模块 com.pwser.springcloud springcloud-common ${project.version} org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-devtools runtime org.springframework.boot spring-boot-starter-test test ${project.artifactId} org.springframework.boot spring-boot-maven-plugin ``` 3、修改yml文件 ```yaml server: port: 80 spring: application: name: springcloud-consumer ``` 4、新建restTemplate配置类 ```java package com.pwser.consumer.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.RetryRule; @Configuration public class RestTemplateRibbonConfig { @Bean public RestTemplate getRestTemplate() { return new RestTemplate(); } } ``` 5、ConsumerController部门微服务消费者 ```java package com.pwser.consumer.controller; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import com.pwser.common.entities.Dept; @RestController public class ConsumerController { private static final String REST_URL_PREFIX = "http://localhost:8001"; /** * 使用 使用restTemplate访问restful接口非常的简单粗暴无脑。 (url, requestMap, * ResponseBean.class)这三个参数分别代表 REST请求地址、请求参数、HTTP响应转换被转换成的对象类型。 */ @Autowired private RestTemplate restTemplate; @GetMapping(value = "/consumer/dept/add") public boolean add(Dept dept) { return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept, Boolean.class); } @GetMapping(value = "/consumer/dept/get/{id}") public Dept get(@PathVariable("id") Long id) { return restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/" + id, Dept.class); } @SuppressWarnings("unchecked") @GetMapping(value = "/consumer/dept/list") public List list() { return restTemplate.getForObject(REST_URL_PREFIX + "/dept/list", List.class); } } ``` 6、SpringcloudConsumer80Application主启动类 ```java package com.pwser.consumer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SpringcloudConsumer80Application { public static void main(String[] args) { SpringApplication.run(SpringcloudConsumer80Application.class, args); } } ``` 7、测试 http://localhost/consumer/dept/get/2 http://localhost/consumer/dept/list http://localhost/consumer/dept/add?deptName=调控中心 ## 四、Eureka服务注册与发现 ### 4.1 Eureka的基本架构 ​ Spring Cloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务注册和发现(请对比Zookeeper)。Eureka 采用了 C-S 的设计架构。Eureka Server 作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用 Eureka 的客户端连接到 Eureka Server并维持心跳连接。这样系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行。SpringCloud 的一些其他模块(比如Zuul)就可以通过 Eureka Server 来发现系统中的其他微服务,并执行相关的逻辑。 **Eureka包含两个组件:Eureka Server和Eureka Client** ​ Eureka Server提供服务注册服务,各个节点启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。 ​ EurekaClient是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒) ![img](https://img-blog.csdn.net/20170828120329859?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcnViZW45NTAwMQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) ### 4.2 Eureka与Zookeeper对比 ​ 作为服务注册中心,Eureka比Zookeeper好在哪里?著名的CAP理论指出,一个分布式系统不可能同时满足C(一致性)、A(可用性)和P(分区容错性)。由于分区容错性P在是分布式系统中必须要保证的,因此我们只能在A和C之间进行权衡。 **Zookeeper保证CP** ​ 当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接down掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。但是zk会出现这样一种情况,当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长,30 ~ 120s, 且**选举期间整个zk集群都是不可用的**,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得zk集群失去master节点是较大概率会发生的事,虽然服务能够最终恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。 **Eureka保证AP** ​ Eureka看明白了这一点,因此在设计时就优先保证可用性。**Eureka各个节点都是平等的**,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册或时如果发现连接失败,则会自动切换至其它节点,**只要有一台Eureka还在,就能保证注册服务可用(保证可用性)**,只不过查到的信息可能不是最新的(不保证强一致性)。除此之外,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况: **(自我保护机制)** 1. Eureka不再从注册列表中移除因为长时间没收到心跳而应该过期的服务 2. Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用) 3. 当网络稳定时,当前实例新的注册信息会被同步到其它节点中 ​ **因此, Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像zookeeper那样使整个注册服务瘫痪。** ### 4.3 构建步骤 #### 4.3.1 springcloud-eureka-7001服务注册中心Module 1、新建springcloud-eureka-7001模块,,创建完成后请回到父工程查看pom文件变化 2、修改pom文件,注意springboot+springcloud版本的差别,依赖有所区别 ```xml org.springframework.cloud spring-cloud-starter-eureka-server org.springframework.cloud spring-cloud-starter-netflix-eureka-server ``` 完整的pom文件如下: ```xml 4.0.0 com.pwser.springcloud springcloud-parent 0.0.1-SNAPSHOT springcloud-eureka-7001 springcloud微服务-服务注册中心模块 org.springframework.cloud spring-cloud-starter-netflix-eureka-server org.springframework.boot spring-boot-devtools runtime org.springframework.boot spring-boot-starter-test test ${project.artifactId} org.springframework.boot spring-boot-maven-plugin ``` 3、修改yml文件 ```yaml server: port: 7001 spring: application: name: springcloud-eureka eureka: instance: hostname: localhost #eureka服务端的实例名称 client: register-with-eureka: false #false表示不向注册中心注册自己。 fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务 service-url: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址(单机)。 #集群 defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ ``` 4、SpringcloudEureka7001Application主启动类 ```java package com.pwser.eureka; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication @EnableEurekaServer public class SpringcloudEureka7001Application { public static void main(String[] args) { SpringApplication.run(SpringcloudEureka7001Application.class, args); } } ``` 5、测试 http://localhost:7001/ #### 4.3.2 springcloud-provider-8001生产者注册eureka 1、修改springcloud-provider-8001 2、修改pom文件,增加eureka客户端依赖 ```xml org.springframework.cloud spring-cloud-starter-netflix-eureka-client ``` 3、修改yml文件,增加eureka配置 ```yaml eureka: client: #客户端注册进eureka服务列表内 service-url: defaultZone: http://localhost:7001/eureka/ #defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ ``` 4、主启动类添加注解支持 ```java @EnableEurekaClient //服务注册 ``` 5、测试 先启动springcloud-eureka-7001,再启动springcloud-provider-8001,访问http://localhost:7001/ #### 4.3.3 actuator与注册微服务信息完善 1、注册中心页面显示服务格式为`微服务名称-IP-port`,修改springcloud-provider-8001的yml文件 ```yaml instance: ip-address: 192.168.0.222 instance-id: ${spring.application.name}-${eureka.instance.ip-address}:${server.port} ``` 2、访问显示真实IP地址,修改springcloud-provider-8001的yml文件 ```yaml prefer-ip-address: true #访问路径可以显示IP地址 ``` 3、微服务info内容详细信息,当前问题:点击微服务超链接报ErrorPage错误 1)、springcloud-provider-8001引入actuator依赖 ```xml org.springframework.boot spring-boot-starter-actuator ``` 2)、修改父工程springcloud的pom文件,添加maven插件支持 ```xml ${project.artifactId} src/main/resources true org.springframework.boot spring-boot-maven-plugin org.apache.maven.plugins maven-resources-plugin @ ``` 3)、修改springcloud-provider-8001的yml文件 ```yaml #开启actuator监控 management: endpoints: web: exposure: include: "*" #开放所有页面节点 默认只开启了health、info两个节点 endpoint: health: show-details: always #显示健康具体信息默认不会显示详细信息 info: app.name: pwser-springcloud company.name: www.pwser.com build.artifactId: @project.artifactId@ build.version: @project.version@ ``` #### 4.3.4 eureka自我保护 ![img](http://images2018.cnblogs.com/blog/435188/201804/435188-20180420125523201-1838137796.png) 一句话:某时刻某一个微服务不可用了,eureka不会立刻清理,依旧会对该微服务的信息进行保存,eureka是AP机制。 解决方式有三种: - 关闭自我保护模式(`eureka.server.enable-self-preservation`设为`false`),**不推荐**。 - 降低`renewalPercentThreshold`的比例(`eureka.server.renewal-percent-threshold`设置为`0.5`以下,比如`0.49`),**不推荐**。 - 部署多个 Eureka Server 并开启其客户端行为(`eureka.client.register-with-eureka`不要设为`false`,默认为`true`),**推荐**。 ​ Eureka 的自我保护模式是有意义的,该模式被激活后,它不会从注册列表中剔除因长时间没收到心跳导致租期过期的服务,而是等待修复,直到心跳恢复正常之后,它自动退出自我保护模式。这种模式旨在避免因网络分区故障导致服务不可用的问题。例如,两个客户端实例 C1 和 C2 的连通性是良好的,但是由于网络故障,C2 未能及时向 Eureka 发送心跳续约,这时候 Eureka 不能简单的将 C2 从注册表中剔除。因为如果剔除了,C1 就无法从 Eureka 服务器中获取 C2 注册的服务,但是这时候 C2 服务是可用的。所以,Eureka 的自我保护模式最好还是开启它。 eureka配置: ```yaml eureka: instance: hostname: localhost server: enable-self-preservation: false #关闭自我保护机制 eviction-interval-timer-in-ms: 3000 #检查服务失效时间 ``` 微服务配置: ```yaml # 默认90秒 lease-expiration-duration-in-seconds: 10 # 默认30秒 lease-renewal-interval-in-seconds: 3 ``` #### 4.3.5 eureka服务发现 对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息。 1、修改springcloud-provider-8001模块的DeptController,增加服务发现rest请求 ```java @GetMapping(value = "/dept/discovery") public List discovery() { List list = discoveryClient.getServices(); System.out.println(list); return list; } @GetMapping(value = "/dept/discovery/{serviceId}") public List getServiceById(@PathVariable("serviceId") String serviceId) { List list = discoveryClient.getInstances(serviceId); for (ServiceInstance element : list) { System.out.println(element.getServiceId() + "\t" + element.getHost() + "\t" + element.getPort() + "\t" + element.getUri()); } return list; } ``` 2、springcloud-provider-8001主启动类添加注解支持 ```java @EnableDiscoveryClient ``` 3、修改springcloud-consumer-80模块的ConsumerController,增加服务发现rest请求 ```java @SuppressWarnings("unchecked") // 测试@EnableDiscoveryClient,消费端可以调用服务发现 @GetMapping(value = "/consumer/dept/discovery") public List discovery() { return restTemplate.getForObject(REST_URL_PREFIX + "/dept/discovery", List.class); } @SuppressWarnings("unchecked") @GetMapping(value = "/consumer/dept/discovery/{serviceId}") public List getServiceById(@PathVariable("serviceId") String serviceId) { return restTemplate.getForObject(REST_URL_PREFIX + "/dept/discovery/" + serviceId, List.class); } ``` 4、测试 先启动springcloud-eureka-7001,再启动springcloud-provider-8001和springcloud-consumer-80,稍等一会再访问http://localhost/consumer/dept/discovery和http://localhost/consumer/dept/discovery/springcloud-provider #### 4.3.6 eureka集群配置 1、根据springcloud-eureka-7001复制新建springcloud-eureka-7002、springcloud-eureka-7003 2、分别修改pom文件 ```yaml #7001 defaultZone: http://localhost:7002/eureka/,http://localhost:7003/eureka/ #7002 defaultZone: http://localhost:7001/eureka/,http://localhost:7003/eureka/ #7003 defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/ ``` 3、springcloud-provider-8001修改yml文件 ```yaml eureka:   client: #客户端注册进eureka服务列表内     service-url:        defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/,http://localhost:7003/eureka/ ``` ## 五、Ribbon负载均衡 ### 5.1 Ribbon简介 ​ Spring Cloud Ribbon是基于Netflix Ribbon实现的一套**客户端负载均衡**的工具。简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法。 Ribbon在工作时分成两步 ​ 第一步先选择 EurekaServer ,它优先选择在同一个区域内负载较少的server。 ​ 第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。 ​ 其中Ribbon提供了多种策略:比如轮询、随机和根据响应时间加权。 ### 5.2 Load Balancer(负载均衡) 1、集中式LB ​ 即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5, 也可以是软件,如nginx), 由该设施负责把访问请求通过某种策略转发至服务的提供方; 2、进程内LB ​ 将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。 ### 5.3 构建步骤 #### 5.3.1 springcloud-consumer-80客户端Ribbon初步配置 1、修改springcloud-consumer-80模块,修改pom文件 ```xml org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.cloud spring-cloud-starter-netflix-ribbon ``` 2、修改yml文件,追加eureka的服务注册地址 ```yaml eureka: client: register-with-eureka: false service-url: defaultZone: http://localhost:7001/eureka/ instance: ip-address: 192.168.0.222 instance-id: ${spring.application.name}-${eureka.instance.ip-address}:${server.port} prefer-ip-address: true ``` 3、RestTemplateRibbonConfig在restTemplate bean上增加@LoadBalanced注解 4、主启动类添加@EnableEurekaClient注解 5、修改ConsumerController,将原ip地址常量修改为微服务名称 ```java private static final String REST_URL_PREFIX = "http://springcloud-provider"; //使用Ribbon后可使用微服务app实例名称调用服务 ``` 6、测试 先启动springcloud-eureka-7001,再启动springcloud-provider-8001和springcloud-consumer-80,稍等一会再访问http://localhost/consumer/dept/get/1和http://localhost/consumer/dept/list **小结:Consumer和Eureka整合后可以直接用微服务名称调用服务而不用再关心地址和端口号** #### 5.3.2 多生产者服务配置 1、参考springcloud-provider-8001,新建另外两份生产者服务,分别命名为8002,8003 2、新建8002/8003数据库,各自微服务分别连各自的数据库 3、修改8002/8003各自yml文件,**特别注意8001、8002、8003必须对外暴露的统一的服务实例名** 4、测试 先启动springcloud-eureka-7001,再启动springcloud-provider-8001、springcloud-provider-8002、springcloud-provider-8003、springcloud-consumer-80,稍等一会再访问http://localhost/consumer/dept/get/1和http://localhost/consumer/dept/list,注意观察看到返回的数据库名字,各不相同,负载均衡实现 **总结:Ribbon其实就是一个软负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。** ![img](https://img-blog.csdn.net/20170411163955182?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvSmVzb24wNzI1/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) ### 5.4 Ribbon核心组件IRule IRule接口:根据特定算法中从服务列表中选取一个要访问的服务 - **RoundRobinRule:**轮询,默认规则 - **RandomRule:**随机 - **AvailabilityFilteringRule:**会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问 - **WeightedResponseTimeRule:**根据平均响应时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越高。刚启动时如果统计信息不足,则使用RoundRobinRule策略,等统计信息足够,会切换到WeightedResponseTimeRule - **RetryRule:**先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务 - **BestAvailableRule:**会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务 - **ZoneAvoidanceRule:**复合判断server所在区域的性能和server的可用性选择服务器 更换默认轮询算法 ```java @Bean public IRule getMyRule() { //return new RandomRule(); //同随机算法替换原有的轮询算法 return new RetryRule(); //如果某个微服务访问不通,重试几次后(大约5-10次)将不再调用该服务 } ``` ### 5.5 自定义Ribbon算法 1、修改springcloud-consumer-80,主启动类添加@RibbonClient ```java @RibbonClient(name = "springcloud-provider", configuration = ProviderRuleConfig.class) ``` 2、新建com.pwser.rule包,再新建ProviderRuleConfig类 > https://cloud.spring.io/spring-cloud-static/Finchley.SR2/single/spring-cloud.html#spring-cloud-ribbon 官方文档明确给出了警告: > 这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,也就是说我们达不到特殊化定制的目的了。 ```java package com.pwser.rule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.netflix.loadbalancer.IRule; @Configuration public class ProviderRuleConfig { @Bean public IRule getMyRule() { return new ProviderRule(); } } ``` 3、解析源码:https://github.com/Netflix/ribbon/blob/master/ribbon-loadbalancer/src/main/java/com/netflix/loadbalancer/RandomRule.java,根据随机算法修改算法,新建ProviderRule.java,粘贴RandomRule.java源码,改成一个服务被访问5次后切换其他服务的算法 ```java package com.pwser.rule; import java.util.List; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; public class ProviderRule extends AbstractLoadBalancerRule { // total = 0 // 当total==5以后,我们指针才能往下走, // index = 0 // 当前对外提供服务的服务器地址, // total需要重新置为零,但是已经达到过一个5次,我们的index = 1 // 分析:我们5次,但是微服务只有8001 8002 8003 三台,OK? private int total = 0; // 总共被调用的次数,目前要求每台被调用5次 private int currentIndex = 0; // 当前提供服务的机器号 public Server choose(ILoadBalancer lb, Object key) { if (lb == null) { return null; } Server server = null; while (server == null) { if (Thread.interrupted()) { return null; } List upList = lb.getReachableServers(); List allList = lb.getAllServers(); int serverCount = allList.size(); if (serverCount == 0) { /* * No servers. End regardless of pass, because subsequent passes only get more * restrictive. */ return null; } // int index = rand.nextInt(serverCount);// java.util.Random().nextInt(3); // server = upList.get(index); // private int total = 0; // 总共被调用的次数,目前要求每台被调用5次 // private int currentIndex = 0; // 当前提供服务的机器号 if (total < 5) { server = upList.get(currentIndex); total++; } else { total = 0; currentIndex++; if (currentIndex >= upList.size()) { currentIndex = 0; } } if (server == null) { /* * The only time this should happen is if the server list were somehow trimmed. * This is a transient condition. Retry after yielding. */ Thread.yield(); continue; } if (server.isAlive()) { return (server); } // Shouldn't actually happen.. but must be transient or a bug. server = null; Thread.yield(); } return server; } @Override public Server choose(Object key) { return choose(getLoadBalancer(), key); } @Override public void initWithNiwsConfig(IClientConfig clientConfig) { } } ``` 4、参照springcloud-provider-8001新建springcloud-provider-8004、springcloud-provider-8005,修改yml文件,重点修改服务实例名称,使8004和8005组成新的服务集群 ```yaml spring: application: name: springcloud-pro ``` 5、修改ConsumerController,增加调用8004和8005的rest请求 ```java @SuppressWarnings("unchecked") @GetMapping(value = "/consumer/pro/list") public List listPro() { return restTemplate.getForObject("http://springcloud-pro/dept/list", List.class); } ``` 5、测试 先启动springcloud-eureka-7001,再启动springcloud-provider-8001、springcloud-provider-8002、springcloud-provider-8003、springcloud-provider-8004、springcloud-provider-8005、springcloud-consumer-80,稍等一会再访问http://localhost/consumer/dept/list和http://localhost/consumer/pro/list,8001、8002、8003应用了5次切换算法,而8004和8005还是应用原有轮询算法 ## 六、Feign面向接口的服务调用 ### 6.1 Feign简介 ​ Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单, 它的使用方法是定义一个接口,然后在上面添加注解,同时也支持JAX-RS标准的注解。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。 Feign是一个声明式的Web服务客户端,使得编写Web服务客户端变得非常容易,**只需要创建一个接口,然后在上面添加注解即可**。 ​ Feign旨在使编写Java Http客户端变得更容易。前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。 ​ Feign集成了Ribbon,利用Ribbon维护了MicroServiceCloud-Dept的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。 ### 6.2 构建步骤 1、参考springcloud-consumer-80,新建springcloud-consumer-feign-80,查看父工程的pom文件变化 2、修改pom文件,增加feign依赖 ```xml 4.0.0 com.pwser.springcloud springcloud-parent 0.0.1-SNAPSHOT springcloud-consumer-feign-80 springcloud微服务-消费者feign客户端模块 org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.cloud spring-cloud-starter-openfeign com.pwser.springcloud springcloud-common ${project.version} org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-devtools runtime org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-starter-actuator ${project.artifactId} org.springframework.boot spring-boot-maven-plugin ``` 3、修改springcloud-common模块的pom文件 ```xml org.springframework.cloud spring-cloud-starter-openfeign ``` 4、在springcloud-common中新建DeptServiceInterf接口,mvn clean,mvn install ```java package com.pwser.common.interf; import java.util.List; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import com.pwser.common.entities.Dept; @FeignClient(name = "springcloud-provider") public interface DeptServiceInterf { @PostMapping(value = "/dept/add") public boolean add(Dept dept); @GetMapping(value = "/dept/get/{id}") public Dept get(@PathVariable("id") Long id); @GetMapping(value = "/dept/list") public List list(); @GetMapping(value = "/dept/discovery") public List discovery(); @GetMapping(value = "/dept/discovery/{serviceId}") public List getServiceById(@PathVariable("serviceId") String serviceId); @GetMapping(value = "/dept/avalanche") public List avalanche() throws InterruptedException; } ``` 5、springcloud-consumer-feign-80修改ConsumerController,注入上一步新建的DeptClientService接口 ```java package com.pwser.consumer.controller; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import com.pwser.common.entities.Dept; import com.pwser.common.interf.DeptServiceInterf; @RestController public class ConsumerController { @Autowired private DeptServiceInterf deptService; @GetMapping(value = "/feign/dept/add") public boolean add(Dept dept) { return deptService.add(dept); } @GetMapping(value = "/feign/dept/get/{id}") public Dept get(@PathVariable("id") Long id) { return deptService.get(id); } @GetMapping(value = "/feign/dept/list") public List list() { return deptService.list(); } @GetMapping(value = "/feign/dept/discovery") public List discovery() { return deptService.discovery(); } @GetMapping(value = "/feign/dept/discovery/{serviceId}") public List getServiceById(@PathVariable("serviceId") String serviceId) { return deptService.getServiceById(serviceId); } } ``` 6、springcloud-consumer-feign-80修改主启动类,增加@EnableFeignClients和@ComponentScan注解 ```java package com.pwser.consumer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.ribbon.RibbonClient; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.ComponentScan; import com.pwser.rule.ProviderRuleConfig; @SpringBootApplication @EnableEurekaClient @RibbonClient(name = "springcloud-provider", configuration = ProviderRuleConfig.class) @EnableFeignClients(basePackages = { "com.pwser.common.interf" }) // 指定feign接口所在的包路径 @ComponentScan(basePackages = { "com.pwser.common", "com.pwser.consumer" }) public class SpringcloudConsumerFeign80Application { public static void main(String[] args) { SpringApplication.run(SpringcloudConsumerFeign80Application.class, args); } } ``` 7、测试 先启动springcloud-eureka-7001,再启动springcloud-provider-8001、springcloud-provider-8002、springcloud-provider-8003、springcloud-consumer-feign-80,稍等一会再访问http://localhost/feign/dept/list,8001、8002、8003应用了5次切换的负载均衡算法 **总结:Feign通过接口的方法调用Rest服务(之前是Ribbon+RestTemplate),该请求发送给Eureka服务器(http://springcloud-provider/dept/list),通过Feign直接找到服务接口,由于在进行服务调用的时候融合了Ribbon技术,所以也支持负载均衡作用。** ## 七、Hystrix断路器 ### 7.1 分布式系统面临的问题 ​ 复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的**“扇出”**。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的**“雪崩效应”**。 ![img](https://upload-images.jianshu.io/upload_images/2833665-6bdde163c19b8aa8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/640/format/webp) ![img](https://upload-images.jianshu.io/upload_images/2833665-c937110515b049a5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/640/format/webp) 一般情况对于服务依赖的保护主要有3中解决方案: **(1)熔断模式:**这种模式主要是参考电路熔断,如果一条线路电压过高,保险丝会熔断,防止火灾。放到我们的系统中,如果某个目标服务调用慢或者有大量超时,此时,熔断该服务的调用,对于后续调用请求,不在继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。 **(2)隔离模式:**这种模式就像对系统请求按类型划分成一个个小岛的一样,当某个小岛被火少光了,不会影响到其他的小岛。例如可以对不同类型的请求使用线程池来资源隔离,每种类型的请求互不影响,如果一种类型的请求线程资源耗尽,则对后续的该类型请求直接返回,不再调用后续资源。这种模式使用场景非常多,例如将一个服务拆开,对于重要的服务使用单独服务器来部署。 **(3)限流模式:**上述的熔断模式和隔离模式都属于出错后的容错处理机制,而限流模式则可以称为预防模式。限流模式主要是提前对各个类型的请求设置最高的QPS阈值,若高于设置的阈值则对该请求直接返回,不再调用后续资源。这种模式不能解决服务依赖的问题,只能解决系统整体资源分配问题,因为没有被限流的请求依然有可能造成雪崩效应。 ### 7.2 服务雪崩效应演示 1、修改springcloud-provider-8001的DeptController,增加测试方法,模拟方法线程停止3秒 ```java @GetMapping(value = "/dept/avalanche") public List avalanche() throws InterruptedException { Thread.sleep(3000); return deptService.list(); } ``` 2、修改springcloud-common接口,增加方法 ```java @GetMapping(value = "/dept/avalanche") public List avalanche() throws InterruptedException; ``` 3、修改springcloud-consumer-feign-80的ConsumerController,增加测试方法 ```java @GetMapping(value = "/feign/dept/avalanche") public List avalanche() throws InterruptedException { return deptService.avalanche(); } @GetMapping(value = "/feign/dept/info") public String getInfo() { return "服务雪崩效应缓解了..."; } ``` 4、修改springcloud-consumer-feign-80的yml文件,将tomcat最大线程数设置为50,feign超时时间设置为10s ```yaml server: port: 80 tomcat: max-threads: 50 #tomcat最大线程数 #开启hystrix feign: hystrix: enabled: false client: config: default: connectTimeout: 10000 #设置feign默认远程访问的超时间,与下面配置必须同时配置 readTimeout: 10000 #设置feign默认远程访问的超时间,默认1s hystrix: command: default: execution: timeout: enabled: true isolation: thread: timeoutInMilliseconds: 10000 ``` 5、使用apache-jmeter工具模拟高并发场景,形成服务雪崩效应 ![img](https://images2015.cnblogs.com/blog/397872/201707/397872-20170718202036130-1408887446.png) ![img](https://images2015.cnblogs.com/blog/397872/201707/397872-20170718202449771-1468394439.png) ### 7.3 Hystrix简介 ​ Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。 ​ “断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。 ### 7.4 服务熔断构建步骤 ​ 熔断机制是应对雪崩效应的一种微服务链路保护机制**(可以理解为服务端的服务故障处理)**。当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回"错误"的响应信息。当检测到该节点微服务调用响应正常后恢复调用链路。在SpringCloud框架里熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败就会启动熔断机制。 1、参考springcloud-provider-8001新建springcloud-provider-hystrix-8001,查看父工程的pom文件变化 2、修改pom文件,增加hystrix依赖 ```xml 4.0.0 com.pwser.springcloud springcloud-parent 0.0.1-SNAPSHOT springcloud-provider-hystrix-8001 springcloud微服务-生产者hystrix服务端模块 org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.boot spring-boot-starter-actuator org.springframework.cloud spring-cloud-starter-netflix-hystrix com.pwser.springcloud springcloud-common ${project.version} org.springframework.boot spring-boot-starter-web org.mybatis.spring.boot mybatis-spring-boot-starter 1.3.2 org.springframework.boot spring-boot-devtools runtime mysql mysql-connector-java com.alibaba druid org.springframework.boot spring-boot-starter-test test ${project.artifactId} org.springframework.boot spring-boot-maven-plugin ``` 3、修改DeptController,在get方法上添加@HystrixCommand注解,表示报异常后如何处理,一旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注好的fallbackMethod调用类中的指定方法 ```java @GetMapping(value = "/dept/get/{id}") // 一旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注好的fallbackMethod调用类中的指定方法 @HystrixCommand(fallbackMethod = "processHystrix_Get") public Dept get(@PathVariable("id") Long id) { Dept dept = deptService.get(id); if (null == dept) { throw new RuntimeException("该ID:" + id + "没有没有对应的信息"); } return dept; } public Dept processHystrix_Get(@PathVariable("id") Long id) { return new Dept().setDeptNo(id).setDeptName("该ID:" + id + "没有没有对应的信息,null--@HystrixCommand") .setDbSource("no this database in MySQL"); } ``` 4、修改主启动类,添加@EnableHystrix注解 5、测试 先启动springcloud-eureka-7001,再启动springcloud-provider-hystrix-8001、springcloud-consumer-feign-80,稍等一会再访问http://localhost/feign/dept/get/123,对于不存在的部门id会调用fallbackMethod指定方法 ### 7.5 服务降级构建步骤 ​ 整体资源快不够了,忍痛将某些服务先关掉,待渡过难关,再开启回来。服务降级处理是在客户端实现完成的,与服务端没有关系。 1、修改springcloud-common,将已经有的DeptClientService接口的@FeignClient注解修改为 ```java @FeignClient(name = "springcloud-provider", fallbackFactory = DeptServiceFallbackFactory.class) ``` 2、新建DeptServiceFallbackFactory,千万不要忘记在类上面新增@Component注解,然后mvn clean install ```java package com.pwser.common.fallback; import com.pwser.common.entities.Dept; import com.pwser.common.interf.DeptServiceInterf; import feign.hystrix.FallbackFactory; import org.springframework.cloud.client.ServiceInstance; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; @Component //不要忘记添加 public class DeptServiceFallbackFactory implements FallbackFactory { @Override public DeptServiceInterf create(Throwable throwable) { return new DeptServiceInterf() { @Override public Dept get(Long id) { return new Dept().setDeptNo(id) .setDeptName("该ID:" + id + "没有没有对应的信息,Consumer客户端提供的降级信息,此刻服务Provider已经关闭") .setDbSource("no this database in MySQL"); } @Override public List list() { return null; } @Override public boolean add(Dept dept) { return false; } @Override public List discovery() { return null; } @Override public List getServiceById(String serviceId) { return null; } @Override public List avalanche() throws InterruptedException { List deptlist = new ArrayList<>(); deptlist.add(new Dept().setDeptName("服务正忙,请稍后再试...")); return deptlist; } }; } } ``` 3、修改springcloud-consumer-feign-80的yml文件,feign.hystrix.enabled设置为true ```yaml #开启hystrix feign: hystrix: enabled: true client: config: default: connectTimeout: 10000 #设置feign默认远程访问的超时间,与下面配置必须同时配置 readTimeout: 10000 #设置feign默认远程访问的超时间,默认1s hystrix: command: default: execution: timeout: enabled: true isolation: thread: timeoutInMilliseconds: 10000 ``` 4、测试 先启动springcloud-eureka-7001,再启动springcloud-provider-8001、springcloud-consumer-feign-80,稍等一会再访问http://localhost/feign/dept/get/1,可以正常访问,然后恶意关闭springcloud-provider-8001,再http://localhost/feign/dept/get/1客户端立即返回了服务降级信息 ### 7.6 服务监控HystrixDashboard构建步骤 ​ 除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(Hystrix Dashboard),Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面。 1、新建springcloud-hystrix-dashboard-9001,查看父工程pom文件 2、修改pom文件 ```xml 4.0.0 com.pwser.springcloud springcloud-parent 0.0.1-SNAPSHOT springcloud-hystrix-dashboard-9001 springcloud微服务-hystrix-dashboard监控模块 org.springframework.cloud spring-cloud-starter-netflix-hystrix org.springframework.cloud spring-cloud-starter-netflix-hystrix-dashboard org.springframework.boot spring-boot-starter-actuator ${project.artifactId} org.springframework.boot spring-boot-maven-plugin ``` 3、修改yml文件 ```yaml server: port: 9001 spring: application: name: springcloud-hystrix-dashboard info: app.name: pwser-springcloud company.name: www.pwser.com build.artifactId: @project.artifactId@ build.version: @project.version@ ``` 4、主启动类改名+新注解@EnableHystrixDashboard 5、所有Provider微服务提供类(8001/8002/8003)都需要监控依赖配置 ```xml org.springframework.boot spring-boot-starter-actuator ``` 6、测试 先启动springcloud-eureka-7001,再启动springcloud-provider-hystrix-8001、springcloud-hystrix-dashboard-9001,稍等一会再访问http://localhost:8001/hystrix.stream,可以看到每隔几秒在都有监控输出,然后访问http://localhost:9001/hystrix可看到 ![img](https://images2017.cnblogs.com/blog/1254583/201801/1254583-20180128213305022-1926741816.png) url输入http://localhost:8001/hystrix.stream,Delay输入2000(2秒一次),自己定义一个title,点击Monitor Stream按钮进入监控页面,疯狂刷新http://localhost:8001/dept/get/1,看到如下效果 ![img](https://images2017.cnblogs.com/blog/1254583/201801/1254583-20180128220443600-243457834.png) ## 八、Zuul路由网关 ### 8.1 Zuul简介 Zuul包含了对请求的**路由和过滤**两个最主要的功能: ​ 其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础。而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础.Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,也即以后的访问微服务都是通过Zuul跳转后获得。注意:Zuul服务最终还是会注册进Eureka。 ![img](http://www.xdlysk.com/wp-content/uploads/2018/02/gateway.png) ### 8.2 构建步骤 #### 8.2.1 路由基本配置 1、新建Module模块springcloud-zuul-9527,查看父工程pom文件 2、修改pom文件,增加zuul依赖 ```xml 4.0.0 com.pwser.springcloud springcloud-parent 0.0.1-SNAPSHOT springcloud-zuul-9527 springcloud微服务-zuul路由网关模块 org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.cloud spring-cloud-starter-netflix-zuul org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-devtools runtime org.springframework.boot spring-boot-starter-test test ${project.artifactId} org.springframework.boot spring-boot-maven-plugin ``` 3、修改yml文件 ```yaml server: port: 9527 spring: application: name: springcloud-zuul eureka: client: #客户端注册进eureka服务列表内 service-url: defaultZone: http://localhost:7001/eureka/ #defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ instance: ip-address: 192.168.0.222 instance-id: ${spring.application.name}-${eureka.instance.ip-address}:${server.port} prefer-ip-address: true #访问路径可以显示IP地址 #开启actuator监控 management: endpoints: web: exposure: include: "*" #开放所有页面节点 默认只开启了health、info两个节点 endpoint: health: show-details: always #显示健康具体信息默认不会显示详细信息 info: app.name: pwser-springcloud company.name: www.pwser.com build.artifactId: @project.artifactId@ build.version: @project.version@ ``` 4、主启动类添加@EnableEurekaClient、@EnableZuulProxy注解 5、测试 先启动springcloud-eureka-7001,再启动springcloud-provider-8001、springcloud-zuul-9527,稍等一会再访问http://localhost:8001/dept/get/2和http://localhost:9527/springcloud-provider/dept/get/2,一个是原地址,一个是路由地址 #### 8.2.2 路由访问映射规则 1、修改yml文件 ```yaml zuul: prefix: /pwser #访问前缀 #ignored-services: springcloud-provider ignored-services: "*" #忽略所有服务真实名称 routes: #路由代理名称 provider.serviceId: springcloud-provider provider.path: /provider/** ``` 2、测试 先启动springcloud-eureka-7001,再启动springcloud-provider-8001、springcloud-zuul-9527,稍等一会再访问http://localhost:9527/pwser/provider/dept/get/2 #### 8.2.3 服务过滤配置 1、新建MyFilter类,继承ZuulFilter类,run方法中判断url有无token参数,没有就拦截,有就放行 ```java package com.pwser.zuul.filter; import javax.servlet.http.HttpServletRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; @Component public class MyFilter extends ZuulFilter { private static Logger log = LoggerFactory.getLogger(MyFilter.class); @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 0; } public boolean shouldFilter() { return true; } public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); log.info(String.format("%s >>> %s", request.getMethod(), request.getRequestURL().toString())); Object accessToken = request.getParameter("token"); if (accessToken != null) { return null; } log.error("token is empty"); ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(401); try { ctx.getResponse().getWriter().write("token is empty"); } catch (Exception e) { e.printStackTrace(); } return null; } } ``` 2、测试 先启动springcloud-eureka-7001,再启动springcloud-provider-8001、springcloud-zuul-9527,稍等一会再访问http://localhost:9527/pwser/provider/dept/get/2和http://localhost:9527/pwser/provider/dept/get/2?token=abc,第一个地址显示token is empty,第二个可以正常显示 ## 九、SpringCloud Config分布式配置中心 ### 9.1 分布式配置中心简介 ​ 微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以一套集中式的、动态的配置管理设施是必不可少的。SpringCloud提供了ConfigServer来解决这个问题,我们每一个微服务自己带着一个application.yml,上百个配置文件的管理。 ​ SpringCloud Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置。SpringCloud Config分为服务端和客户端两部分。 ​ 服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口。 ​ 客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容。 ![img](https://img-blog.csdn.net/20180309165553122) 分布式配置中心作用: - 集中管理配置文件 - 不同环境不同配置,动态化的配置更新,分环境部署比如dev/test/prod/beta/release - 运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉取配置自己的信息 - 当配置发生变动时,服务不需要重启即可感知到配置的变化并应用新的配置 - 将配置信息以REST接口的形式暴露 ### 9.2 与GitHub整合配置 ​ 由于SpringCloud Config默认使用Git来存储配置文件(也有其它方式,比如支持SVN和本地文件),但最推荐的还是Git,而且使用的是http/https访问的形式。 1、用自己的Gitee账号在Gitee上新建一个名为springCloudConfig的新仓库,获取https地址如:https://gitee.com/fenghao1987/AppConfig.git 2、本地硬盘目录上新建git仓库并clone,本地地址:D:\git-repository\springCloudConfig,git命令: ```shell git clone https://gitee.com/fenghao1987/AppConfig.git ``` 3、在本地D:\git-repository\springCloudConfig里面新建一个application.yml保存格式必须为UTF-8 ```yaml spring: profiles: active: - site --- spring: profiles: site application: name: springcloud-config-site --- spring: profiles: test application: name: springcloud-config-test ``` 4、将上一步的YML文件推送到gitee上,命令: ```shell git add . git commit -m "init yml" git push origin master ``` 5、测试 查看gitee上的显示效果 ### 9.3 SpringCloud Config服务端配置 1、新建Module模块springcloud-config-server-3344,查看父工程pom文件 2、修改pom文件 ```xml 4.0.0 com.pwser.springcloud springcloud-parent 0.0.1-SNAPSHOT springcloud-config-server-3344 springcloud微服务-config配置中心服务端模块 org.springframework.cloud spring-cloud-config-server org.springframework.boot spring-boot-starter-test org.springframework.boot spring-boot-devtools ${project.artifactId} org.springframework.boot spring-boot-maven-plugin ``` 3、修改yml文件 ```yaml server: port: 3344 spring: application: name: springcloud-config-server cloud: config: server: git: uri: https://gitee.com/fenghao1987/AppConfig.git #GitHub上面的git仓库名字 info: app.name: pwser-springcloud company.name: www.pwser.com build.artifactId: @project.artifactId@ build.version: @project.version@ ``` 4、主启动类添加@EnableConfigServer注解 5、测试 启动springcloud-config-server-3344,访问http://localhost:3344/application-site.yml,http://localhost:3344/application-test.yml,http://localhost:3344/application-xxxx.yml 配置文件读取规则: - /{application}-{profile}.yml:http://localhost:3344/application-test.yml - /{application}/{profile}[/{label}]:http://localhost:3344/application/test/master - /{label}/{application}-{profile}.yml:http://localhost:3344/master/application-test.yml ### 9.4 SpringCloud Config客户端配置 1、在本地D:\git-repository\springCloudConfig路径下新建文件springcloud-config-client.yml,保存为utf-8编码,提交到gitee上 ```yaml spring: profiles: active: - site --- server: port: 3355 spring: profiles: site application: name: springcloud-config-client-site eureka: client: service-url: defaultZone: http://localhost:7001/eureka/ --- server: port: 3366 spring: profiles: test application: name: springcloud-config-client-test eureka: client: service-url: defaultZone: http://localhost:7001/eureka/ ``` 2、新建springcloud-config-client-3355,查看父工程pom文件 3、修改pom文件 ```xml 4.0.0 com.pwser.springcloud springcloud-parent 0.0.1-SNAPSHOT springcloud-config-client-3355 springcloud微服务-config客户端模块 org.springframework.cloud spring-cloud-starter-config org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test org.springframework.boot spring-boot-devtools ${project.artifactId} org.springframework.boot spring-boot-maven-plugin ``` 4、新建bootstrap.yml文件,applicaiton.yml是用户级的资源配置项,而bootstrap.yml是系统级的,优先级更加高。 > Spring Cloud会创建一个`Bootstrap Context`,作为Spring应用的`Application Context`的父上下文。初始化的时候,`Bootstrap Context`负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的`Environment`。`Bootstrap`属性有高优先级,默认情况下,它们不会被本地配置覆盖。 `Bootstrap context`和`Application Context`有着不同的约定,所以新增了一个`bootstrap.yml`文件,保证`Bootstrap Context`和`Application Context`配置的分离。 ```yaml spring: cloud: config: name: springcloud-config-client #需要从github上读取的资源名称,注意没有yml后缀名 profile: test #本次访问的配置项 label: master uri: http://localhost:3344 #本微服务启动后先去找3344号服务,通过SpringCloudConfig获取GitHub的服务地址 ``` 5、修改application.yml文件 ```yaml #server: # port: 3355 spring: application: name: springcloud-config-client #开启actuator监控 management: endpoints: web: exposure: include: "*" #开放所有页面节点 默认只开启了health、info两个节点 endpoint: health: show-details: always #显示健康具体信息默认不会显示详细信息 info: app.name: pwser-springcloud company.name: www.pwser.com build.artifactId: @project.artifactId@ build.version: @project.version@ ``` 6、新建ConfigClientController测试配置文件读取 ```java package com.pwser.configclient.controller; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class ConfigClientController { @Value("${spring.application.name}") private String applicationName; @Value("${eureka.client.service-url.defaultZone}") private String eurekaServers; @Value("${server.port}") private String port; @GetMapping("/config") public String getConfig() { String str = "applicationName: " + applicationName + "\t eurekaServers:" + eurekaServers + "\t port: " + port; System.out.println("******str: " + str); return "[applicationName: " + applicationName + "]-----[eurekaServers: " + eurekaServers + "]-----[port: " + port + "]"; } } ``` 7、测试 启动springcloud-config-server-3344,再启动springcloud-config-client-3355,bootstrap.yml里面的profile值是什么,决定从gitee上读取什么,假如目前是 profile: site,site默认在gitee上对应的端口就是3355http://localhost:3355/config;假如目前是 profile: test,test默认在gitee上对应的端口就是3366http://localhost:3366/config ### 9.5 配置文件实时更新 1、修改ConfigClientController,添加@RefreshScope注解 2、springcloud-config-client-3355pom文件添加actuator依赖 3、测试,修改gitee上配置文件参数,通过postman模拟post请求:http://localhost:3355/actuator/refresh,刷新http://localhost:3355/config配置参数发生变化说明成功