# 最简单springcloud教程

返回:springcloud

# 第一篇:服务的注册与发现Eureka(Finchley版本)

# 一、spring-cloud简介

鉴于《史上最简单的Spring Cloud教程》很受读者欢迎,再次我特意升级了一下版本,目前支持的版本为Spring Boot版本2.0.3.RELEASE,Spring Cloud版本为Finchley.RELEASEFinchley版本的官方文档

spring cloud 为开发人员提供了快速构建分布式系统的一些工具,包括配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等。它运行环境简单,可以在开发人员的电脑上跑。另外说明spring cloud是基于springboot的,所以需要开发中对springboot有一定的了解,如果不了解的话可以看这篇文章:2小时学会springboot。另外对于“微服务架构” 不了解的话,可以通过搜索引擎搜索“微服务架构”了解下。

# 二、创建服务注册中心

返回最简单的教程

在这里,我还是采用Eureka作为服务注册与发现的组件,至于Consul 之后会出文章详细介绍。

# 2.1、首先创建一个maven主工程

返回最简单的教程

首先创建一个主Maven工程,在其pom文件引入依赖,spring Boot版本为2.0.3.RELEASE,Spring Cloud版本为Finchley.RELEASE。这个pom文件作为父pom文件,起到依赖版本控制的作用,其他module工程继承该pom。这一系列文章全部采用这种模式,其他文章的pom跟这个pom一样。再次说明一下,以后不再重复引入。代码如下:

Table 1. Release train Spring Boot compatibility
Release Train Boot Version
Greenwich 2.1.x
Finchley 2.0.x
Edgware 1.5.x
Dalston 1.5.x
<?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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.9.RELEASE</version><!--2.0.x:2.0.9.RELEASE,2.1.x:2.1.5.RELEASE,1.5.x:1.5.21.RELEASE-->
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.clm</groupId>
    <artifactId>htcloud</artifactId>
    <version>0.0.1</version>
    <!--打包方式-->
    <packaging>pom</packaging>
    <name>htcloud</name>
    <description>htcloud project for Spring Boot</description>

    <modules>
        <module>htserver</module>
        <module>htclient</module>
    </modules>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
<!--        <spring-cloud.version>Finchley.SR2</spring-cloud.version>-->
        <spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
<!--        <spring-cloud.version>Greenwich.SR1</spring-cloud.version>-->
    </properties>

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

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

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

</project>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

# 2.2、server

返回最简单的教程

创建完后的工程,其pom.xml继承了父pom文件,并引入spring-cloud-starter-netflix-eureka-server的依赖,代码如下:

<?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>
    <parent>
        <groupId>com.clm</groupId>
        <artifactId>htcloud</artifactId>
        <version>0.0.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.me</groupId>
    <artifactId>htserver</artifactId>
    <version>0.0.1</version>
    <packaging>jar</packaging>
    <name>htserver</name>
    <description>Demo project for Spring Boot</description>

<!--    <properties>-->
<!--        <java.version>1.8</java.version>-->
<!--        <spring-cloud.version>Greenwich.SR1</spring-cloud.version>-->
<!--    </properties>-->

    <dependencies>
<!--        <dependency>-->
<!--            <groupId>org.springframework.cloud</groupId>-->
<!--            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>-->
<!--        </dependency>-->

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>
    </dependencies>

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

</project>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# 2.3、启动一个服务注册中心

返回最简单的教程

只需要一个注解@EnableEurekaServer,这个注解需要在springboot工程的启动application类上加:

@SpringBootApplication
@EnableEurekaServer
public class HtserverApplication {

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

}
1
2
3
4
5
6
7
8
9

# 2.4、server配置

返回最简单的教程

eureka是一个高可用的组件,它没有后端缓存,每一个实例注册之后需要向注册中心发送心跳(因此可以在内存中完成),在默认情况下erureka server也是一个eureka client ,必须要指定一个 server。
eureka server的配置文件

spring.application.name=eureka-server
server.port=5027

eureka.instance.hostname=localhost
eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
#通过eureka.client.registerWithEureka:false和fetchRegistry:false来表明自己是一个eureka server.
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.instance.prefer-ip-address=true
1
2
3
4
5
6
7
8
9

