SpringCloud

  • Post author:
  • Post category:其他


环境

  1. spring 官网的 sts 3.9.2,就是有spring 相关插件的eclipse;
  2. apache maven 3.5.4,配置阿里云镜像
  3. jdk1.8
  4. Spring Cloud Finchley版本
  5. Spring Boot 2.0.3

Spring Cloud 组件

服务注册中心、服务、断路器、配置中心

服务的注册与发现 – Eureka

使用 Eureka 来实现服务注册中心、服务提供者和服务消费者。

服务注册中心

一个服务注册中心,所有的服务都在注册中心注册,负载均衡也是通过在注册中心注册的服务来使用一定策略来实现。

1.新建一个 Spring Boot 工程,用来管理服务,elipse右键 -> new -> Spring Starter Project :

clipboard.png

点击 next ,选择 Cloud Discovery 下的 Eureka Server 组件

clipboard.png

生成的 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>

    <groupId>com.beigege.cloud</groupId>
    <artifactId>eureka-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>eureka-server</name>
    <description>spring cloud学习</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>

    <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.RELEASE</spring-cloud.version>
    </properties>

    <dependencies>
        <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>
        </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>

2.使用 @EnableEurekaServer 来说明项目是一个 Eureka:

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

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

3.修改配置文件,配置文件,可以是 application.properties 或 application.yml,这里改后缀,使用 yml,两种格式内容都是一样的 只是格式不同 application.yml:

server:
  port: 8761 #服务端口

spring:
  application:
    name: eurka-server #服务应用名称

eureka:
  instance:
    hostname: localhost
  client:
    registerWithEureka: false #是否将自己注册到Eureka Server,默认为true
    fetchRegistry: false #是否从Eureka Server获取注册信息,默认为true
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #服务注册的 URL

默认 eureka server 也是一个 eureka client,registerWithEureka: false 和 fetchRegistry: false 来表明项目是一个 Eureka Server。

4.运行 EurekaServerApplication 类,启动项目,访问项目

http://localhost

:8761/

clipboard.png

服务提供者

1.新建项目名为 eureka-client 的 Spring Boot 项目,跟 Eureka Server 新建一样,只是在选择 Spring Boot 组件的时候,不选 Eureka Server ,选择 Eureka Client,这里为了测试,再添加一个 Web 下的 Web 组件:

clipboard.png

2.修改配置,application.yml:

server:
  port: 8762
  
spring:
  application:
    name: service-hi #服务之间的调用都是根据这个 name
    
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

3.添加 @EnableEurekaClient 注解,并添加一个测试接口 /hi:

@SpringBootApplication
@EnableEurekaClient
@RestController
public class EurekaClientApplication {

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

    @Value("${server.port}")
    String port;

    @RequestMapping("/hi")
    public String home(@RequestParam(value = "name", defaultValue = "beigege") String name) {
        return "hi " + name + " ,i am from port:" + port;
    }
}

4.启动 Eureka Server 和 Eureka Client 项目,访问

http://localhost

:8761 ,即eureka server 的网址:

clipboard.png

服务消费者 – Ribbon | Feign

Spring Cloud 两种调用服务的方式,ribbon + restTemplate,和 feign。

Ribbon

ribbon 在客户端实现了负载均衡。

启动 Eureka Server 和 Eureka Client,修改 Eureka Client 端口,再启动一个 Eureka Client 实例,相当于一个小的集群,访问localhost:8761

clipboard.png

1.新建一个 spring boot 工程,名称为 service-ribbon,创建过程和 eureka-client 一样,组件多选一个 Cloud Routing 下的 Ribbon,创建完成之后的 pom 文件依赖:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

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

application.yml 配置:

server:
  port: 8764
  
spring:
  application:
    name: service-ribbon
    
eureka: 
  client: 
    service-url: 
      defaultZone: http://localhost:8761/eureka/

ServiceRibbonApplication 加上 @EnableEurekaClient 注解

2.向 Spring 注入一个 bean: restTemplate,并添加 @LoadBalanced 注解,表明这个 restTemplate 开启负载均衡功能:

@SpringBootApplication
@EnableEurekaClient
public class ServiceRibbonApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServiceRibbonApplication.class, args);
    }
    
    @Bean
    @LoadBalanced
    RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

3.写一个接口用来调用之前的 service-hi 服务的 /hi 接口:

新建 service 层,名叫 HelloService,调用 /hi 接口,这里用服务名 SERVICE-HI 代替具体的 URL:

@Service
public class HelloService {

    @Autowired
    RestTemplate restTemplate;

    public String hiService(String name) {
        return restTemplate.getForObject("http://SERVICE-HI/hi?name="+name,String.class);
    }

}

新建 controller 层,调用 service 层:

@RestController
public class HelloControler {

    @Autowired
    HelloService helloService;
    @RequestMapping(value = "/hi")
    public String hi(@RequestParam String name){
        return helloService.hiService(name);
    }
}

4.启动 service-ribbon 工程,多次访问该工程的 /hi 接口,即 localhost:8764/hi?name=test:

hi test ,i am from port:8762

hi test ,i am from port:8763

上面交替显示,说明实现了负载均衡,ribbon 会在客户端发送请求时做一个负载均衡。

Feign

整合了 Ribbon,具有负载均衡的能力,整合了Hystrix,具有熔断的能力.

1.创建一个 spring boot工程,创建过程和 eureka-client 一样,多添加一个组件 Cloud Routing 下的 Feign,项目名叫 service-feign,创建完后的 pom 文件依赖:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <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-openfeign</artifactId>
        </dependency>

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

application.yml 配置文件:

server:
  port: 8765

spring:
  application:
    name: service-feign

eureka:
  client:
    serviceUrl: 
      defaultZone: http://localhost:8761/eureka/

SericeFeignApplication 上添加 @EnableEurekaClient 和 @EnableFeignClients 注解:

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class SericeFeignApplication {

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

2.写一个 SchedualServiceHi 接口,通过@ FeignClient(“服务名”),来指定调用哪个服务,@GetMapping(“接口名”),来向接口发送 Get 请求,@RequestParam 是请求参数:

@FeignClient(value = "service-hi")
public interface SchedualServiceHi {

    @GetMapping("/hi")
    String sayHiFromClientOne(@RequestParam(value = "name") String name);
    
}

写一个 controller 层测试 SchedualServiceHi 接口:

@RestController
public class HiController {
    
    @Autowired
    SchedualServiceHi schedualServiceHi;

    @GetMapping(value = "/hi")
    public String sayHi(@RequestParam String name) {
        return schedualServiceHi.sayHiFromClientOne( name );
    }

}

3.启动 service-feign,访问localhost:8765/hi?name=test 测试,下面交替显示,说明实现了负载均衡:

hi test ,i am from port:8762

hi test ,i am from port:8763

断路器 – Hystrix

链式调用服务,其中一个服务宕机,其他服务可能会跟着异常,发生雪崩,当对一个服务的调用失败次数到达一定阈值,断路器会打开,执行服务调用失败时的处理,避免连锁故障,上面已经启动:eureka-server、两个 eureka-client 实例、service-ribbon 和 service-feign,下面继续修改,分别在 service-ribbon 和 service-feign 上实现断路器。

service-ribbon + Hystrix

1.在 pom 文件中加入 Hystrix 依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

2.在程序的启动类 ServiceRibbonApplication 加 @EnableHystrix 注解开启Hystrix:

@SpringBootApplication
@EnableEurekaClient
@EnableHystrix
public class ServiceRibbonApplication {

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

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

}

3.添加熔断方法,改造 HelloService 类,在 hiService 方法上面添加 @HystrixCommand 注解,fallbackMethod 是熔断方法,当服务不可用时会执行,该方法,即 hiError 方法:

@Service
public class HelloService {

    @Autowired
    RestTemplate restTemplate;

    @HystrixCommand(fallbackMethod = "hiError")
    public String hiService(String name) {
        return restTemplate.getForObject("http://SERVICE-HI/hi?name="+name,String.class);
    }

    public String hiError(String name) {
        return "hi,"+name+",sorry,error!";
    }

}

重启 service-ribbon,访问 localhost:8764/hi?name=test

hi test ,i am from port:8763

关闭两个 eureka-client 实例,在访问 localhost:8764/hi?name=test,浏览器显示:

hi,test,sorry,error!

当服务不可用时,断路器会迅速执行熔断方法

service-feign + Hystrix

1.Feign 自带断路器,在D版本的Spring Cloud之后,它没有默认打开。需要在配置文件中配置打开:

feign: 
  hystrix: 
    enabled: true

2.在请求接口指定熔断实现类,fallback 指定熔断实现类:

@FeignClient(value ="service-hi",fallback = SchedualServiceHiHystric.class)
public interface SchedualServiceHi {
    
    @GetMapping("/hi")
    String sayHiFromClientOne(@RequestParam(value = "name") String name);

}

SchedualServiceHiHystric 类实现 SchedualServiceHi 接口:

@Component
public class SchedualServiceHiHystric implements SchedualServiceHi{

    @Override
    public String sayHiFromClientOne(String name) {
        return "sorry "+name;
    }

}

3.访问测试,重启 service-feign,访问 localhost:8765/hi?name=test:

sorry test

打开 eureka-client,再次访问 localhost:8765/hi?name=test:

hi test ,i am from port:8762

证明断路器起到作用,注意浏览器缓存。

路由网关 – Zuul

1.创建一个 spring boot 工程,名称为 service-zuul,创建过程和 eureka-client 一样,组件要多添加一个 Cloud Routing 下的 Zuul,pom 文件的依赖如下:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

2.在入口 application 中添加 @EnableZuulProxy 注解,开启路由:

@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy
public class ServiceZuulApplication {

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

application.yml 配置:

server:
  port: 8769
 
spring:
  application:
    name: service-zuul
    
eureka:
  client:
    serviceUrl: 
      defaultZone: http://localhost:8761/eureka/

添加路由

修改 application.yml 添加路由配置:

server:
  port: 8769
 
spring:
  application:
    name: service-zuul
    
eureka:
  client:
    serviceUrl: 
      defaultZone: http://localhost:8761/eureka/
      
zuul:
  routes:
    api-a: 
      path: /api-a/**
      service-id: service-ribbon
    api-b:
      path: /api-b/**
      service-id: service-feign

8769 端口的所有 api-a 请求会转发到 service-ribbon 工程,api-b 到 service-feign

启动 service-zuul,访问 localhost:8769/api-a/hi?name=test,浏览器显示:

hi test ,i am from port:8762

访问 localhost:8769/api-b/hi?name=test,浏览器显示:

hi test ,i am from port:8762

说明路由成功。

过滤器

新建自定义过滤器,继承 ZuulFilter:

@Component
public class MyFilter extends ZuulFilter {

    private static Logger log = LoggerFactory.getLogger(MyFilter.class);

    /*
     * filterType:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下:
     * pre:路由之前
     * routing:路由之时 
     * post: 路由之后 
     * error:发送错误调用 
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /*
     * filterOrder:过滤的顺序
     */
    @Override
    public int filterOrder() {
        return 0;
    }

    /*
     * shouldFilter:这里可以写逻辑判断,是否要过滤,本文true,永远过滤。
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    /*
     * run:过滤器的具体逻辑。可以很复杂,包括查sql,nosql去判断该请求到底有没有权限访问,
     * 这里是判断请求有没有 token 参数
     */
    @Override
    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) {
            log.warn("token is empty");
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(401);
            try {
                ctx.getResponse().getWriter().write("token is empty");
            }catch (Exception e){}

            return null;
        }
        log.info("ok");
        return null;
    }
}

