spring cloud分析

访客 阅读:266 2020-02-18 16:41:00 评论:0

一、什么是Spring Cloud?

Spring提供了一系列工具,可以帮助开发人员迅速搭建分布式系统中的公共组件(比如:配置管理,服务发现,断路器,智能路由,微代理,控制总线,一次性令牌,全局锁,主节点选举, 分布式session, 集群状态)。协调分布式环境中各个系统,为各类服务提供模板性配置。使用Spring Cloud, 开发人员可以搭建实现了这些样板的应用,并且在任何分布式环境下都能工作得非常好,小到笔记本电脑, 大到数据中心和云平台。

Spring Cloud是基于Spring Boot的, 最适合用于管理Spring Boot创建的各个微服务应用。要管理分布式环境下的各个Spring Boot微服务,必然存在服务的注册问题。所以我们先从服务的注册谈起。既然是注册,必然有个管理注册中心的服务器,各个在Spring Cloud管理下的Spring Boot应用就是需要注册的client。

Spring Cloud使用erureka server,  然后所有需要访问配置文件的应用都作为一个erureka client注册上去。eureka是一个高可用的组件,它没有后端缓存,每一个实例注册之后需要向注册中心发送心跳,在默认情况下erureka server也是一个eureka client ,必须要指定一个 server。

1.创建Eureka Server

先创建一个spring boot的项目

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>me.cn</groupId> 
    <artifactId>test</artifactId> 
    <version>0.0.1-SNAPSHOT</version> 
    <packaging>jar</packaging> 
 
    <name>test</name> 
    <description>Demo project for Spring Boot</description> 
 
    <parent> 
        <groupId>org.springframework.boot</groupId> 
        <artifactId>spring-boot-starter-parent</artifactId> 
        <version>2.0.5.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>10</java.version> 
        <spring-cloud.version>Finchley.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> 
        <dependency> 
            <groupId>javax.xml.bind</groupId> 
            <artifactId>jaxb-api</artifactId> 
            <version>2.3.0</version> 
        </dependency> 
        <dependency> 
            <groupId>com.sun.xml.bind</groupId> 
            <artifactId>jaxb-impl</artifactId> 
            <version>2.3.0</version> 
        </dependency> 
        <dependency> 
            <groupId>org.glassfish.jaxb</groupId> 
            <artifactId>jaxb-runtime</artifactId> 
            <version>2.3.0</version> 
        </dependency> 
        <dependency> 
            <groupId>javax.activation</groupId> 
            <artifactId>activation</artifactId> 
            <version>1.1.1</version> 
        </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>
pom

 

需要一个注解@EnableEurekaServer加在springboot工程的启动类上

@EnableEurekaServer 
@SpringBootApplication 
public class TestApplication { 
 
    public static void main(String[] args) { 
        SpringApplication.run(TestApplication.class, args); 
    } 
}

eureka server的配置文件appication.yml

server: 
  port: 8761 
 
eureka: 
  instance: 
    hostname: localhost 
  client: 
    registerWithEureka: false 
    fetchRegistry: false 
    serviceUrl: 
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

启动eureka server,然后访问http://localhost:8761, 界面如下, "No instances available" 表示无client注册

2.创建一个Eureka Client

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.chry</groupId> 
    <artifactId>test1</artifactId> 
    <version>0.0.1-SNAPSHOT</version> 
    <name>test1</name> 
    <packaging>jar</packaging> 
    <description>Demo Spring Boot Client</description> 
 
    <parent> 
        <groupId>org.springframework.boot</groupId> 
        <artifactId>spring-boot-starter-parent</artifactId> 
        <version>1.5.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> 
    </properties> 
 
    <dependencies> 
        <dependency> 
            <groupId>org.springframework.cloud</groupId> 
            <artifactId>spring-cloud-starter-eureka</artifactId> 
        </dependency> 
        <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> 
    </dependencies> 
 
    <dependencyManagement> 
        <dependencies> 
            <dependency> 
                <groupId>org.springframework.cloud</groupId> 
                <artifactId>spring-cloud-dependencies</artifactId> 
                <version>Dalston.RC1</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> 
 
    <repositories> 
        <repository> 
            <id>spring-milestones</id> 
            <name>Spring Milestones</name> 
            <url>https://repo.spring.io/milestone</url> 
            <snapshots> 
                <enabled>false</enabled> 
            </snapshots> 
        </repository> 
    </repositories> 
 
 