# 三、创建一个服务提供者(eureka-client)

返回最简单的教程

当client向server注册时,它会提供一些元数据,例如主机和端口,URL,主页等。Eureka server 从每个client实例接收心跳消息。 如果心跳超时,则通常将该实例从注册server中删除。
创建过程同server类似,创建完pom.xml如下

<?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>
    <parent>
        <groupId>com.clm</groupId>
        <artifactId>htcloud</artifactId>
        <version>0.0.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.me</groupId>
    <artifactId>htclient</artifactId>
    <version>0.0.1</version>
    <packaging>jar</packaging>
    <name>htclient</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <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>
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
<!--            <version>1.18.8</version>-->
            <scope>provided</scope>
        </dependency>


    </dependencies>

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

</project>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

通过注解@EnableEurekaClient 表明自己是一个eurekaclient.

@SpringBootApplication
@EnableEurekaClient
public class HtclientApplication {

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

}
1
2
3
4
5
6
7
8
9

仅仅@EnableEurekaClient是不够的,还需要在配置文件中注明自己的服务注册中心的地址

spring.application.name=eureka-client
server.port=5028

eureka.client.serviceUrl.defaultZone=http://localhost:5027/eureka/
1
2
3
4

# 第二篇:服务消费者(rest+ribbon)(Finchley版本)

返回最简单的教程

了服务的注册和发现。在微服务架构中,业务都会被拆分成一个独立的服务,服务与服务的通讯是基于http restful的。Spring cloud有两种服务调用方式,一种是ribbon+restTemplate,另一种是feign。在这一篇文章首先讲解下基于ribbon+rest

# 一、ribbon简介

返回最简单的教程

Ribbon is a client side load balancer which gives you a lot of control over the behaviour of HTTP and TCP clients. Feign already uses Ribbon, so if you are using @FeignClient then this section also applies.
-----摘自官网 ribbon是一个负载均衡客户端,可以很好的控制htt和tcp的一些行为。Feign默认集成了ribbon。 ribbon 已经默认实现了这些配置bean:
IClientConfig ribbonClientConfig: DefaultClientConfigImpl IRule ribbonRule: ZoneAvoidanceRule IPing ribbonPing: NoOpPing ServerList ribbonServerList: ConfigurationBasedServerList ServerListFilter ribbonServerListFilter: ZonePreferenceServerListFilter ILoadBalancer ribbonLoadBalancer: ZoneAwareLoadBalancer

# 二、准备工作

这一篇文章基于上一篇文章的工程,启动eureka-server 工程;启动service-hi工程,它的端口为8762;将service-hi的配置文件的端口改为8763,并启动,这时你会发现:service-hi在eureka-server注册了2个实例,这就相当于一个小的集群。

# 三、建一个服务消费者

返回最简单的教程

重新新建一个spring-boot工程,取名为:service-ribbon; 在它的pom.xml继承了父pom文件,并引入了以下依赖:

<?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>
    <parent>
        <groupId>com.clm</groupId>
        <artifactId>htcloud</artifactId>
        <version>0.0.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.me</groupId>
    <artifactId>htconsumer</artifactId>
    <version>0.0.1</version>
    <packaging>jar</packaging>
    <name>htconsumer</name>
    <description>Demo project for Spring Boot</description>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-ribbon -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-ribbon</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-ribbon -->
<!--        <dependency>-->
<!--            <groupId>org.springframework.cloud</groupId>-->
<!--            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>-->
<!--            <version>2.1.1.RELEASE</version>-->
<!--        </dependency>-->

    </dependencies>

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

</project>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

在工程的配置文件指定服务的注册中心地址为http://localhost:8761/eureka/,程序名称为 service-ribbon,程序端口为5030。配置文件

在工程的启动类中,通过@EnableDiscoveryClient向服务中心注册;并且向程序的ioc注入一个bean: restTemplate;并通过@LoadBalanced注解表明这个restRemplate开启负载均衡的功能。

