SpringCloud学习与搭建

Spring Cloud 微服务实践

第一章:微服务架构概述

1-1 系统进化理论概述

在系统架构与设计的实践中,经历了两个阶段,一个阶段是早些年常见的集中式系统,一个阶段是近年来流行的分布式系统;

集中式系统
集中式系统也叫单体应用,就是把所有的程序、功能、模块都集中到一个项目中,部署在一台服务器上,从而对外提供服务;

分布式系统
分布式系统就是把所有的程序、功能拆分成不同的子系统,部署在多台不同的服务器上,这些子系统相互协作共同对外提供服务,而对用户而言他并不知道后台是多个子系统和多台服务器在提供服务,在使用上和集中式系统一样;

第二章:SpringCloud 入门

2-1 搭建和配置一个服务提供者

1.由于我们可能要搭建多个服务者消费者,这里我们创建多模块工程。创建一个SpringBoot父工程-springcloudparent,加入springboot父级依赖:

<!--SpringBoot的父级依赖-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

2.创建springboot子工程01-springcloud-service-provider,并且添加SpringBoot相关依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         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.wkcto.springcloud</groupId>
    <artifactId>01-springcloud-service-provider</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>01-springcloud-service-provider</name>
    <description>Demo project for Spring Boot</description>

    <!-- 继承本项目的父工程 -->
    <parent>
        <artifactId>springcloud-parent</artifactId>
        <groupId>com.shuaibin.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <!--Maven的属性配置-->
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>

        <!--SpringBoot开发web项目的起步依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--SpringBoot测试的起步依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--SpringCloud集成eureka客户端的起步依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <!--SpringBoot打包编译插件-->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Finchley.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/libs-milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

</project>

3.创建服务提供者的访问方法,也就是后续消费者如何访问提供者;Spring Cloud 是基于 rest 的访问,所以我们添加一个 Controller,在该Controller 中提供一个访问入口:

@RestController
public class HelloController {
 	@RequestMapping(value = "/hello", method = RequestMethod.GET)
 	public String hello() {
 		return "Hello Spring Cloud";
 	}
}

4.启动运行该 SpringBoot 程序,访问该 controller;

2-2 搭建和配置一个服务消费者

1.服务消费者也是一个 SpringBoot 项目,服务消费者主要用来消费服务提供者提供的服务;添加消费者相关依赖。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         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.wkcto.springcloud</groupId>
    <artifactId>02-springcloud-service-consumer</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>02-springcloud-service-consumer</name>
    <description>Demo project for Spring Boot</description>

    <!-- 继承本项目的父工程 -->
    <parent>
        <artifactId>springcloud-parent</artifactId>
        <groupId>com.shuaibin.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--SpringCloud集成eureka客户端的起步依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <!--Spring Cloud熔断器起步依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix</artifactId>
            <version>1.4.5.RELEASE</version>
        </dependency>

        <!--Spring Boot提供的一个服务健康检查监控的依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Finchley.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/libs-milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

</project>

2.开发一个消费者方法,去消费服务提供者提供的服务,这个消费者方法也是一个 Controller:

@RestController
public class ConsumerController {
	 	@Autowired
	 	private RestTemplate restTemplate;
	 	
	 	@RequestMapping(value="/cloud/hello")
		public String helloController() {
		return restTemplate.getForEntity("http://localhost:9100/hello",String.class).getBody();
	 } 
 }

3.启动该 SpringBoot 程序,测试服务消费者调用服务提供者;

注意服务者端口号是多少改成对应的端口号!此时我们调用的是服务者的实际地址,但实际的微服务中是不会去记住服务者实际的IP地址实际的端口号的,所以这里只是做个简单的测试,后面会写到实际上会如何去调用服务者的方法。

2-3 搭建服务注册中心 Eureka

在微服务架构中,服务注册与发现是核心组件之一,手动指定每个服务是很低效的,Spring Cloud 提供了多种服务注册与发现的实现方式,例如:Eureka、Consul、Zookeeper。Spring Cloud 支持得最好的是 Eureka,其次是 Consul,再次是 Zookeeper。
什么是服务注册?
服务注册:将服务所在主机、端口、版本号、通信协议等信息登记到注册中心上;
什么是服务发现?
服务发现:服务消费者向注册中心请求已经登记的服务列表,然后得到某个服务的主机、端口、版本号、通信协议等信息,从而实现对具体服务的调用;
Eureka 是什么?
Eureka 是一个服务治理组件,它主要包括服务注册和服务发现,主要用来搭建服务注册中心。Eureka 是一个基于 REST 的服务,用来定位服务,进行中间层服务器的负载均衡和故障转移;
Eureka 是 Netflix 公司开发的,Spring Cloud 封装了 Netflix 公司开发的Eureka 模块来实现服务注册和发现,也就是说 Spring Cloud 对 Netflix Eureka 做了二次封装;

Eureka 采用了 C-S(客户端/服务端)的设计架构,也就是 Eureka 由两个组件组成:Eureka 服务端和 Eureka 客户端。Eureka Server 作为服务注册的服务端,它是服务注册中心,而系统中的其他微服务,使用 Eureka 的客户端连接到 Eureka Server 服务端,并维持心跳连接,Eureka 客户端是一个 Java 客户端,用来简化与服务器的交互、负载均衡,服务的故障切换等;
有了 Eureka 注册中心,系统的维护人员就可以通过 Eureka Server 来监控系
统中各个微服务是否正常运行。

Spring Cloud 要使用 Eureka 注册中心非常简单和方便,Spring Cloud 中的Eureka 服务注册中心实际上也是一个 Spring Boot 工程,我们只需通过引入相 关依赖和注解配置就能让 Spring Boot 构建的微服务应用轻松地与 Eureka 进行整合。
具体步骤如下:
1、创建一个 SpringBoot 项目,并且添加 SpringBoot 的相关依赖;
03-springcloud-eureka-server
2.添加 eureka 的依赖:

<!--Spring Cloud 的 eureka-server 起步依赖--> 
<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

3、在 Spring Boot 的入口类上添加一个@EnableEurekaServer 注解,用于开启 Eureka 注册中心服务端
4、在 application.properties 文件中配置 Eureka 服务注册中心信息:

#内嵌定时 tomcat 的端口
server.port=8761
#设置该服务注册中心的 hostname
eureka.instance.hostname=localhost
#由于我们目前创建的应用是一个服务注册中心,而不是普通的应用,默认情况下,这个应用会向注
册中心(也是它自己)注册它自己,设置为 false 表示禁止这种自己向自己注册的默认行为
eureka.client.register-with-eureka=false
#表示不去检索其他的服务,因为服务注册中心本身的职责就是维护服务实例,它不需要去检索其他
服务
eureka.client.fetch-registry=false
#指定服务注册中心的位置
eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${serv
er.port}/eureka

5.然后通过浏览器可以直接访问到注册中心:8761注册中心此时注册中心上没有服务。
如图所示:
SpringCloud学习与搭建_第1张图片

2-4 向 Eureka 服务注册中心注册服务

我们前面搭建了服务提供者项目,接下来我们就可以将该服务提供者注册到
Eureke 注册中心,步骤如下:
1、在该服务提供者中添加 eureka 的依赖,因为服务提供者向注册中心注册服务,需要连接 eureka,所以需要 eureka 客户端的支持;

<!--SpringCloud 集成 eureka 客户端的起步依赖-->
<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

2、激活 Eureka 中的 EnableEurekaClient 功能:
在 Spring Boot 的入口函数处,通过添加@EnableEurekaClient 注解来表明自
己是一个 eureka 客户端,让我的服务提供者可以连接 eureka 注册中心;
3、配置服务名称和注册中心地址

spring.application.name=01-springcloud-service-provider
eureka.client.service-url.defaultZone=http://localhost:8761/eureka

4、启动服务提供者 SpringBoot 程序的 main 方法运行;
5、启动运行之后,通过在浏览器地址栏访问我们之前搭建好的 eureka 注册中心,就可以看到有一个服务已经注册成功了;
如图所示:
SpringCloud学习与搭建_第2张图片

2-5 从 Eureka 服务注册中心发现与消费服务

我们已经搭建一个服务注册中心,同时也向这个服务注册中心注册了服务,接下来我们就可以发现和消费服务了,这其中服务的发现由 eureka 客户端实现,而服务的消费由 Ribbon 实现,也就是说服务的调用需要 eureka 客户端和 Ribbon。

两者配合起来才能实现;
Eureka 客户端是什么?
Eureka 客户端是一个 Java 客户端,用来连接 Eureka 服务端,与服务端进行交互、负载均衡,服务的故障切换等;

Ribbon 是什么?
Ribbon 是一个基于 HTTP 和 TCP 的客户端负载均衡器,当使用 Ribbon 对服务进行访问的时候,它会扩展 Eureka 客户端的服务发现功能,实现从 Eureka注册中心中获取服务端列表,并通过 Eureka 客户端来确定服务端是否己经启动。Ribbon 在 Eureka 客户端服务发现的基础上,实现了对服务实例的选择策略,从而实现对服务的负载均衡消费。
接下来我们来让服务消费者去消费服务:
我们前面搭建了服务消费者项目,接下来我们就可以使用该服务消费者通过注册中心去调用服务提供者,步骤如下:
1、在该消费者项目中添加 eureka 的依赖,因为服务消费者从注册中心获取服务,需要连接 eureka,所以需要 eureka 客户端的支持;

<!--SpringCloud 集成 eureka 客户端的起步依赖--> 
<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency> 

2、激活 Eureka 中的 EnableEurekaClient 功能:
在 Spring Boot 的入口函数处,通过添加@EnableEurekaClient 注解来表明自己是一个 eureka 客户端,让我的服务消费者可以使用 eureka 注册中心;
3、配置服务的名称和注册中心的地址:

spring.application.name=03-springcloud-web-consumer
eureka.client.service-url.defaultZone=http://localhost:8761/eureka

4、前面我介绍了服务的发现由 eureka 客户端实现,而服务的真正调用由 ribbon实现,所以我们需要在调用服务提供者时使用 ribbon 来调用:

@LoadBalanced //负载均衡使用,目前可以删除。
@Bean
public RestTemplate restTemplate () {
 return new RestTemplate();
}

加入了 ribbon 的支持,那么在调用时,即可改为使用服务名称来访问:

restTemplate.getForEntity("http://01-SPRINGCLOUD-SERVICE-PROVIDER/cloud/hello", 
String.class).getBody();

5、完成上面的步骤后,我们就可以启动消费者的 SpringBoot 程序,main 方法运行;
6、启动成功之后,通过在浏览器地址栏访问我们的消费者,看是否可以正常调用远程服务提供者提供的服务;

2-6 Eureka 与 Zookeeper 的比较

著名的 CAP 理论指出,一个分布式系统不可能同时满足 C(一致性)、A(可用性) 和 P(分区容错性)。
由于分区容错性在是分布式系统中必须要保证的,因此我们只能在 A 和 C 之间进行权衡,在此 Zookeeper 保证的是 CP, 而 Eureka 则是 AP。
Zookeeper 保证 CP
在 ZooKeeper 中,当 master 节点因为网络故障与其他节点失去联系时,剩余节点会重新进行 leader 选举,但是问题在于,选举 leader 需要一定时间, 且选举期间整个 ZooKeeper 集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得 ZooKeeper 集群失去 master 节点是大概率事件,虽然服务最终能够恢复,但是在选举时间内导致服务注册长期不可用是难以容忍的。
Eureka 保证 AP
Eureka 优先保证可用性,Eureka 各个节点是平等的,某几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而 Eureka 的客户端在向某个 Eureka 注册或时如果发现连接失败,则会自动切换至其它节点,只要有一台 Eureka 还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。所以 Eureka 在网络故障导致部分节点失去联系的情况下,只要有一个节点可用,那么注册和查询服务就可以正常使用,而不会像 zookeeper 那样使整个注册服务瘫痪,Eureka 优先保证了可用性。

第三章:服务注册中心 Eureka

3-1Eureka 注册中心高可用集群概述