</project>
pom

 

 

使用@EnableEurekaClient注解表明是client

@SpringBootApplication 
@EnableEurekaClient 
@RestController 
public class Test1Application { 
 
    public static void main(String[] args) { 
        SpringApplication.run(Test1Application.class, args); 
    } 
    @Value("${server.port}") 
     String port; 
     @RequestMapping("/") 
     public String home() { 
                 return "hello world from port " + port;    } 
 
}

yml文件的配置

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

最后启动client,之后就可以在服务器端口看到该client成功注册

二、配置管理

Spring Cloud的解决方案是, 将这些配置文件放到版本管理服务器里面,Spring Cloud缺省配置使用GIT中。所有Web服务均从GIT中获取这些配置文件。由于GIT服务器与具体Web服务器之间不需要共享存储, 只要网络可达就行,从而可以实现Web服务于配置信息的存放位置的解耦。

2.1创建config Server

pom文件新加了依赖

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

 @EnableConfigServer注解说明了一个Config Server。同样我们使用@EnableEurekaClient将它注册到服务中心。

Config server的配置文件appication.yml , 注意配置文件的url是GIT服务器的仓库地址, searchPaths配置文件所在的文件夹在仓库中的路径, 在server端不需要指定具体配置文件名, 因为具体的配置文件是什么有应用(也就是client)决定。

eureka: 
  client: 
    serviceUrl: 
      defaultZone: http://localhost:8761/eureka/ 
server: 
  port: 8888 
 
spring: 
  cloud: 
    config: 
      server: 
        git: 
          uri: https://gitee.com/ning9/CRUD.git 
          searchPaths: spring-cloud/helloworldConfig 
  application: 
    name: config-server

启动Config server,访问http://localhost:8888/config-client-dev.properties就可以显示配置文件内容

2.2创建config client

启动文件

 @SpringBootApplication 
 @RestController 
 public class ConfigClientApplication { 
  
     public static void main(String[] args) { 
         SpringApplication.run(ConfigClientApplication.class, args); 
     } 
  
     @Value("${hello}") 
     String hello; 
     @RequestMapping(value = "/hello") 
     public String hello(){ 
         return hello; 
     } 
 }

yml文件

 spring: 
    application: 
      name: config-client 
    cloud: 
      config: 
        label: master 
        profile: dev 
        uri: http://localhost:8888/ 
  server: 
   port: 8881

启动config-client应用后, 可以访问http://locahost/8881/hello, 可以看到,应用本身并没有直接配置hello的具体内容, 也没指定具体配置文件,所欲这些都由spring cloud框架提交给config server了。

三、配置自动刷新

首先,在pom.xml中添加以下依赖。spring-boot-starter-actuator是一套监控的功能,可以监控程序在运行时状态,其中就包括/refresh的功能。

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

其次,开启refresh机制, 需要给加载变量的类上面加载@RefreshScope注解,其它代码可不做任何改变,那么在客户端执行/refresh的时候就会更新此类下面的变量值,包括通过config client从GIT获取的配置。

@SpringBootApplication 
@RestController 
@RefreshScope 
public class ConfigClientApplication { 
    public static void main(String[] args) { 
        SpringApplication.run(ConfigClientApplication.class, args); 
    } 
 
    @Value("${hello}") 
    String hello; 
    @RequestMapping(value = "/hello") 
    public String hello(){ 
        return hello; 
    } 
}

但是这种方式需要自己手动重新运行方法,才能看到更新后的数据

要实现自动更新使用spring cloud bus

下载安装rabbitmq

在server和client的pom文件中添加依赖

<dependency> 
  <groupId>org.springframework.cloud</groupId> 
  <artifactId>spring-cloud-starter-bus-amqp</artifactId> 
</dependency> 
 
<dependency> 
  <groupId>org.springframework.boot</groupId> 
  <artifactId>spring-boot-starter-actuator</artifactId> 