@SpringBootApplication
@EnableDiscoveryClient
@EnableEurekaClient
public class HtconsumerApplication {

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

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Slf4j
@Service
public class TestConsumerServiceImpl implements TestConsumerService {
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private LoadBalancerClient loadBalancerClient;
    @Override
    public String consumerHello(String name) {
//        ServiceInstance serviceInstance = loadBalancerClient.choose("eureka-client");
//        String url = "http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+"/test/sayHello?name="+name;
        //此处用应用名来获取服务接口
        return
restTemplate.getForObject("http://eureka-client/test/sayHello?name="+name,String.class);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RestController
@RequestMapping("/test")
@Slf4j
public class TestController {
    @Autowired
    private TestConsumerService testConsumerService;

    @GetMapping("/sayHello")
    public String sayHello(String name){

        return this.testConsumerService.consumerHello(name);
    }
}

我在RestTemplate的配置类里使用了 @LoadBalanced
@Component
public class RestTemplateConfig {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

再调用

Autowired
private RestTemplate restTemplate;
1
2

必须使用应用名作为代替ip:端口,
http://127.0.0.1:8080/msg
改成
http://应用名/msg

个服务注册中心,eureka server,端口为5027
service-hi工程跑了两个实例,端口分别为5028,5029,分别向服务注册中心注册 sercvice-ribbon端口为5030,向服务注册中心注册 当sercvice-ribbon通过restTemplate调用service-hi的hi接口时,因为用ribbon进行了负载均衡,会轮流的调用service-hi:5028和5029 两个端口的hi接口;

# 第三篇:服务消费者(Feign)(Finchley版本)

返回最简单的教程

# 一、Feign简介

Feign是一个声明式的伪Http客户端,它使得写Http客户端变得更简单。
使用Feign,只需要创建一个接口并注解。它具有可插拔的注解特性,可使用Feign 注解和JAX-RS注解。Feign支持可插拔的编码器和解码器。
Feign默认集成了Ribbon,并和Eureka结合,默认实现了负载均衡的效果。

简而言之:

  • Feign 采用的是基于接口的注解
  • Feign 整合了ribbon,具有负载均衡的能力
  • 整合了Hystrix,具有熔断的能力

# 二、准备工作(2)

继续用上一节的工程, 启动eureka-server,端口为8761; 启动service-hi 两次,端口分别为8762 、8773.

# 三、创建一个feign的服务

返回最简单的教程

新建一个spring-boot工程,取名为serice-feign,在它的pom文件引入Feign的起步依赖
spring-cloud-starter-feign、Eureka的起步依赖spring-cloud-starter-netflix-eureka-client、Web的起步依赖spring-boot-starter-web,代码如下:

<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>
    <parent>
        <groupId>com.clm</groupId>
        <artifactId>htcloud</artifactId>
        <version>0.0.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.me</groupId>
    <artifactId>htfeign</artifactId>
    <version>0.0.1</version>
    <packaging>jar</packaging>
    <name>htfeign</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

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

        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-feign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
            <version>1.4.7.RELEASE</version>
        </dependency>

    </dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

配置文件

spring.application.name=eureka-feign
server.port=5051

eureka.client.serviceUrl.defaultZone=http://localhost:5027/eureka/
1
2
3
4

在程序的启动类HtfeignApplication,加上@EnableFeignClients注解开启Feign的功能:

@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
@EnableFeignClients
public class HtfeignApplication {

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

}
1
2
3
4
5
6
7
8
9
10
11

定义一个feign接口,通过@ FeignClient(“服务名”),来指定调用哪个服务。
比如在代码中调用了eureka-client服务的“/test/sayHello”接口,代码如下:

@FeignClient(value = "eureka-client")
public interface TestService {
    @RequestMapping(value = "/test/sayHello",method = RequestMethod.GET)
    String sayHello(@RequestParam(value = "name") String name);
}
1
2
3
4
5

在Web层的controller层,对外暴露一个"/hi"的API接口,通过上面定义的Feign客户端SchedualServiceHi 来消费服务。代码如下:

@RestController
@RequestMapping("/test")
@Slf4j
public class TestController {

    @Autowired
    private TestService testService;

    @RequestMapping("/sayHello")
    public String sayHello(String name){
        return testService.sayHello(name);

    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 增强Feign,可传对象

返回最简单的教程

引入依赖

<!-- https://mvnrepository.com/artifact/com.moonciki.strongfeign/feign-httpclient -->
<!-- 10.3.X版本有问题 -->
<dependency>
    <groupId>com.moonciki.strongfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
    <version>10.2.3</version>
</dependency>
1
2
3
4
5
6
7

# 第四篇:断路器(Hystrix)

返回最简单的教程

在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以相互调用(RPC),在Spring Cloud可以用RestTemplate+Ribbon和Feign来调用。为了保证其高可用,单个服务通常会集群部署。由于网络原因或者自身的原因,服务并不能保证100%可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。

# 一、断路器简介

Netflix开源了Hystrix组件,实现了断路器模式,SpringCloud对这一组件进行了整合。 在微服务架构中,一个请求需要调用多个服务是非常常见的,如下图:

较底层的服务如果出现故障,会导致连锁故障。当对特定的服务的调用的不可用达到一个阀值(Hystrix 是5秒20次) 断路器将会被打开。 断路打开后,可以避免连锁故障,fallback方法可以直接返回一个固定值

  • 服务容错保护(hystrix服务降级)
  • 服务容错保护(Hystrix依赖隔离)
  • 服务容错保护(hystrix断路器)

# 断路器原理

我们来说说断路器的工作原理。当我们把服务提供者eureka-client中加入了模拟的时间延迟之后,在服务消费端的服务降级逻辑因为hystrix命令调用依赖服务超时,触发了降级逻辑,但是即使这样,受限于Hystrix超时时间的问题,我们的调用依然很有可能产生堆积。

这个时候断路器就会发挥作用,那么断路器是在什么情况下开始起作用呢?这里涉及到断路器的三个重要参数:快照时间窗、请求总数下限、错误百分比下限。这个参数的作用分别是:

快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。 请求总数下限:在快照时间窗内,必须满足请求总数下限才有资格根据熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用此时不足20次,即时所有的请求都超时或其他原因失败,断路器都不会打开。 错误百分比下限:当请求总数在快照时间窗内超过了下限,比如发生了30次调用,如果在这30次调用中,有16次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%下限情况下,这时候就会将断路器打开。 那么当断路器打开之后会发生什么呢?我们先来说说断路器未打开之前,对于之前那个示例的情况就是每个请求都会在当hystrix超时之后返回fallback,每个请求时间延迟就是近似hystrix的超时时间,如果设置为5秒,那么每个请求就都要延迟5秒才会返回。当熔断器在10秒内发现请求总数超过20,并且错误百分比超过50%,这个时候熔断器打开。打开之后,再有请求调用的时候,将不会调用主逻辑,而是直接调用降级逻辑,这个时候就不会等待5秒之后才返回fallback。通过断路器,实现了自动地发现错误并将降级逻辑切换为主逻辑,减少响应延迟的效果。

在断路器打开之后,处理逻辑并没有结束,我们的降级逻辑已经被成了主逻辑,那么原来的主逻辑要如何恢复呢?对于这一问题,hystrix也为我们实现了自动恢复功能。当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合,主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。

通过上面的一系列机制,hystrix的断路器实现了对依赖资源故障的端口、对降级策略的自动切换以及对主逻辑的自动恢复机制。这使得我们的微服务在依赖外部服务或资源的时候得到了非常好的保护,同时对于一些具备降级逻辑的业务需求可以实现自动化的切换与恢复,相比于设置开关由监控和运维来进行切换的传统实现方式显得更为智能和高效

# 二、准备工作(3)

这篇文章基于上一篇文章的工程,首先启动上一篇文章的工程,启动eureka-server 工程;启动service-hi工程,它的端口为5028\5029。

# 三、在ribbon使用断路器

改造serice-ribbon 工程的代码,首先在pox.xml文件中加入spring-cloud-starter-hystrix的起步依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
    <version>1.4.7.RELEASE</version>
</dependency>
1
2
3
4
5

在程序的启动类ServiceRibbonApplication 加@EnableHystrix或者@EnableCircuitBreaker注解开启Hystrix:

@SpringBootApplication
@EnableDiscoveryClient
@EnableEurekaClient
@EnableHystrix
public class HtconsumerApplication {

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

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

改造service方法

@Slf4j
@Service
public class TestConsumerServiceImpl implements TestConsumerService {
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @HystrixCommand(fallbackMethod = "sayError")
    @Override
    public String consumerHello(String name) {
//        ServiceInstance serviceInstance = loadBalancerClient.choose("eureka-client");
//        String url = "http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+"/test/sayHello?name="+name;
        //此处用应用名来获取服务接口
        return restTemplate.getForObject("http://eureka-client/test/sayHello?name="+name,String.class);
    }

    public String sayError(String name){
        return "sorry,"+name+"error";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

新增的错误回调方法的参数必须和调用服务的方法一样
而不是等待响应超时,这很好的控制了容器的线程阻塞。

# 四、Feign中使用断路器

返回最简单的教程

Feign是自带断路器的,在D版本的Spring Cloud中,它没有默认打开。需要在配置文件中配置打开它,在配置文件加以下代码:

基于service-feign工程进行改造,只需要在FeignClient的SchedualServiceHi接口的注解中加上fallback的指定类就行了:

@FeignClient(value = "eureka-client",fallback = TestServiceImpl.class)
public interface TestService {
    @RequestMapping(value = "/test/sayHello",method = RequestMethod.GET)
    String sayHello(@RequestParam(value = "name") String name);
}
1
2
3
4
5

SchedualServiceHiHystric需要实现SchedualServiceHi 接口,并注入到Ioc容器中,代码如下:

@Service
public class TestServiceImpl implements TestService {
    @Override
    public String sayHello(String name) {
        return "sorry!!!"+name;
    }
}
1
2
3
4
5
6
7
spring.application.name=nacos-consumer-hystrix
server.port=8050
feign.hystrix.enabled=true
spring.cloud.nacos.discovery.server-addr=localhost:8848
1
2
3
4

配置文件必须开启断路器

启动工程,验证断路器的工作。

# 五、Hystrix-Dashboard(断路器:Hystrix仪表盘)

返回最简单的教程

基于Feign 改造,service-ribbon的改造和这一样。 首选在pom.xml引入spring-cloud-starter-hystrix-dashboard的起步依赖:

<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-hystrix-dashboard -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
    <version>1.4.7.RELEASE</version>
</dependency>
1
2
3
4
5
6

主启动类加注解@EnableHystrixDashboard

@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
@EnableFeignClients
@EnableHystrixDashboard
public class HtfeignApplication {

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

}
1
2
3
4
5
6
7
8
9
10
11
12

打开浏览器:访问http://localhost:5051/hystrix,界面如下:

# 仪表盘应用

返回最简单的教程

pom文件修改,新增依赖

这是Hystrix Dashboard的监控首页,该页面中并没有具体的监控信息。从页面的文字内容中我们可以知道,Hystrix Dashboard共支持三种不同的监控方式,依次为:

默认的集群监控:通过URLhttp://turbine-hostname:port/turbine.stream开启,实现对默认集群的监控。 指定的集群监控:通过URLhttp://turbine-hostname:port/turbine.stream?cluster=[clusterName]开启,实现对clusterName集群的监控。 单体应用的监控:通过URLhttp://hystrix-app:port/hystrix.stream开启,实现对具体某个服务实例的监控。

前两者都对集群的监控,需要整合Turbine才能实现,这部分内容我们将在下一篇中做详细介绍。在本节中,我们主要实现对单个服务实例的监控,所以这里我们先来实现单个服务实例的监控。

既然Hystrix Dashboard监控单实例节点需要通过访问实例的/hystrix.stream接口来实现,自然我们需要为服务实例添加这个端点,而添加该功能的步骤也同样简单,只需要下面两步:

pom文件修改

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-actuator -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
            <version>1.4.7.RELEASE</version>
        </dependency>
1
2
3
4
5
6
7
8
9
10

启动类开启dashboard

@SpringCloudApplication
@EnableHystrixDashboard
@EnableFeignClients
public class NacosConsumerHystrixApplication {

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

    @Bean
    public ServletRegistrationBean getServlet(){
        HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
        ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
        registrationBean.setLoadOnStartup(1);
        registrationBean.addUrlMappings("/actuator/hystrix.stream");
        registrationBean.setName("HystrixMetricsStreamServlet");
        return registrationBean;
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

同时需要增加一个servlet的返回。否则仪表盘出不来。记住。

# Hystrix监控数据聚合

返回最简单的教程

通过Hystrix Dashboard,我们可以方便的查看服务实例的综合情况,比如:服务调用次数、服务调用延迟等。但是仅通过Hystrix Dashboard我们只能实现对服务当个实例的数据展现,在生产环境我们的服务是肯定需要做高可用的,那么对于多实例的情况,我们就需要将这些度量指标数据进行聚合。下面,在本篇中,我们就来介绍一下另外一个工具:Turbine

引入Turbine来对服务的Hystrix数据进行聚合展示。这里我们将分别介绍两种聚合方式。

<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-turbine -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-turbine</artifactId>
            <version>1.4.7.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-actuator -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
1
2
3
4
5
6
7
8
9
10
11
spring.application.name=nacos-turbine
server.port=8091
management.server.port=8093

spring.cloud.nacos.discovery.server-addr=localhost:8848

turbine.app-config=nacos-consumer-hystrix
turbine.cluster-name-expression="default"
turbine.combine-host-port=true
1
2
3
4
5
6
7
8
9
  • turbine.app-config

参数指定了需要收集监控信息的服务名;

  • turbine.cluster-name-expression

参数指定了集群名称为default,当我们服务数量非常多的时候,可以启动多个Turbine服务来构建不同的聚合集群,而该参数可以用来区分这些不同的聚合集群,同时该参数值可以在Hystrix仪表盘中用来定位不同的聚合集群,只需要在Hystrix Stream的URL中通过cluster参数来指定;

  • turbine.combine-host-port参数设置为true

可以让同一主机上的服务通过主机名与端口号的组合来进行区分,默认情况下会以host来区分不同的服务,这会使得在本地调试的时候,本机上的不同服务聚合成一个服务来统计。

# 通过消息代理收集聚合

Spring Cloud在封装Turbine的时候,还实现了基于消息代理的收集实现。所以,我们可以将所有需要收集的监控信息都输出到消息代理中,然后Turbine服务再从消息代理中异步的获取这些监控信息,最后将这些监控信息聚合并输出到Hystrix Dashboard中。通过引入消息代理,我们的Turbine和Hystrix Dashoard实现的监控架构可以改成如下图所示的结构:

# 第五篇:路由网关(zuul)(Finchley版本)

在微服务架构中,需要几个基础的服务治理组件,包括服务注册与发现、服务消费、负载均衡、断路器、智能路由、配置管理等,由这几个基础组件相互协作,共同组建了一个简单的微服务系统。一个简答的微服务系统如下图:

在Spring Cloud微服务系统中,一种常见的负载均衡方式是,客户端的请求首先经过负载均衡(zuul、Ngnix),再到达服务网关(zuul集群),然后再到具体的服。,服务统一注册到高可用的服务注册中心集群,服务的所有的配置文件由配置服务管理(下一篇文章讲述),配置服务的配置文件放在git仓库,方便开发人员随时改配置。

# 一、Zuul简介

Zuul的主要功能是路由转发和过滤器。路由功能是微服务的一部分,比如/api/user转发到到user服务,/api/shop转发到到shop服务。zuul默认和Ribbon结合实现了负载均衡的功能。

zuul有以下功能:

  • Authentication
  • Insights
  • Stress Testing
  • Canary Testing
  • Dynamic Routing
  • Service Migration
  • Load Shedding
  • Security
  • Static Response handling
  • Active/Active traffic management

# 二、准备工作(4)

继续使用上一节的工程。在原有的工程上,创建一个新的工程。

# 三、创建service-zuul工程

返回最简单的教程

其pom.xml文件如下:

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
            <version>1.4.7.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-zuul -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zuul</artifactId>
            <version>1.4.7.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-zuul -->
<!--        <dependency>-->
<!--            <groupId>org.springframework.cloud</groupId>-->
<!--            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>-->
<!--            <version>2.1.1.RELEASE</version>-->
<!--        </dependency>-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

在其入口applicaton类加上注解@EnableZuulProxy,开启zuul的功能:

@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient
@EnableDiscoveryClient
public class HtzuulApplication {

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

}
1
2
3
4
5
6
7
8
9
10
11

配置文件:

spring.application.name=eureka-zuul
server.port=5052
eureka.client.serviceUrl.defaultZone=http://localhost:5027/eureka/

zuul.routes.api-a.path=/api-a/**
zuul.routes.api-a.service-id=eureka-ribbon
zuul.routes.api-b.path=/api-b/**
zuul.routes.api-b.service-id=eureka-feign
1
2
3
4
5
6
7
8

首先指定服务注册中心的地址为http://localhost:5027/eureka/,服务的端口为5053,服务名为service-zuul;以/api-a/ 开头的请求都转发给service-ribbon服务;以/api-b/开头的请求都转发给service-feign服务;

# 四、服务过滤

返回最简单的教程

zuul不仅只是路由,并且还能过滤,做一些安全验证。继续改造工程;

@Slf4j
@Component
public class TestFilter 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();
        log.info("{}>>>{}",request.getMethod(),request.getRequestURL().toString());
        Object token = request.getParameter("token");
        if(token==null){
            log.error("token is empty");
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED);
            try {
                ctx.getResponse().getWriter().write("token is empty");

            } catch (Exception e){
                return null;
            }
        }
        log.info("ok");
        return null;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

filterType:
返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下:

  • pre:路由之前
  • routing:路由之时
  • post: 路由之后
  • error:发送错误调用
  • filterOrder:过滤的顺序
  • shouldFilter:这里可以写逻辑判断,是否要过滤,本文true,永远过滤。
  • run:过滤器的具体逻辑。可用很复杂,包括查sql,nosql去判断该请求到底有没有权限访问。

# 第六篇:分布式配置中心(Spring-Cloud-Config)(Finchley版本)

返回最简单的教程

在上一篇文章讲述zuul的时候,已经提到过,使用配置服务来保存各个服务的配置文件。它就是Spring Cloud Config。

# 一、简介

在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在Spring Cloud中,有分布式配置中心组件spring cloud config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。在spring cloud config 组件中,分两个角色,一是config server二是config client

# 二、构建Config Server

父maven工程省略,父pom文件:

<?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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.me</groupId>
    <artifactId>htconfig</artifactId>
    <version>0.0.1</version>
    <name>htconfig</name>
    <description>Demo project for Spring Boot</description>

    <modules>
        <module>confg-server</module>
    </modules>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <!--        <spring-cloud.version>Finchley.SR2</spring-cloud.version>-->
        <spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
        <!--        <spring-cloud.version>Greenwich.SR1</spring-cloud.version>-->
    </properties>

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

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

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

</project>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

创建一个spring-boot项目,取名为config-server,其pom.xml如下:

<parent>
        <groupId>com.me</groupId>
        <artifactId>htconfig</artifactId>
        <version>0.0.1</version>
    </parent>
    <artifactId>confg-server</artifactId>
    <version>0.0.1</version>
    <name>confg-server</name>
    <packaging>jar</packaging>
    <description>Demo project for Spring Boot</description>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-config-server -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
<!--            <version>2.1.2.RELEASE</version>-->
        </dependency>
    </dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

在程序的入口Application类加上@EnableConfigServer注解开启配置服务器的功能,代码如下:

@SpringBootApplication
@EnableConfigServer
public class ConfgServerApplication {

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

}
1
2
3
4
5
6
7
8
9

需要在程序的配置文件application.properties文件配置以下:

spring.application.name=config-server
server.port=6027

#配置git仓库地址
spring.cloud.config.server.git.uri=https://github.com/forezp/SpringcloudConfig/
#配置仓库路径
spring.cloud.config.server.git.searchPaths=respo
#配置仓库的分支
spring.cloud.config.label=master
#访问git仓库的用户名
spring.cloud.config.server.git.username=
#访问git仓库的用户密码
spring.cloud.config.server.git.password=
#如果Git仓库为公开仓库,可以不填写用户名和密码,如果是私有仓库需要填写,本例子是公开仓库,放心使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14

证明配置服务中心可以从远程程序获取配置信息。

http请求地址和资源文件映射如下:

/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
1
2
3
4
5

# 三、构建一个config-client

返回最简单的教程

重新创建一个springboot项目,取名为config-client,其pom文件: