Spring Cloud 组件快速入门

组件简介

spring cloud 是基于 springboot 基础之上构建的一系列分布式微服务组件集,其组件主要包括:

  • Eureka 微服务注册发现中心
  • Feign 微服务远程调用
  • Ribbon 微服务客户端之间负载均衡器
  • Config server 微服务配置中心
  • Zuul 微服务调用网关
  • Hystrix 微服务调用熔断降级

spring cloud 提供的是一整套的开源分布式微服务解决方案。当然,随着业务发展的瓶颈,各家都会 “因地制宜” 自研对应的替代组件,像阿里的 Dubbo rpc 框架,以 zookeeper 为注册中心。携程的 Apollo 配置中心。腾讯的 tRPC,七彩石远程配置中心等等。相对而言,spring cloud 组件更易上手,有利于对分布式微服务解决方案有个大致的概念。

Eureka Server

Eureka 是 spring cloud 的微服务注册发现中心,所有的微服务只有服务注册中心注册后才能被其他服务远程调用,注册到 Eureka 的微服务会定时收到心跳信号,但服务异常时会被注册中心下线。下面我们首先实现一个简单服务注册发现中心。

首先搭建最基础的 spring boot 工程,引入 Eureka 组件依赖,最终的 maven 依赖如下 (pom.xml):

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>      <groupId>www.gaiserchan.test</groupId>      <artifactId>eureka-test</artifactId>      <packaging>jar</packaging>      <version>1.0-SNAPSHOT</version>      <parent>          <groupId>org.springframework.boot</groupId>          <artifactId>spring-boot-starter-parent</artifactId>          <version>2.1.10.RELEASE</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>              <version>2.1.10.RELEASE</version>              <exclusions>                  <exclusion>                      <groupId>org.springframework.boot</groupId>                      <artifactId>spring-boot-starter-tomcat</artifactId>                  </exclusion>              </exclusions>          </dependency>          <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-undertow</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-netflix-eureka-server</artifactId>              <version>2.1.4.RELEASE</version>          </dependency>      </dependencies>      <dependencyManagement>          <dependencies>              <dependency>                  <groupId>org.springframework.cloud</groupId>                  <artifactId>spring-cloud-dependencies</artifactId>                  <version>Greenwich.SR4</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>

项目工程结构如图所示

Eureka Server 配置文件

修改项目配置文件 (application.properties)

# 服务名称  spring.application.name=eureka-server  # 服务端口号  server.port=20200  # 注册中心实例地址  eureka.instance.hostname=localhost  # 是否注册到注册中心  eureka.client.register-with-eureka=false  # 屏蔽注册中心  eureka.client.fetch-registry=false

编写 spring boot 启动代码 (Application.java)

Application.java

package www.eureka.test;    import org.springframework.boot.SpringApplication;  import org.springframework.boot.autoconfigure.SpringBootApplication;  import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;  /**   * @author gaiserchen   * @date 2020.02.20   * spring boot 应用启动注解   */  @SpringBootApplication  @EnableEurekaServer  public class Application {      public static void main (String [] args){          SpringApplication.run (Application.class,args);      }  }

运行程序 'Run Application.main ()'

程序启动后访问 <http://localhost:20200> 可以看到如下界面,我们的第一个服务注册发现中心就启动成功了。

Eureka Server 启动结果

可以看到,此时注册中心是没有任何微服务注册到注册中心的,下面我们写一个简单的 web 服务,并注册到服务注册中心。

Eureka client

所有基于 spring boot 框架构建的 web 程序,只要注册到服务注册中心,都可以称之为提供 web 服务的 “Eureka client”。

同样,我们先搭建一个最简单的 spring boot 工程,引入 eureka client 组件,最终的 maven 依赖如下:

Eureka client 依赖文件

<?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>www.eureka.test</groupId>      <artifactId>eureka-client</artifactId>      <version>1.0-SNAPSHOT</version>      <parent>          <groupId>org.springframework.boot</groupId>          <artifactId>spring-boot-starter-parent</artifactId>          <version>2.1.10.RELEASE</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>              <version>2.1.10.RELEASE</version>              <exclusions>                  <exclusion>                      <groupId>org.springframework.boot</groupId>                      <artifactId>spring-boot-starter-tomcat</artifactId>                  </exclusion>              </exclusions>          </dependency>          <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-undertow</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-netflix-eureka-client</artifactId>              <version>2.1.4.RELEASE</version>          </dependency>      </dependencies>      <build>          <plugins>              <plugin>                  <groupId>org.springframework.boot</groupId>                  <artifactId>spring-boot-maven-plugin</artifactId>              </plugin>          </plugins>      </build>  </project>

工程项目结构如图所示:

Eureka client 配置文件

修改对应工程的配置文件 (application.properties), 填写对应的服务注册中心地址。

# 服务名称  spring.application.name=eureka-client  # 服务端口  server.port=20201  # 注册中心地址  eureka.client.serviceUrl.defaultZone=http://localhost:20200/eureka/

eureka client 的测试代码中我们主要编写 Application.java 和 Sample.java

所有 spring boot 工程的 Application.java 的代码都类似,主要的系统接口在 controller 层编写。

Application.java

package com.eureka.client;  import org.springframework.boot.SpringApplication;  import org.springframework.boot.autoconfigure.SpringBootApplication;  import org.springframework.cloud.netflix.eureka.EnableEurekaClient;  /**   * @author gaiserchen   * @date 2020.02.20   * spring boot 应用启动注解   */  @SpringBootApplication  @EnableEurekaClient  public class Application {      public static void main (String [] args) {          SpringApplication.run (Application.class, args);      }  }

controller/Sample.java, 这里的逻辑是,直接获取配置文件里的 spring.application.name 和 server.port 变量然后返回。

package com.eureka.client.controller;  import org.springframework.beans.factory.annotation.Value;  import org.springframework.web.bind.annotation.GetMapping;  import org.springframework.web.bind.annotation.RequestParam;  import org.springframework.web.bind.annotation.RestController;  /**   * @author gaiserchen   * @date 2020.02.20   * sample controller   */  @RestController  public class Sample {      @Value ("${server.port}")      String port;      @Value ("${spring.application.name}")      String applicationName;      @GetMapping (value = "/sample")      public String sample (@RequestParam (value = "name", defaultValue = "gaiserchen") String name) {          return "hello" + name + ", i am from:" + applicationName + ", and port is:" + port;      }  }

Eureka client 启动结果

同样,启动程序之后,我们直接访问 <http://localhost:20201>,其结果如图:

这时,我们再访问 Eureka Server 的地址 <http://localhost:20200>,可以看到此时的服务注册中心已经有注册的应用服务。

Feign 远程调用

在前面我们已经构建好一个 web 应用,并且已经注册到注册中心,下面我们可以通过 Feign 远程调用组件获取接口数据。

我们再次构建一个 springboot 应用,引入 feign 组件依赖,项目最终的 maven 如下:

Feign 依赖文件

<?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.eureka.feign</groupId>      <artifactId>eurekaFeign</artifactId>      <version>1.0-SNAPSHOT</version>      <parent>          <groupId>org.springframework.boot</groupId>          <artifactId>spring-boot-starter-parent</artifactId>          <version>2.1.10.RELEASE</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>              <version>2.1.10.RELEASE</version>              <exclusions>                  <exclusion>                      <groupId>org.springframework.boot</groupId>                      <artifactId>spring-boot-starter-tomcat</artifactId>                  </exclusion>              </exclusions>          </dependency>          <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-undertow</artifactId>          </dependency>          <dependency>              <groupId>org.springframework.cloud</groupId>              <artifactId>spring-cloud-starter-eureka</artifactId>              <version>1.4.7.RELEASE</version>          </dependency>          <dependency>              <groupId>org.springframework.cloud</groupId>              <artifactId>spring-cloud-starter-feign</artifactId>              <version>1.4.7.RELEASE</version>          </dependency>          <dependency>              <groupId>org.springframework.cloud</groupId>              <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>              <version>1.4.7.RELEASE</version>          </dependency>          <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-actuator</artifactId>          </dependency>      </dependencies>      <dependencyManagement>          <dependencies>              <dependency>                  <groupId>org.springframework.cloud</groupId>                  <artifactId>spring-cloud-dependencies</artifactId>                  <version>Greenwich.SR4</version>                  <type>pom</type>                  <scope>import</scope>              </dependency>          </dependencies>      </dependencyManagement>  </project>

工程项目结构如图所示:

Feign 配置文件

修改配置文件,因为是远程调用其他服务,所以当其他远程服务异常时需要有一定的服务熔断处理,所以这里启用了 hystrix 服务熔断组件。

application.properties

# 服务名称  spring.application.name=service-feign  # 服务端口  server.port=20202  # 服务注册中心地址  eureka.client.serviceUrl.defaultZone=http://localhost:20200/eureka/  # hystrix 服务熔断降级启用  feign.hystrix.enabled=true

这次我们需要编写 feign 工程自己的接口层逻辑,接口拿到请求值 name, 具体处理逻辑在 service 层。

controller/FeignController.java

package com.feign.test.controller;  import com.feign.test.service.FeignService;  import org.springframework.beans.factory.annotation.Autowired;  import org.springframework.web.bind.annotation.GetMapping;  import org.springframework.web.bind.annotation.RequestParam;  import org.springframework.web.bind.annotation.RestController;  /**   * @author gaiserchen   * @date 2020.02.02   * feign controller   */  @RestController  public class FeignController {      @Autowired      FeignService feignService;        @GetMapping ("/feign-consumer")      public String feignConsumer (@RequestParam (defaultValue = "gaiserchan") String name) {          return feignService.feignConsumer (name);      }  }

下面编写我们 service 层的业务处理逻辑,service 接口引入了 @FeignClient 注解,这个注解直接启用 Feign 组件,value 填需要调用的远程服务名 spring.application.name,fallback 是当远程服务调用异常需要做哪些异常处理,在具体业务逻辑中,直接拿上一步取到的 name 值远程调用 eureka-client 的 sample 接口。

service/FeignService.java