在微服务架构的这种分布式系统中,我们要充分考虑各个微服务组件的高可用性问题,不能有单点故障,由于注册中心 eureka 本身也是一个服务,如果它只有一个节点,那么它有可能发生故障,这样我们就不能注册与查询服务了,所以我们需要一个高可用的服务注册中心,这就需要通过注册中心集群来解决。eureka 服务注册中心它本身也是一个服务,它也可以看做是一个提供者,又可以看做是一个消费者,我们之前通过配置:eureka.client.register-with-eureka=false 让注册中心不注册自己,但是我们可以向其他注册中心注册自己;

Eureka Server 的高可用实际上就是将自己作为服务向其他服务注册中心注册自己,这样就会形成一组互相注册的服务注册中心,进而实现服务清单的互相同步,往注册中心 A 上注册的服务,可以被复制同步到注册中心 B 上,所以从任何一台注册中心上都能查询到已经注册的服务,从而达到高可用的效果。
SpringCloud学习与搭建_第3张图片

3-2 Eureka 注册中心高可用集群搭建

我们知道,Eureka 注册中心高可用集群就是各个注册中心相互注册,所以:
1.在 8761 的配置文件中,让它的 service-url 指向 8762,在 8762 的配置文件
中让它的 service-url 指向 8761
8761配置文件如下:

#内嵌定时tomcat的端口
server.port=8761

#设置该服务注册中心的hostname
eureka.instance.hostname=eureka8761

#测试时关闭自我保护机制,保证不可用服务及时踢出
eureka.server.enable-self-preservation=false

#由于我们目前创建的应用是一个服务注册中心,而不是普通的应用,默认情况下,这个应用会向注册中心(也是它自己)注册它自己,设置为false表示禁止这种自己向自己注册的默认行为
eureka.client.register-with-eureka=false
#表示不去检索其他的服务,因为服务注册中心本身的职责就是维护服务实例,它不需要去检索其他服务
eureka.client.fetch-registry=false
#指定服务注册中心的位置
eureka.client.service-url.defaultZone=http://eureka8762:8762/eureka

2.由于 8761 和 8762 互相指向对方,实际上我们构建了一个双节点的服务注册中心集群.
3.然后在本地 hosts 文件配置:C:\Windows\System32\drivers\etc\hosts
127.0.0.1 eureka8761
127.0.0.1 eureka8762
这一步是为了模仿不同的主机,但这里我们是同一台主机,指向的主机号也是同一个(自己欺骗自己一下)
4.分别启动两个注册中心,访问两个注册中心页面,观察注册中心页面是否正常;
如下所示:
SpringCloud学习与搭建_第4张图片

3-3 Eureka 注册中心高可用集群测试

将服务提供者的eureka的连接地址配置为:

eureka.client.service-url.defaultZone=http://eureka8761:8761/eureka/,http://eureka8762:8762/eureka/

启动服务提供者服务,然后观察注册中心页面,可以看到服务会在两个注册中心上都注册成功;

3-4 Eureka 服务注册中心自我保护机制

自我保护机制是 Eureka 注册中心的重要特性,当 Eureka 注册中心进入自我保护模式时,在 Eureka Server 首页会输出如下警告信息:

EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT.
RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED
JUST TO BE SAFE.
emergency! eureka may be incorrectly claiming instances are up when they’re not. renewals are
lesser than threshold and hence the instances are not being expired just to be safe.

在没有 Eureka 自我保护的情况下,如果 Eureka Server 在一定时间内没有接收到某个微服务实例的心跳,Eureka Server 将会注销该实例,但是当发生网络分区故障时,那么微服务与 Eureka Server 之间将无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是正常的,此时不应该注销这个微服务,如果没有自我保护机制,那么 Eureka Server 就会将此服务注销掉。

Eureka 通过“自我保护模式”来解决这个问题——当 Eureka Server 节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么就会把这个微服务节点进行保护。一旦进入自我保护模式,Eureka Server 就会保护服务注册表中的信息,不删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该 Eureka Server 节点会再自动退出自我保护模式。

所以,自我保护模式是一种应对网络异常的安全保护措施,它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留),也不盲目注销任何健康的微服务,使用自我保护模式,可以让 Eureka 集群更加的健壮、稳定。当然也可以使用配置项:eureka.server.enable-self-preservation = false 禁用自我保护模式。

但是 Eureka Server 自我保护模式也会给我们带来一些困扰,如果在保护期内某个服务提供者刚好非正常下线了,此时服务消费者就会拿到一个无效的服务实例,此时会调用失败,对于这个问题需要服务消费者端具有一些容错机制,如重试,断路器等。

Eureka 的自我保护模式是有意义的,该模式被激活后,它不会从注册列表中剔除因长时间没收到心跳导致注册过期的服务,而是等待修复,直到心跳恢复正常之后,它自动退出自我保护模式。这种模式旨在避免因网络分区故障导致服务不可用的问题。

例如,两个微服务客户端实例 A 和 B 之间有调用的关系,A 是消费者,B 是提供者,但是由于网络故障,B 未能及时向 Eureka 发送心跳续约,这时候 Eureka 不能简单的将 B 从注册表中剔除,因为如果剔除了,A 就无法从 Eureka 服务器中获取 B 注册的服务,但是这时候 B 服务是可用的;所以,Eureka 的自我保护模式最好还是开启它。

关于自我保护常用几个配置如下:

服务器端配置:
#测试时关闭自我保护机制,保证不可用服务及时踢出
eureka.server.enable-self-preservation=false
客户端配置:
#每间隔 2s,向服务端发送一次心跳,证明自己依然"存活"
eureka.instance.lease-renewal-interval-in-seconds=2 #告诉服务端,如果我 10s 之内没有给你发心跳,就代表我故障了,将我踢出掉
eureka.instance.lease-expiration-duration-in-seconds=10

第四章:客户端负载均衡 Ribbon

4-1Spring Cloud 中的 Ribbon 是什么?

我们通常说的负载均衡是指将一个请求均匀地分摊到不同的节点单元上执行,负载均和分为硬件负载均衡和软件负载均衡:

硬件负载均衡或是软件负载均衡,他们都会维护一个可用的服务端清单,通过心跳检测来剔除故障的服务端节点以保证清单中都是可以正常访问的服务端节点。当客户端发送请求到负载均衡设备的时候,该设备按某种算法(比如轮询、权重、最小连接数等)从维护的可用服务端清单中取出一台服务端的地址,然后进行转发。