</dependency>

再修改client的配置文件

server: 
  port: 8910 
 
eureka: 
  client: 
    serviceUrl: 
          defaultZone: http://localhost:8010/eureka/ 
 
spring: 
  application: 
      name: client-a 
  cloud: 
      config: 
        discovery: 
          enabled: true #开启通过服务来访问Config Server的功能 
          service-id: config-server 
        profile: dev 
        label: master 
 
  rabbitmq: 
      host: localhost 
      port: 5672 
      username: guest 
      password: guest

最后要实现自动更新要用到git的webhooks来达到自动更新配置。

四、分布式环境下自动发现配置服务

将config-server注册到服务中心

将hello world的应用注册到eureka 服务中心, 配置方法同前面一样

修改配置文件,将config-server的URL硬编码机制改成,通过服务中心根据名字自动发现机制

eureka: 
  client: 
    serviceUrl: 
      defaultZone: http://localhost:8761/eureka/ 
spring: 
  application: 
    name: config-client 
  cloud: 
    config: 
      label: master 
      profile: dev 
      discovery: 
        enabled: true 
        service-id: config-server 
management: 
  security: 
    enabled: false 
server: 
   port: 8881

我们注释掉了硬编码的config-server的URL配置, 取而代之的是服务注册中心的地址http://localhost:8761/eureka/以及配置服务的名字“config-server”, 同时打开自动发现机制discovery.enable = true. 我们在运行一下hello world应用, 可以发现, GIT里面的内容依然可以访问。此时我们的hello world应用已经完全不知道配置服务的地址,也不知道配置的内容, 所有这些都通过服务注册中心自动发现。

五、客户端的负载均衡

Spring Cloud用Ribbon来实现两个Hello World服务的负载均衡

5.1创建ribbon服务

添加pom依赖

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

创建启动类ServiceRibbonApplication

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

@EnableDiscoveryClient向服务中心注册,并且注册了一个叫restTemplate的bean。

@ LoadBalanced注册表明,这个restRemplate是需要做负载均衡的。

创建获取一个获取Hello内容的service类

 @Service 
 public class HelloService { 
     @Autowired RestTemplate restTemplate; 
  
     public String getHelloContent() { 
         return restTemplate.getForObject("http://SERVICE-HELLOWORLD/",String.class); 
     } 
 }

这里关键代码就是, restTemplate.getForObject方法会通过ribbon负载均衡机制, 自动选择一个Hello word服务,

这里的URL是“http://SERVICE-HELLOWORLD/",其中的SERVICE-HELLOWORLD是Hello world服务的名字,而注册到服务中心的有两个SERVICE-HELLOWORLD。 所以,这个调用本质是ribbon-service作为客户端根据负载均衡算法自主选择了一个作为服务端的SERVICE-HELLOWORLD服务。然后再访问选中的SERVICE-HELLOWORLD来执行真正的Hello world调用。

六、用声明式REST客户端Feign调用远端http服务

添加新的依赖

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

创建启动类,需要加上@EnableFeignClients注解以使用Feign, 使用@EnableDiscoveryClient开启服务自动发现

添加配置文件application.yml, 使用端口8902, 名字定义为service-feign, 并注册到eureka服务中心

定义Feign:一个用@FeignClient注解的接口类

@FeignClient用于通知Feign组件对该接口进行代理(不需要编写接口实现),使用者可直接通过@Autowired注入; 该接口通过value定义了需要调用的SERVICE-HELLOWORLD服务(通过服务中心自动发现机制会定位具体URL); @RequestMapping定义了Feign需要访问的SERVICE-HELLOWORLD服务的URL(本例中为根“/”)

@FeignClient(value = "SERVICE-HELLOWORLD") 
  public interface HelloWorldService { 
      @RequestMapping(value = "/",method = RequestMethod.GET) 
     String sayHello(); 
 }

Spring Cloud应用在启动时,Feign会扫描标有@FeignClient注解的接口,生成代理,并注册到Spring容器中。生成代理时Feign会为每个接口方法创建一个RequetTemplate对象,该对象封装了HTTP请求需要的全部信息,请求参数名、请求方法等信息都是在这个过程中确定的,Feign的模板化就体现在这里