package com.feign.test.service;  import com.feign.test.service.impl.FeignHystricServiceImpl;  import org.springframework.cloud.openfeign.FeignClient;  import org.springframework.web.bind.annotation.GetMapping;  import org.springframework.web.bind.annotation.RequestParam;  /**   * @author gaiserchen   * @date 2020.02.20   * feign client   */  @FeignClient (value = "eureka-client", fallback = FeignHystricServiceImpl.class)  public interface FeignService {      @GetMapping (value = "/sample")      /**       description: feign service       @param  name       @return String string       */      String feignConsumer (@RequestParam (value = "name") String name);  }

在异常处理中,如果远程调用失败,直接返回,起到服务熔断作用。不会影响其他服务,避免出现系统 “雪崩”。

service/impl/FeignHystricServiceImpl.java

package com.feign.test.service.impl;  import com.feign.test.service.FeignService;  import org.springframework.stereotype.Component;  /**   * @author gaiserchen   * @date 2020.02.02   * feign fallback   */  @Component  public class FeignHystricServiceImpl implements FeignService {        @Override      public String feignConsumer (String name) {          return "sorry," + name;      }  }

Feign 启动结果

我们启动 Feign 程序之后,访问地址 <http://localhost:20202/feign-consumer?name=consumer>, 其返回结果如图,表示远程调用结果成功。

同时,feign 程序也需要注册到服务注册中心才能实现远程调用。我们可以查看服务注册中心的实例。

但当我们把之前启动的 eureka-client 服务停止时,再访问该地址,feign 会直接返回服务熔断的结果。

zuul 网关

zuul 是 spring cloud 的网关组件,起到路由代理转发的功能。同样构建一个 spring boot 工程,引入 zuul 组件依赖,其最终 maven 依赖如下:

zuul 依赖文件

<?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.eureka.test</groupId>      <artifactId>zuulTest</artifactId>      <version>1.0-SNAPSHOT</version>      <parent>          <groupId>org.springframework.boot</groupId>          <artifactId>spring-boot-starter-parent</artifactId>          <version>2.1.10.RELEASE</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>              <version>2.1.10.RELEASE</version>              <exclusions>                  <exclusion>                      <groupId>org.springframework.boot</groupId>                      <artifactId>spring-boot-starter-tomcat</artifactId>                  </exclusion>              </exclusions>          </dependency>          <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-undertow</artifactId>          </dependency>          <dependency>              <groupId>org.springframework.cloud</groupId>              <artifactId>spring-cloud-starter-eureka</artifactId>              <version>1.4.7.RELEASE</version>          </dependency>          <dependency>              <groupId>org.springframework.cloud</groupId>              <artifactId>spring-cloud-starter-zuul</artifactId>              <version>1.4.7.RELEASE</version>          </dependency>      </dependencies>      <dependencyManagement>          <dependencies>              <dependency>                  <groupId>org.springframework.cloud</groupId>                  <artifactId>spring-cloud-dependencies</artifactId>                  <version>Greenwich.SR4</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>

zuul 配置文件

zuul 网关主要起到请求代理和路由转发的作用,其转发规则就是在配置文件里配置,其配置如下,将所有 /eureka-client 下的请求转发到 eureka-client 这个服务,将所有 /service-feign 下的请求转发到 service-feign 这个服务。

# 服务名称  spring.application.name=eureka-zuul  # 服务端口号  server.port=20204  # 服务注册中心地址  eureka.client.serviceUrl.defaultZone=http://localhost:20200/eureka/  # zuul 网关代理规则  zuul.routes.eureka-client.path=/eureka-client/*  zuul.routes.eureka-client.serviceId=eureka-client  zuul.routes.service-feign.path=/service-feign/*  zuul.routes.service-feign.serviceId=service-feign

zuul 没有具体的接口和业务处理逻辑,所以只有 spring boot 启动代码

Application.java

package com.zuul.test;  import org.springframework.boot.SpringApplication;  import org.springframework.boot.autoconfigure.SpringBootApplication;  import org.springframework.cloud.netflix.eureka.EnableEurekaClient;  import org.springframework.cloud.netflix.zuul.EnableZuulProxy;  /**   * @author gaiserchen   * @date 2020.02.02   * zuul client   */    @EnableEurekaClient  @EnableZuulProxy  @SpringBootApplication  public class Application {      public static void main (String [] args) {          SpringApplication.run (Application.class, args);      }  }

zuul 启动结果

zuul 启动之后,我们直接访问地址 <http://localhost:20204/eureka-client/sample?name=zuul>, 其结果如下,可以看到请求直接转发到了 eureka-client 服务。

我们接着访问 <http://localhost:20204/service-feign/feign-consumer?name=zuuConsumer>,可以看到,请求直接转发到了 service-feign 服务。

总结

以上就是几个 spring cloud 组件的基本入门,其中 config server 是配置中心,可以结合 gitlab 或者 git 仓库使用。而负载均衡器 Ribbon 已经集成在 Feign 远程调用组件里,当你的微服务在注册中心大于等于 2 个时,默认会开启轮询机制做负载均衡。当然,负载均衡策略也是可以配置的。