重启 service-zuul 访问 localhost:8769/api-a/hi?name=test,浏览器显示

token is empty

加上 token 参数:localhost:8769/api-a/hi?name=test&token=666

hi test ,i am from port:8762

分布式配置中心

远程资源库

1.这里在

码云

注册一个账号,用来创建存储配置的资源库,在码云上创建一个名叫 SpringCloudConfig 项目:

clipboard.png

2.新建一个配置文件 config-client-dev.properties,也可以是 yml 配置文件,内容为 test = version 1,然后提交:

clipboard.png

clipboard.png

2.默认是 master 分支,新建一个分支,名字叫 aa:

clipboard.png

clipboard.png

修改 aa 分支中的 config-client-dev.properties 配置文件的 test 属性为 version 2:

clipboard.png

config-server

1.新建 spring boot 项目,组件选择 Cloud Config 中的 Config Server,pom 中的依赖:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>

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

在入口程序添加 @EnableConfigServer 注解:

@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {

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

application.yml 配置文件,如果是公开的不需要填写用户名密码:

server:
  port: 8888
  
spring:
  application:
    name: config-server
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/candy-boy/SpringCloudConfig  #配置git仓库地址,后面的.git可有可无 
          username:  #访问git仓库的用户名,码云登陆用户名
          password:  #访问git仓库的用户密码,码云登陆密码

2.访问测试,启动程序,访问配置文件,可以如下访问:

  • {application}/{profile}[/{label}]

    如 localhost:8888/config-client/dev,即访问 config-client-dev.properties,其中 {application} 就是 最后一道横线前面的 config-client,{profile} 是最后一道横线后面到点,即 dev,{label} 指的是资源库的分支,不填则为默认分支,刚创建的资源库,默认分支是 master,访问结果如下:

clipboard.png

其他访问方式如:

clipboard.png

/{application}/{profile}[/{label}]

/{application}-{profile}.yml

/{label}/{application}-{profile}.yml

/{application}-{profile}.properties

/{label}/{application}-{profile}.properties

如果是下面这样,可能是用户名或密码错误:

clipboard.png

3.测试其他分支,访问 aa 分支下的 config-client-dev.properties,localhost:8888/config-client/dev/aa

clipboard.png

config-client

待续….