Ribbon 是 Netflix 发布的开源项目,主要功能是提供客户端的软件负载均衡算法,是一个基于 HTTP 和 TCP 的客户端负载均衡工具。Spring Cloud 对 Ribbon 做了二次封装,可以让我们使用 RestTemplate 的服务请求,自动转换成客户端负载均衡的服务调用。Ribbon 支持多种负载均衡算法,还支持自定义的负载均衡算法。Ribbon 只是一个工具类框架,比较小巧,Spring Cloud 对它封装后使用也非常方便,它不像服务注册中心、配置中心、API 网关那样需要独立部署,Ribbon只需要在代码直接使用即可;

Ribbon 与 Nginx 的区别
Ribbon 是客户端的负载均衡工具,而客户端负载均衡和服务端负载均衡最大的区别在于服务清单所存储的位置不同,在客户端负载均衡中,所有客户端节点下 的服务端清单,需要自己从服务注册中心获取,比如 Eureka 服务注册中心。同服务端负载均衡的架构类似,在客户端负载均衡中也需要心跳去维护服务端清单的健康性,只是这个步骤需要与服务注册中心配合完成。在 Spring Cloud 中,由于 Spring Cloud 对 Ribbon 做了二次封装,所以默认会创建针对 Ribbon 的自动化整合配置;

在 Spring Cloud 中,Ribbon 主要与 RestTemplate 对象配合起来使用,Ribbon会自动化配置 RestTemplate 对象,通过@LoadBalanced 开启 RestTemplate对象调用时的负载均衡。
SpringCloud学习与搭建_第5张图片

4-2 Ribbon 实现客户端负载均衡

由于 Spring Cloud Ribbon 的封装, 我们在微服务架构中使用客户端负载均衡调用非常简单, 只需要如下两步:
1、启动多个服务提供者实例并注册到一个服务注册中心或是服务注册中心集群。
2、服务消费者通过被@LoadBalanced 注解修饰过的 RestTemplate 来调用服务提供者。
这样,我们就可以实现服务提供者的高可用以及服务消费者的负载均衡调用。
注意:我们两个服务提供者注册到注册中心上的名字要一样。

4-3 Ribbon 负载均衡策略

Ribbon 的负载均衡策略是由 IRule 接口定义, 该接口由如下实现:
SpringCloud学习与搭建_第6张图片

RandomRule 随机
RoundRobinRule 轮询
AvailabilityFilteringRule 先过滤掉由于多次访问故障的服务,以及并发连接数超过阈值的服务,然后对剩下的服务按照轮询策略进行访问;
WeightedResponseTimeRule 根据平均响应时间计算所有服务的权重,响应时间越快服务权重就越大被选中的概率即越高,如果服务刚启动时统计信息不足,则使用 RoundRobinRule 策略,待统计信息足够会切换到该 WeightedResponseTimeRule 策 略;
RetryRule 先按照 RoundRobinRule 策略分发,如果分发到的服务不能访问,则在指定时间内进行重试,分发其他可用的服务;
BestAvailableRule 先过滤掉由于多次访问故障的服务,然后选择一个并发量最小的服务;
ZoneAvoidanceRule 综合判断服务节点所在区域的性能和服务节点的可用性,来决定选择哪个服务;
	/**
     * 覆盖掉原来ribbon默认的轮询负载均衡策略
     *
     *
     * @return
     */
    //@Bean
    public IRule iRule() {
        //return new RandomRule(); //采用随机的负载均衡策略
        return new RetryRule(); //采用重试的负载均衡策略
    }

第五章:服务熔断 Hystrix

5-1 Hystrix 是什么

在微服务架构中,我们是将一个单体应用拆分成多个服务单元,各个服务单元之间通过注册中心彼此发现和消费对方提供的服务,每个服务单元都是单独部署,在各自的服务进程中运行,服务之间通过远程调用实现信息交互,那么当某个服务的响应太慢或者故障,又或者因为网络波动或故障,则会造成调用者延迟或调用失败,当大量请求到达,则会造成请求的堆积,导致调用者的线程挂起,从而引发调用者也无法响应,调用者也发生故障,

比如电商中的用户下订单,我们有两个服务,一个下订单服务,一个减库存服务,当用户下订单时调用下订单服务,然后下订单服务又调用减库存服务,如果减库存服务响应延迟或者没有响应,则会造成下订单服务的线程挂起等待,如果大量的用户请求下订单,或导致大量的请求堆积,引起下订单服务也不可用,如果还有另外一个服务依赖于订单服务,比如用户服务,它需要查询用户订单,那么用户服务查询订单也会引起大量的延迟和请求堆积,导致用户服务也不可用。所以在微服务架构中,很容易造成服务故障的蔓延,引发整个微服务系统瘫痪不可用。

为了解决此问题,微服务架构中引入了一种叫熔断器的服务保护机制。
熔断器也有叫断路器,他们表示同一个意思,最早来源于微服务之父 Martin Fowler 的论文 CircuitBreaker 一文。“熔断器”本身是一种开关装置,用于在电路上保护线路过载,当线路中有电器发生短路时,能够及时切断故障电路,防止发生过载、发热甚至起火等严重后果。微服务架构中的熔断器,就是当被调用方没有响应,调用方直接返回一个错误响应即可,而不是长时间的等待,这样避免调用时因为等待而线程一直得不到释放,避免故障在分布式系统间蔓延;

Spring Cloud Hystrix 实现了熔断器、线程隔离等一系列服务保护功能。该功能也是基于 Netflix 的开源框架 Hystrix 实现的,该框架的目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix 具备服务降级、服务熔断、线程和信号隔离、请求缓存、请求合并以及服务监控等强大功能。

5-2 Hystrix 快速入门

在 SpringCloud 中使用熔断器 Hystrix 是非常简单和方便的,只需要简单两步即可:
1、在消费者端也就是客户端添加依赖

<!--Spring Cloud 熔断器起步依赖--> 
<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-hystrix</artifactId>
 <version>1.4.4.RELEASE</version>
</dependency>

2.在入口类中使用@EnableCircuitBreaker 注解开启断路器功能,也可以使用一个名为@SpringCloudApplication 的注解代替主类上的三个注解(@SpringBootApplication,
@EnableEurekaClient,@EnableCircuitBreaker );
3、在调用远程服务的方法上添加注解:

@HystrixCommand(fallbackMethod="error")
/**
     * hystrix 默认超时时间是 1000 毫秒,如果你后端的响应超过此时间,就会触发
断路器;后面设可手动更改默认超时时间
     * 忽视运行时异常(后面会讲到)
     * @return
     */
    @RequestMapping("/web/hystrix")
    @HystrixCommand(fallbackMethod="error", ignoreExceptions= RuntimeException.class, commandProperties={@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value="3500")})
    public String hystrix () {

        //int a = 10 / 0 ; //除数是不能为0的,会抛出运行时异常

        return restTemplate.getForEntity("http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/hello", String.class).getBody();
    }

    /**
     * 熔断的回调方法,也就是降级方法
     *
     * @return
     */
    public String error(Throwable throwable) {

        System.out.println("异常:" + throwable.getMessage());

        //访问远程服务失败,该如何处理?这些处理逻辑就可以写在该方法中
        return "error";
    }

5-3 Hystrix 的服务降级

有了服务的熔断,随之就会有服务的降级,所谓服务降级,就是当某个服务熔断之后,服务端提供的服务将不再被调用,此时由客户端自己准备一个本地的fallback 回调,返回一个默认值来代表服务端的返回;这种做法,虽然不能得到正确的返回结果,但至少保证了服务的可用,比直接抛出错误或服务不可用要好很多,当然这需要根据具体的业务场景来选择;

5-4 Hystrix 的异常处理

我们在调用服务提供者时,我们自己也有可能会抛异常,默认情况下方法抛了异常会自动进行服务降级,交给服务降级中的方法去处理; 当我们自己发生异常后,只需要在服务降级方法中添加一个Throwable 类型的参数就能够获取到抛出的异常的类型,如下:

public String error(Throwable throwable) {
 	System.out.println(throwable.getMessage());
 	return "error"; 
 }

此时我们可以在控制台看到异常的类型;
如果远程服务有一个异常抛出后我们不希望进入到服务降级方法中去处理,而是直接将异常抛给用户,那么我们可以在@HystrixCommand 注解中添加忽略异常,如下:

@HystrixCommand(fallbackMethod="error", ignoreExceptions = Exception.class)

自定义 Hystrix 请求的服务异常熔断处理
我们也可以自定义类继承自 HystrixCommand 来实现自定义的 Hystrix 请求, 在 getFallback 方法中调用 getExecutionException 方法来获取服务抛出的异常;
com.netflix.hystrix.HystrixCommand.Setter.withGroupKey(HystrixCommandGroupK
ey.Factory.asKey(""));

步骤如下:
1.在消费者创建一个类MyHystrixCommand继承HystrixCommand

/**
 * 自定义的Hystrix请求
 *
 */
public class MyHystrixCommand extends HystrixCommand<String> {

    private RestTemplate restTemplate;

    public MyHystrixCommand (Setter setter, RestTemplate restTemplate) {
        super(setter);
        this.restTemplate = restTemplate;
    }

    @Override
    protected String run() throws Exception {
        int a = 10 / 0;
        //调用远程的服务
        return restTemplate.getForEntity("http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/hello", String.class).getBody();
    }

    /**
     * 当远程服务超时、异常、不可用等情况时,会触发该熔断方法
     *
     * @return
     */
    @Override
    public String getFallback() {
        Throwable throwable = super.getExecutionException();

        System.out.println(throwable.getMessage());
        System.out.println(throwable.getStackTrace());

        //实现服务熔断/降级逻辑
        return "error";
    }
}

2.客户端调用时,new一个MyHystrixCommand 的实例,传入构造参数:

@RequestMapping("/web/hystrix2")
    public String hystrix2 () throws ExecutionException, InterruptedException {

        MyHystrixCommand myHystrixCommand = new MyHystrixCommand(com.netflix.hystrix.HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("")), restTemplate);

        //同步调用(该方法执行后,会等待远程的返回结果,拿到了远程的返回结果,该方法才返回,然后代码继续往下执行)
        //String str = myHystrixCommand.execute();

        //异步调用(该方法执行后,不会马上有远程的返回结果,将来会有结果)
        Future<String> future = myHystrixCommand.queue();

        //写一些业务的逻辑

        //阻塞的方法,直到拿到结果
        String str = future.get();

        //写一些业务逻辑

        return str;
    }

5-5 Hystrix 仪表盘监控

Hystrix 仪表盘(Hystrix Dashboard),就像汽车的仪表盘实时显示汽车的各项数据一样,Hystrix 仪表盘主要用来监控 Hystrix 的实时运行状态,通过它我们可以看到 Hystrix 的各项指标信息,从而快速发现系统中存在的问题进而解决它。要使使用 Hystrix 仪表盘功能,我们首先需要有一个 HystrixDashboard,这个功能我们可以在原来的消费者应用上添加,让原来的消费者应用具备 Hystrix 仪表盘功能,但一般地,微服务架构思想是推崇服务的拆分,Hystrix Dashboard 也是一个服务,所以通常会单独创建一个新的工程专门用做 Hystrix Dashboard服务;

搭建一个 Hystrix Dashboard 服务的步骤:
第一步:创建一个普通的 Spring Boot 工程
比如创建一个名为 springcloud-hystrix-dashboard 的 Spring Boot 工程,建立好基本的结构和配置;
第二步:添加相关依赖
在创建好的 Spring Boot 项目的 pom.xml 文件中添加相关依赖,如下:

<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
 <version>1.4.5.RELEASE</version>
</dependency>

第三步:入口类上添加注解
添加好依赖之后,在入口类上添加@EnableHystrixDashboard 注解开启仪表盘功能,如下:

@SpringBootApplication
@EnableHystrixDashboard //开启仪表盘功能
public class Application3721 {

    public static void main(String[] args) {
        SpringApplication.run(Application3721.class, args);
    }
}

第四步:属性配置
最后,我们可以根据个人习惯配置一下 application.properties 文件,如下:

server.port=3721

至此,我们的 Hystrix 监控环境就搭建好了;
SpringCloud学习与搭建_第7张图片
Hystrix 仪表盘工程已经创建好了,现在我们需要有一个服务,让这个服务提供一个路径为/actuator/hystrix.stream 接口,然后就可以使用 Hystrix 仪表盘来对该服务进行监控了;
我们改造消费者服务,让其能提供/actuator/hystrix.stream 接口,步骤如下:
1、消费者项目需要有 hystrix 的依赖:

		<!--SpringCloud集成eureka客户端的起步依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

2、需要有一个 spring boot 的服务监控依赖:

		<!--Spring Boot提供的一个服务健康检查监控的依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

3、配置文件需要配置 spring boot 监控端点的访问权限:

#springboot的监控端点访问权限,*表示所有的访问端点都允许访问
management.endpoints.web.exposure.include=*

这个是用来暴露 endpoints 的,由于 endpoints 中会包含很多敏感信息,除了 health 和 info 两个支持直接访问外,其他的默认不能直接访问,所以我们让它都能访问,或者指定:

management.endpoints.web.exposure.include=hystrix.stream

4、访问入口 http://localhost:8081/actuator/hystrix.stream

注意:这里有一个细节需要注意,要访问/hystrix.stream 接口,首先得访问consumer 工程中的任意一个其他接口,否则直接访问/hystrix.stream 接口时 会输出出一连串的 ping: ping: …,先访问consumer 中的任意一个其他接口,然后再访问/hystrix.stream 接口即可;
出现下图所示则是正常:
SpringCloud学习与搭建_第8张图片
5.http://localhost:8081/actuator/hystrix.stream地址作为要监控的地址:
如图下:
SpringCloud学习与搭建_第9张图片
即可成功看到仪表盘监控数据:
SpringCloud学习与搭建_第10张图片

5-6 Hystrix 仪表盘监控数据解读

SpringCloud学习与搭建_第11张图片

第六章:声明式服务消费 Feign

6-1 Feign 是什么

Feign 是 Netflix 公司开发的一个声明式的 REST 调用客户端;
Ribbon 负载均衡、Hystrix 服务熔断是我们 Spring Cloud 中进行微服务开非常基础的组件,在使用的过程中我们也发现它们一般都是同时出现的,而配置也都非常相似,每次开发都有很多相同的代码,因此 Spring Cloud 基于 Netflix Feign 整合了 Ribbon 和 Hystrix 两个组件,让我们的开发工作变得加简单,就像 Spring Boot 是对 Spring+SpringMVC 的简化一样,SpringCloud Feign对 Ribbon 负载均衡、Hystrix 服务熔断进行简化,在其基础上进行了一步的封装,不仅在配置上大大简化了开发工作,同时还提供了一种声明式的Web 服务客户端定义方式;

6-2 使用 Feign 实现消费者

使用 Feign 实现消费者,我们通过下面步骤进行:
第一步:创建普通 Spring Boot 工程
首先我们来创建一个普通的 Spring Boot 工程,取名为:
05-springcloud-service-feign;
第二步:添加依赖
要 添 加 的 依 赖 主 要 是 spring-cloud-starter-netflix-eureka-client 和spring-cloud-starter-feign,如下:

<dependency>
<groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-feign</artifactId>
 <version>1.4.5.RELEASE</version>
</dependency>
	<!--Spring Cloud 熔断器起步依赖--> 
<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-hystrix</artifactId>
 <version>1.4.5.RELEASE</version>
</dependency>

第三步:添加注解
在项目入口类上添加@EnableFeignClients 注解表示开启 Spring Cloud Feign的支持功能;
第四步:声明服务
定义一个 HelloService 接口,通过@FeignClient 注解来指定服务名称,进而绑定服务,然后再通过 SpringMVC 中提供的注解来绑定服务提供者提供的接口,如下:

/**
 * 使用feign的客户端注解绑定远程服务的名称
 * 远程服务的名称可以大写,也可以小写
 *
 */
@FeignClient(name="01-springcloud-service-provider", /*fallback = MyFallback.class*/ fallbackFactory = MyFallbackFactory.class)
public interface HelloService {

    /**
     * 声明一个方法,这个方法就是远程的服务提供者提供的那个方法
     *
     * @return
     */
    @RequestMapping("/service/hello")
    public String hello();
}

这相当于绑定了一个名叫 01-springcloud-service-provider (这里
01-springcloud-service-provider 大小写 01-SPRINGCLOUD-SERVICE-PROVIDER都可以 ) 的服务提供者提供的/service/hello 接口;
我们服务提供者提供的接口如下:

@GetMapping("/service/hello")
public String hello() {
 	System.out.println("服务提供者 1。。。。。。。");
return "Hello, Spring Cloud,Provider 1"; }

第五步:在Controller 中调用服务
接着来创建一个 Controller 来调用上面的服务,如下:

@RestController
public class FeignController {

    @Autowired
    private HelloService helloService;

    @RequestMapping("/web/hello")
    public String hello () {

        //调用声明式的接口方法,实现对远程服务的调用
        return helloService.hello();

    }
}

第六步:属性配置

server.port=8082

#配置服务的名称
spring.application.name=05-springcloud-service-feign

#配置eureka注册中心地址
eureka.client.service-url.defaultZone=http://eureka8761:8761/eureka/,http://eureka8762:8762/eureka/

#开启hystrix功能
feign.hystrix.enabled=true

第七步:测试
依次启动注册中心、服务提供者和 feign 实现服务消费者,然后访问如下地址:http://localhost:8082/web/hello

6-3 使用 Feign 实现消费者的测试

负载均衡: 我们知道,Spring Cloud 提供了 Ribbon 来实现负载均衡,使用 Ribbo 直接注入一个 RestTemplate 对象即可,RestTemplate 已经做好了负载均衡的配置;在 Spring Cloud 下,使用 Feign 也是直接可以实现负载均衡的,定义一个注解有@FeignClient 注解的接口,然后使用@RequestMapping 注解到方法上映射远程的 REST 服务,此方法就默认做好了负载均衡,默认为轮询策略。
倘若要改变负载均衡策略,可以自定义配置文件,重写IRule接口,自定义配置文件如下:

package com.wkcto.springcloud.config;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import com.netflix.loadbalancer.RetryRule;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration //等价于一个Spring applicationContext.xml配置文件
public class BeanConfig {

    /**
     * 覆盖掉原来ribbon默认的轮询负载均衡策略
     *
     *
     * @return
     */
    @Bean
    public IRule iRule() {
        return new RandomRule(); //采用随机的负载均衡策略
        //return new RetryRule(); //采用重试的负载均衡策略
    }
}

服务熔断:
1、在 application.properties 文件开启 hystrix 功能

feign.hystrix.enabled=true

2、指定熔断回调逻辑

@FeignClient(name="01-springcloud-service-provider", fallback = 
MyFallback.class)

3.创建MyFallback类并实现接口

@Component
public class MyFallback implements HelloService {
 @Override
 public String hello() {
 	return "发生异常了";
 } 
}

4、服务熔断获取异常信息:
为@FeignClient 修饰的接口加上 fallback 方法可以实现远程服务发生异常后进行服务的熔断,但是不能获取到远程服务的异常信息,如果要获取远程服务的异常信息,怎么办?此时可以使用 fallbackFactory:
指定fallbackFactory:

@FeignClient(name="01-springcloud-service-provider", fallbackFactory= 
MyFallbackFactory.class)
@Component
public class MyFallbackFactory implements FallbackFactory<HelloService> {
 @Override
 public HelloService create(Throwable throwable) {
 	 return new HelloService() {
	 @Override
	 public String hello() {
	 	return throwable.getMessage();
		 }
	 };
 } 
}

第七章:API 网关 Zuul

7-1 Spring Cloud 的 Zuul 是什么

我们首先来看一个微服务架构图:
SpringCloud学习与搭建_第12张图片
在上面的架构图中,我们的服务包括:内部服务 Service A 和内部服务 Service B,这两个服务都是集群部署,每个服务部署了 3 个实例,他们都会通过 Eureka Server 注册中心注册与订阅服务,而 Open Service 是一个对外的服务,也是集群部署,外部调用方通过负载均衡设备调用 Open Service 服务,比如负载均衡使用 Nginx,这样的实现是否合理,或者是否有更好的实现方式呢?接下来我们主要围绕该问题展开讨论。
1、如果我们的微服务中有很多个独立服务都要对外提供服务,那么我们要如何去管理这些接口?特别是当项目非常庞大的情况下要如何管理?
2、在微服务中,一个独立的系统被拆分成了很多个独立的服务,为了确保安全,权限管理也是一个不可回避的问题,如果在每一个服务上都添加上相同的权限验证代码来确保系统不被非法访问,那么工作量也就太大了,而且维护也非常不方便。
为了解决上述问题,微服务架构中提出了 API 网关的概念,它就像一个安检站一样,所有外部的请求都需要经过它的调度与过滤,然后 API 网关来实现请求路由、负载均衡、权限验证等功能;
那么 Spring Cloud 这个一站式的微服务开发框架基于 Netflix Zuul 实现了Spring Cloud Zuul,采用 Spring Cloud Zuul 即可实现一套 API 网关服务。

7-2 使用 Zuul 构建 API 网关

1、创建一个普通的 Spring Boot 工程名为 06-springcloud-api-gateway,然后添加相关依赖,这里我们主要添加两个依赖 zuul 和 eureka 依赖:

<!--添加 spring cloud 的 zuul 的起步依赖--> 
<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<!--添加 spring cloud 的 eureka 的客户端依赖--> 
<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

2、在入口类上添加@EnableZuulProxy 注解,开启 Zuul 的 API 网关服务功能:

@EnableZuulProxy //开启Zuul的API网关服务功能
@SpringBootApplication
public class ZuulApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication.class, args);
    }
}

3、在 application.properties 文件中配置路由规则:

#配置服务内嵌的 Tomcat 端口
server.port=8080
#配置服务的名称
spring.application.name=06-springcloud-api-gateway
#配置路由规则
zuul.routes.api-wkcto.path=/api-wkcto/**
zuul.routes.api-wkcto.serviceId=05-springcloud-service-feign
#配置 API 网关到注册中心上,API 网关也将作为一个服务注册到 eureka-server 上
eureka.client.service-url.defaultZone=http://eureka8761:8761/eureka/,http:/
/eureka8762:8762/eureka/

以上配置,我们的路由规则就是匹配所有符合/api-wkcto/**的请求,只要路径中带有/api-wkcto/都将被转发到 05-springcloud-service-feign 服务上,至于05-springcloud-service-feign 服务的地址到底是什么则由 eureka-server 注册中心去分析,我们只需要写上服务名即可。
以我们目前搭建的项目为例,请求 http://localhost:8080/api-wkcto/web/hello 接口则相当于请求 http://localhost:8082/web/hello(05-springcloud-service-feign 服务的地址为 http://localhost:8082/web/hello),路由规则中配置的 api-wkcto 是路由的名字,可以任意定义,但是一组 path 和serviceId 映射关系的路由名要相同。如果以上测试成功,则表示们的 API 网关服务已经构建成功了,我们发送的符合路由规则的请求将自动被转发到相应的服务上去处理。

7-3 使用 Zuul 进行请求过滤

我们知道 Spring cloud Zuul 就像一个安检站,所有请求都会经过这个安检站,所以我们可以在该安检站内实现对请求的过滤,下面我们以一个权限验证案例说这一点:
1、我们定义一个过滤器类并继承自 ZuulFilter,并将该 Filter 作为一个Bean:

@Component
public class AuthFilter extends ZuulFilter {
   @Override
   public String filterType() {
     return "pre";
   }
   @Override
   public int filterOrder() {
     return 0;
   }
   @Override
   public boolean shouldFilter() {
     return true;
   }
   @Override
   public Object run() throws ZuulException {
    RequestContext ctx = RequestContext.getCurrentContext();
    HttpServletRequest request = ctx.getRequest();
    String token = request.getParameter("token");
    if (token == null) {
      ctx.setSendZuulResponse(false);
      ctx.setResponseStatusCode(401);
      ctx.addZuulResponseHeader("contenttype","text/html;charset=utf-8");
      ctx.setResponseBody("非法访问");
    }
    return null;
  } 
}

(1)filterType 方法的返回值为过滤器的类型,过滤器的类型决定了过滤器在哪个生命周期执行,pre 表示在路由之前(也就是还没有进行路由转发)执行过滤器,其他值还有 post、error、route 和 static,当然也可以自定义。
(2)filterOrder 方法表示过滤器的执行顺序,当过滤器很多时,我们可以通过该方法的返回值来指定过滤器的执行顺序。
(3)shouldFilter 方法用来判断过滤器是否执行,true 表示执行,false 表示不执行。
(4)run 方法则表示过滤的具体逻辑,这里举例:如果请求地址中携带了 token 参数的话,则认为是合法请求,否则为非法请求,如果是非法请求的话,首先设置ctx.setSendZuulResponse(false); 表示不对该请求进行路由,然后设置响应码和响应值。这个 run 方法的返回值目前暂时没有任何意义,可以返回任意值。
2、通过 http://localhost:8080/api-wkcto/web/hello 地址访问,就会被过滤器过滤。则会打印:非法访问,因为请求地址未携带参数。

7-4 Zuul 的路由规则

(1) 在前面的例子中:

#配置路由规则
zuul.routes.api-wkcto.path=/api-wkcto/**
zuul.routes.api-wkcto.serviceId=05-springcloud-service-feign

当访问地址符合/api-wkcto/**规则的时候,会被自动定位到05-springcloud-service-feign 服务上,不过两行代码有点麻烦,还可以简化为:

zuul.routes.05-springcloud-service-feign=/api-wkcto/**

zuul.routes 后面跟着的是服务名,服务名后面跟着的是路径规则,这种配置方式更简单。

(2) 如果映射规则我们什么都不写,系统也给我们提供了一套默认的配置规则默认的配置规则如下:

#默认的规则
zuul.routes.05-springcloud-service-feign.path=/05-springcloud-service-feign/**
zuul.routes.05-springcloud-service-feign.serviceId=05-springcloud-service-feign

(3) 默认情况下,Eureka 上所有注册的服务都会被 Zuul 创建映射关系来进行路由。但是对于我这里的例子来说,我想:
05-springcloud-service-feign 提供服务;而 01-springcloud-service-provider 作为服务提供者只对服务消费者提供服务,不对外提供服务。
如果使用默认的路由规则,则 Zuul 也会自动为01-springcloud-service-provider 创建映射规则,这个时候我们可以采用如下方式来让 Zuul 跳过 01-springcloud-service-provider 服务,不为其创建路由规则:

#忽略掉服务提供者的默认规则
zuul.ignored-services=01-springcloud-service-provider

不给某个服务设置映射规则,这个配置我们可以进一步细化,比如说我不想给 /hello 接口路由,那我们可以按如下方式配置:

#忽略掉某一些接口路径
zuul.ignored-patterns=/**/hello/**

此外,我们也可以统一的为路由规则增加前缀,设置方式如下:

#配置网关路由的前缀
zuul.prefix=/myapi

此时我们的访问路径就变成了 http://localhost:8080/myapi/api-wkcto/web/hello
若没有设置路由规则,则需要使用默认的路由规则。

(4) 一般情况下 API 网关只是作为各个微服务的统一入口,但是有时候我们可能也需要在 API 网关服务上做一些特殊的业务逻辑处理,那么我们可以让请求到达 API 网关后,再转发给自己本身,由 API 网关自己来处理,那么我们可以进行如下的操作:
在 06-springcloud-api-gateway 项目中新建如下 Controller:

	@RestController
	public class GateWayController {
 		@RequestMapping("/api/local")
		public String hello() {
 			return "exec the api gateway.";
 		}
    }

然后在 application.properties 文件中配置:

zuul.routes.gateway.path=/gateway/**
zuul.routes.gateway.url=forward:/api/local

这表示当 http://localhost:8080/gateway/api/local 时请求将会被转发到本身。

7-5 Zuul 的异常处理

Spring Cloud Zuul 对异常的处理是非常方便的,但是由于 Spring Cloud 处于迅速发展中,各个版本之间有所差异,本案例是以 Finchley.RELEASE 版本为例,来说明 Spring Cloud Zuul 中的异常处理问题。首先我们来看一张官方给出的 Zuul 请求的生命周期图:
SpringCloud学习与搭建_第13张图片
1.正常情况下所有的请求都是按照 pre、route、post 的顺序来执行,然后由 post返回 response
2.在 pre 阶段,如果有自定义的过滤器则执行自定义的过滤器
3.pre、routing、post 的任意一个阶段如果抛异常了,则执行 error 过滤器我们可以有两种方式统一处理异常:
(1)、禁用 zuul 默认的异常处理 SendErrorFilter 过滤器,然后自定义我们自己的Errorfilter 过滤器。在配置文件中添加如下配置禁用默认异常处理过滤器。

zuul.SendErrorFilter.error.disable=true

自定义Error过滤器:

@Component
public class ErrorFilter extends ZuulFilter {

    private static final Logger logger = LoggerFactory.getLogger(ErrorFilter.class);

    @Override
    public String filterType() {
        return "error";
    }

    @Override
    public int filterOrder() {
        return 1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        try {
            RequestContext context = RequestContext.getCurrentContext();
            ZuulException exception = (ZuulException)context.getThrowable();

            logger.error("进入系统异常拦截:", exception);

            HttpServletResponse response = context.getResponse();
            response.setContentType("application/json; charset=utf8");
            response.setStatus(exception.nStatusCode);

            PrintWriter writer = null;
            try {
                writer = response.getWriter();
                writer.print("{code:"+ exception.nStatusCode +", message:\""+ exception.getMessage() +"\"}");
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if(writer!=null){
                    writer.close();
                }
            }
        } catch (Exception e) {
            ReflectionUtils.rethrowRuntimeException(e);
        }
        return null;
    }
}

(2)自定义全局 error 错误页面

@RestController
public class ErrorHandlerController implements ErrorController {

    /**
     * 出异常后进入该方法,交由下面的方法处理
     */
    @Override
    public String getErrorPath() {
        return "/error";
    }

    @RequestMapping("/error")
    public Object error() {
    	//这里进行错误逻辑处理
        RequestContext ctx = RequestContext.getCurrentContext();
        ZuulException exception = (ZuulException)ctx.getThrowable();
        return exception.nStatusCode + "--" + exception.getMessage();
    }
}

你可能感兴趣的:(java,分布式,微服务,spring,cloud)