编写一个Controller。

注入之前通过@FeignClient定义生成的bean, 

sayHello()映射到http://localhost:8902/hello, 在这里,我修改了Hello World服务的映射,将根“/”, 修改成了“/hello”。

@RestController 
  public class WebController { 
     @Autowired HelloWorldService helloWorldFeignService; 
     @RequestMapping(value = "/hello",method = RequestMethod.GET) 
     public String sayHello(){ 
         return helloWorldFeignService.sayHello(); 
     } 
 }

七、断路器

7.1在Ribbon应用中使用断路器

先添加依赖

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

在Spring Boot启动类上添加@EnableCircuitBreaker注解

用@HystrixCommand注解标注访问服务的方法

@Service 
  public class HelloService { 
      @Autowired RestTemplate restTemplate; 
   
      @HystrixCommand(fallbackMethod = "serviceFailure") 
      public String getHelloContent() { 
          return restTemplate.getForObject("http://SERVICE-HELLOWORLD/",String.class); 
      } 
   
     public String serviceFailure() { 
         return "hello world service is not available !"; 
     } 
 }

@HystrixCommand注解定义了一个断路器,它封装了getHelloContant()方法, 当它访问的SERVICE-HELLOWORLD失败达到阀值后,将不会再调用SERVICE-HELLOWORLD, 取而代之的是返回由fallbackMethod定义的方法serviceFailure()。@HystrixCommand注解定义的fallbackMethod方法,需要特别注意的有两点:

第一,  fallbackMethod的返回值和参数类型需要和被@HystrixCommand注解的方法完全一致。否则会在运行时抛出异常。比如本例中,serviceFailure()的返回值和getHelloContant()方法的返回值都是String。

第二,  当底层服务失败后,fallbackMethod替换的不是整个被@HystrixCommand注解的方法(本例中的getHelloContant), 替换的只是通过restTemplate去访问的具体服务。可以从中的system输出看到, 即使失败,控制台输出里面依然会有“call SERVICE-HELLOWORLD”。

7.2在Feign应用中使用断路器

 用@FeignClient注解添加fallback类, 该类必须实现@FeignClient修饰的接口。

@FeignClient(name = "SERVICE-HELLOWORLD", fallback = HelloWorldServiceFailure.class) 
 public interface HelloWorldService { 
     @RequestMapping(value = "/", method = RequestMethod.GET) 
     public String sayHello(); 
 }

创建HelloWorldServiceFailure类, 必须实现被@FeignClient修饰的HelloWorldService接口。注意添加@Component或者@Service注解,在Spring容器中生成一个Bean

 @Component 
 public class HelloWorldServiceFailure implements HelloWorldService { 
     @Override 
     public String sayHello() { 
         System.out.println("hello world service is not available !"); 
         return "hello world service is not available !"; 
     } 
 }

在application.yml中添加如下配置:

feign: 
   hystrix: 
     enabled: true

八、路由网管zuul

创建新的工程添加新的依赖

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

 创建启动类: 使用@EnableZuulProxy注解

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

编写zuul服务配置:

简单配置两个路由, 一个路由到ribbon,一个路由到feign; 由于都注册到eureka服务中心,所以都用通过serviceId来发现服务具体地址, path是路由的地址映射关系

eureka: 
      client: 
          serviceUrl: 
              defaultZone: http://localhost:8761/eureka/ 
  server: 
      port: 8904 
  spring: 
      application: 
          name: service-zuul 
 zuul: 
   routes: 
     ribbo: 
       path: /api-ribbon/** 
       serviceId: service-ribbon 
     feign: 
       path: /api-feign/** 
       serviceId: service-feign

这时启动zuul服务, 然后访问http://localhost:8904/api-ribbon可直接路由到http://localhost:8901/.  

http://localhost:8904/api-feign/hello可路由到http://localhost:8902/hello

zuul还提供了过滤功能, 只要实现接口ZuulFilter即可对请求先进行筛选和过滤之后再路由到具体服务。

声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

发表评论
搜索
排行榜
关注我们

扫一扫关注我们,了解最新精彩内容