1.Spring Cloud简介

Spring Cloud是一个微服务框架,常用组件如下:

Eureka:实现服务的注册与发现(可以理解为注册中心,另外Spring Cloud也支持Zookeeper、Consul用于服务注册和发现)。Eureka组件提供服务的健康检测以及界面友好的UI,便于开发人员随时了解服务单元运行情况

Hystrix: 熔断器,Hystrix除了基本熔断功能外还提供服务降级、服务限流的功能

Ribbon:负载均衡组件,Ribbon和Zuul组合实现负载均衡,将请求根据负载均衡策略分配到不同服务实例中

Zuul:路由网关组件,Zuul有只能路由和过滤的功能。内部的API接口通过Zuul网关同一对外暴漏,内部服务的API接口不直接对外暴露(防止内部服务敏感信息暴漏)

Spring Cloud Config:Spring Cloud Config提供配置文件统一管理功能,Spring Cloud Config包括Server端和Clinet端,Server端读取本地或者远程配置文件,所有Client端向Server端读取配置信息

Spring Cloud Security:是对Spring Security的封装,Spring Cloud Security向服务单元提供用户验证和权限认证,一般他和Spring Security Oauth2组件一起使用

Spring Cloud Sleuth:分布式链路追踪组件,通过它可以知道各个微服务间的相互依赖关系

 

                                                                                                                                                                                                            Spring Cloud vs Dubbo

微服务关注点 Spring Cloud Dubbo
注册中心   Eureka、Consul、Zookeeper Zookeeper
熔断 Hystrix 不完善
通信方式 Http、Message RPC接口(比如ESA SOA接口)
负载均衡  Ribbon 自带
网关 Zuul
分布式链路追踪 Spring Cloud Sleuth
配置管理 Spring Cloud Config
安全认证 Spring Cloud Security  

 

2. Eureka

 2.1 Eureka基本框架

   

Eureka Server:服务注册中心,提供服务注册和发现功能

Eureka Client:分为Application service和Application client,即服务的提供者和服务消费者

2.2 Eureka一些概念

Register:服务注册,当Eureka Client向Eureka Server注册时,Eureka Client提供自身元数据:IP地址、端口号等

Renew:服务续约,Eureka Client默认情况下会每隔30s向Eureka Server发送一次心跳来进行服务续约,通过续约告知Eureka server该Eureka Client仍然可用

Fetch registries:获取服务注册列表,Eureka Client从Eureka Server获取服务注册表信息,并将其缓存到本地。Eureka Client中Applicaiton  Client会使用服务注册表信息查找其他服务信息(applicaiton server),从而进行远程调用。改注册表信息定时(30s)更新

Cancel:服务下线,当Eureka Client在程序关闭时可以向Eureka server发送下线请求,发送请求后,Eureka client实例会从Eureka Server的服务注册表中删除。该线下不会自动完成,需要在程序关闭时调用以下代码:

    DiscoveryManager.getInstance().shutdownComponent()

Eviction:服务剔除,默认情况下,当Eureka Client连续90s没有向Eureka Server发送心跳,Eureka Server会将该服务实例从服务注册列表删除

2.3 Eureka 实例代码

注意下例中Eureka Client只写了一个服务,实际中Eureka Client是多个调用与被调用服务

项目架构:

 

 2.3.1 Eureka Server:

pom.xml

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

application.yml 

server:
port: 8761

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

  注意,registerWithEureka和fetchRegistry都设置为false是为了防止自己注册自己

启动类:

EnableEurekaServer开启Eureka Server功能
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {

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

2.3.2 Eureka Client:

pom.xml

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

 bootstrap.yml

server:
port: 8762
spring:
application:
name: eureka-client

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

  注意: defaultZone: http://localhost:8761/eureka/是服务注册地址(即Eureka Server暴漏地址)

启动类 

@EnableEurekaClient开启Eureka Client功能
@SpringBootApplication
@EnableEurekaClient
public class EurekaClientApplication {

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

代码:https://github.com/forezp/springcloud-book/tree/master/chapter5-2

 

3.Ribbon 负载均衡 

 3.1 Ribbon简介

负载均衡作用是将请求分摊到多个执行单元上。常见的负载均衡方式:

①独立进程单元(比如Ngnix),通过负载均衡策略将请求转发到不同的执行单元上

②将负载均衡逻辑封装在请求端,并且运行在请求端的进程里(比如Ribbon)

 

3.2 Ribbon & RestTemplate来实现消费服务

项目结构

 

 

 Eureka Server以及App Server1、App Server2如2.3节中代码一致(只是换两个端口号启动两个App Server实例)

 App Client代码如下:

pom.xml

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-ribbon</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
View Code

 

application.yml

spring:
  application:
    name: eureka-ribbon-client
server:
  port: 8764

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

 

Java代码:

启动类:
@SpringBootApplication
@EnableEurekaClient
public class EurekaRibbonClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaRibbonClientApplication.class,args);
    }
}

RestTemplate配置类:
@Configuration
public class RibbonConfig {
    @Bean
    @LoadBalanced
    RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

Controller类:
@RestController
public class RibbonController {
    @Autowired
    RestTemplate restTemplate;
    @GetMapping("/ribbon/hi")
    public String hi(@RequestParam String name){
        return restTemplate.getForObject("http://eureka-client/hi?name="+name,String.class);
    }
}
View Code

 

 注意点1:RibbonConfig类中要加上@LoadBalanced注解,此时restTemplate就结合Ribbon开启了负载均衡功能

注意点2:RibbonController类中用resetTemplate调用Eureka-client的api接口,此时uri上不需要使用硬编码(诸如http://localhost:8762/**),只需要写服务名(即App server1和App server2的applicaiton name,所以要确保app server1和app server2使用同一个application name

在浏览器多次访问 http://localhost:8764/ribbon/hi?name=test 浏览器会轮流显示如下内容.这说明负载均衡器起了作用,负载均衡器会轮流请求eureka-client(app server)的两个实例中"/hi" API接口

hi test,i am from port:8763

hi test,i am from port:8762

代码地址: https://github.com/forezp/springcloud-book/tree/master/chapter6-3

 3.3 LoadBalancerClient简介

负载均衡器的核心类为LoadBalancerClient,LoadBalancerClient可以获取负载均衡的服务提供者的实例信息(比如IP、port、hostname)

LoaderBalancerClient是从Eureka Client获取服务注册列表信息(比如服务提供者host ip port 等),并将服务注册列表信息缓存一份,在LoadBalancerClient调用choose方法时,根据负载均衡策略选择一个服务实例信息,进而进行了负载均衡。当然LoadBalancerClient也可以不从Eureka Client获取注册列表,这时需要自己维护一份本地服务注册列表信息

 

3.3.1 LoadBalancerClient从Eureka Client获取服务注册信息:

3.2节中 RibbonController中添加如下代码

@GetMapping("/loadbalance")
public String sayHi(){
ServiceInstance instance=loadBalancerClient.choose("eureka-client");//注意从Eureka Client获取服务注册信息时choose方法中是服务提供者的applicaiton name
    return instance.getHost()+":"+instance.getPort();instance.
}
浏览器多次访问http://localhost:8764/loadbalance,浏览器轮流显示:
H02KDAFI12P0198.nam.nsroot.net:8763
H02KDAFI12P0198.nam.nsroot.net:8762

 3.3.2 LoadBalancerClient获取本地自己维护服务注册列表

将3.2节中applicaiton.yml改为

spring:
  application:
    name: eureka-ribbon-client
server:
  port: 8765
stores:
  ribbon:
    listOfServers: baidu.com,google.com
ribbon:
  eureka:
    enabled: false
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
View Code

 

通过配置ribbon.eureka.enable=false来禁止调用EurekaClient获取服务注册列表。另外在配置文件中有一个程序名为stores的服务,有两个不同Url的服务实例,通过stores.ribbon.listOfServers来配置这些服务实例的url

启动工程,浏览器多次访问http://localhost:8765/loadbalance,浏览器会交替出现以下内容:

baidu.com:80

google.com:80 

 

实例代码: https://github.com/forezp/springcloud-book/tree/master/chapter6-4

3.4 Ribbon小结

Ribbon的负载均衡主要是通过LoadeBalancerClient来实现的,LoadeBalancerClient在初始化时向Eureka获取服务注册列表信息,并且每隔10s向Eureka发送“ping"指令来判断服务的可用性。有了这些服务注册表信息可以根据具体Irule策略来进行负载均衡;

RestTemplate加上@LoadBalanced注解后,在远程调度时就能够负载均衡的原因是维护了一个被@LoadBalanced注解的RestTemplate列表,并给该列表中的RestTemplate对象添加了拦截器。在拦截器的方法中,将远程调度方法交给Ribbon的负载均衡器LoadBalancedClient去处理从而达到负载均衡的目的。

 

4. 声明式调用Feign

Feign作用与RestTemplate作用一样都是为了远程调用,只是二者使用上有区别

4.1 实例:

 基于2.3节中代码,新加module eureka-feign-client如下

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>

    <groupId>com.forezp</groupId>
    <artifactId>eureka-feign-client</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>eureka-feign-client</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <artifactId>chapter5-2</artifactId>
        <groupId>com.forezp</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

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

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

</project>

application.yml:
spring:
  application:
    name: eureka-feign-client
server:
  port: 8766

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

启动类:
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class FeignClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(FeignClientApplication.class,args);
    }
}
View Code

 

@EnableFeignClients开启Feign Client的功能

接下来实现一个Feign Client: EurekaClientFeign(这是一个Interface,采用@FeignClient注解声明)+FeignConfig(为FeignClient的配置类)+FeignController(简单的暴露接口) 

 EurekaClientFeign:

如下两个value指定对应服务提供者的接口

@FeignClient(value = "eureka-client",configuration = FeignConfig.class) //value为服务提供者application name
public interface EureKaClientFeign {
    @GetMapping(value="/hi") //value为服务提供者内部定义是哪个接口路径
    String sayHiFromEurekaClient(@RequestParam(value = "name") String name);
}

 

 FeignConfig:

@Configuration
public class FeignConfig {
    @Bean
    public Retryer feignRetryer(){
        return new Retryer.Default(100, SECONDS.toMillis(1),5); //重试间隔为100ms,最大重试时间为1s,重试次数为5次
    }
}

 

 FeignController:

@RestController
public class HiController {
    @Autowired
    EureKaClientFeign eureKaClientFeign;
    @GetMapping("/feign/hi")
    public String sayHi(@RequestParam String name){
        return eureKaClientFeign.sayHiFromEurekaClient(name);
    }
}

启动服务,浏览器多次访问http://localhost:8766/feign/hi?name=feign后交替输出如下结果.这也足以说明Feign有负载均衡的能力(Feign集成了Ribbon技术所以支持负载均衡.Feign和Ribbon最终能实现负载均衡时都交给LoadBalancerContext来处理

hi feign,i am from port:8762

hi feign,i am from port:8763

代码地址:https://github.com/forezp/springcloud-book/tree/master/chapter7

4.2 Feign工作原理

4.3 Feign vs RestTemplate

  Feign RestTemplate
请求方式

不用自己拼接url和参数,只需要在FeignClient中指定被调用appname和接口名即可。

Feign底层实现是动态代理。

请求方式有两种:

①http://url:port/apiname 这样可以不用通过注册中心也可以直接请求接口

②http://appname:port/apiname 此时RestTemplate需要添加@LoadBalanced注解,进行负载均衡

 

5. Hystrix 熔断器

 Hystrix提供了熔断器功能,能阻止分布式系统中出现联动故障

5.1 Hystrix工作机制

                         

 

5.2 在RestTemplate和Ribbon上使用熔断器

在3.2节 eureka-ribbon-client中修改如下内容 

pom.xml中添加spring-cloud-starter-hystrix

<?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.forezp</groupId>
    <artifactId>eureka-ribbon-client</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>eureka-ribbon-client</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <artifactId>chapter5-2</artifactId>
        <groupId>com.forezp</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

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

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

</project>
View Code

 

 启动类中添加@EnableHystrix

@SpringBootApplication
@EnableEurekaClient
@EnableHystrix
public class EurekaRibbonClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaRibbonClientApplication.class,args);
    }
}
View Code

 

 兜底类方法上添加@HystrixCommand

@Service
public class RibbonService {
    @Autowired
    RestTemplate restTemplate;
    @HystrixCommand(fallbackMethod = "sayError") //sayError就是sayHi远程调用异常时的兜底方法
    public String sayHi(String name){
        return restTemplate.getForObject("http://eureka-client/hi?name="+name,String.class);
    }
    public String sayError(String name){
        return name+":error service";
    }
}

 

 

启动Eureka-server eureka-client eureka-ribbon-client,访问http://localhost:8765/ribbon/hi?name=qf输出结果:

hi qf,i am from port:8764

当把eureka-client关闭后,此时restTemplate.getForObject("http://eureka-client/hi?name="+name,String.class)已经访问不了,访问http://localhost:8765/ribbon/hi?name=qf输出结果:

qf:error service

5.3 在Feign上使用熔断器 

 由于Feign的起步依赖中已经引入Hystrix的依赖,所以在Feign中使用Hystrix不需要引入任何的依赖,只需要在配置文件appliction.yml中开启Hystrix功能       

feign:
     hystrix:
       enabled: true

 

 在4.1节中,applicaiton.yml添加如上配置信息

在@Feign注解的fallback配置加上快速失败处理类

@FeignClient(value = "eureka-client",configuration = FeignConfig.class, fallback = HystrixCatchService.class) //value为服务提供者application name
public interface EureKaClientFeign {
    @GetMapping(value="/hi") //value为服务提供者内部定义接口路径
    String sayHiFromEurekaClient(@RequestParam(value = "name") String name);
}

 

 快速失败处理类实现以上接口,并重写需要被兜底的接口

@Service
public class HystrixCatchService implements EureKaClientFeign{

    @Override
    public String sayHiFromEurekaClient(String name) {
        return name+":Feign Hystrix error catch";
    }
}

 

 启动eureka-server eureka-cleint eureka-feign-client,在浏览器中输入http://localhost:8766/feign/hi?name=feign,输出结果为:

hi feign,i am from port:8764

当关闭eureka-client后,输出结果为:

feign:Feign Hystrix error catch

 

5.4 Hystrix使用在Ribbon和Feign上区别

  Ribbon & RestTemplate Feign
依赖

pom.xml中引入

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
 不需要
启动类

 @EnableHystrix

@SpringBootApplication
@EnableEurekaClient
@EnableHystrix
public class EurekaRibbonClientApplication {...}
 不需要
配置文件  不需要

 添加

feign:
hystrix:
enabled: true
 熔断器处理逻辑  
HystrixCommand标注在需要被兜底的具体远程调用方法上
@HystrixCommand(fallbackMethod = "sayError")
public String sayHi(String name){
return restTemplate.getForObject("http://eureka-client/hi?name="+name,String.class);
}
public String sayError(String name){
return name+":error service";
}

在 @Feign注解的fallback配置加上快速失败处理类

@FeignClient(value = "eureka-client",configuration = FeignConfig.class, fallback = HystrixCatchService.class) //value为服务提供者application name
public interface EureKaClientFeign {
@GetMapping(value="/hi") //value为服务提供者内部定义接口路径
String sayHiFromEurekaClient(@RequestParam(value = "name") String name);
}
快速处理类实现@FeignClient注解的接口
@Service
public class HystrixCatchService implements EureKaClientFeign{

@Override
public String sayHiFromEurekaClient(String name) {
return name+":Feign Hystrix error catch";
}
}

 

5.5 Htystrix Dashboard监控熔断器的状态

Hystrix Dashboard是监控Hystrix的熔断器状态的一个组件,提供了数据监控和有好的图像展示界面

5.5.1 在restTemplate中使用Hystrix Dashboard

 在5.2节基础上,pom.xml添加如下依赖

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

 启动类添加@EnableHystrixDashboard注解

启动eureka-server eureka-client eureka-ribbon-client在浏览器访问 http://localhost:8765/ribbon/hi?name=test(启动微服务调用逻辑),然后在浏览器访问 http://localhost:8765/hystrix.stream,浏览器上会显示熔断器的数据指标,如下图所示

 

 

 在浏览器访问http://localhost:8765/hystrix 浏览器显示如下界面

 

 

 在界面依次填写 http://localhost:8765/hystrix.stream 2000 test,显示如下内容

 

 

 5.5.2 在Feign中使用Hystrix Dashboard

 5.3节代码基础上,pom.xml添加如下内容

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

 启动类添加@EnableHystrixDashboard注解。启动后和5.5.1内容一样

 5.6 使用Turbine

Hystrix Dashboard监控服务熔断情况时,每个服务都有一个Hystrix Dashboard主页,当入伍很多时,监控不方便。为了监控多个服务的熔断器状况,Netfix开源了Hystrix另一个组件turbine.Turbine聚合多个Hystrix Dashboard在一个页面展示进行集中监控

在5.5节基础上新增eureka-turbine-client 模块

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>

    <groupId>com.forezp</groupId>
    <artifactId>eureka-ribbon-client</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>eureka-turbine-client</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <artifactId>chapter5-2</artifactId>
        <groupId>com.forezp</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

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


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

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

</project>
View Code

 

applicaiton.yml

spring:
  application.name: service-turbine
server:
  port: 8769
security.basic.enabled: false
turbine:
  aggregator:
    clusterConfig: default   # 指定聚合哪些集群,多个使用","分割,默认为default。可使用http://.../turbine.stream?cluster={clusterConfig之一}访问
  appConfig: eureka-ribbon-client,eureka-feign-client  ### 配置Eureka中的serviceId列表,表明监控哪些服务
  clusterNameExpression: new String("default")
  # 1. clusterNameExpression指定集群名称,默认表达式appName;此时:turbine.aggregator.clusterConfig需要配置想要监控的应用名称
  # 2. 当clusterNameExpression: default时,turbine.aggregator.clusterConfig可以不写,因为默认就是default
  # 3. 当clusterNameExpression: metadata['cluster']时,假设想要监控的应用配置了eureka.instance.metadata-map.cluster: ABC,则需要配置,同时turbine.aggregator.clusterConfig: ABC
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
View Code

 

启动类

package com.corezp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.turbine.EnableTurbine;

@SpringBootApplication
@EnableTurbine
public class TurbineApplicaiton {
    public static void main(String[] args) {
        SpringApplication.run(TurbineApplicaiton.class,args);
    }
}
View Code

 

 代码路径: https://github.com/forezp/springcloud-book/tree/master/chapter8-8

 

6 路由网关 Spring Cloud Zuul

6.1 Zuul作用

Zuul、Ribbon以及Eureka结合,可以实现只能路由和负载均衡的功能,Zuul可以将请求流量按照某种策略分发到集群状态中多个服务实例

Zuul将所有服务的api接口同意聚合,并统一对外暴露

zuul中pre过滤器可以做到用户身份认证功能

6.2 Zuul工作原理  

 Zuul是通过Servlet来实现的,Zuul通过自定义ZuulServlet来对请求进行控制。Zuul的核心是一系列过滤器可以在http请求的发起和相应期间执行一系列的过滤器

Zuul过滤器:

pre: 请求到具体服务之前执行的,这种类型服务器可以做安全认证(比如身份验证等)

routing:用于将请求路由到具体的微服务实例

post:是在请求已被路由到微服务后执行,一般情况下用作收集统计信息、指标,以及将响应传输到客户端

error: 是在其他过滤器发生错误时执行的

Zuul过滤器特性:

type:Zuul过滤器类型,如上四种类型。这个类型决定过滤器请求在哪个阶段起作用

execution order:执行顺序,order越小,越先执行

criteria:过滤器执行所需的条件

action: 如果符合执行条件,则执行action(即逻辑代码)

 ZuulServlet是Zuul的核心Servlet,ZuulServlet的作用是初始化ZuulFilter,并编排这些ZuulFilter的执行顺序,该类有一个Service方法,执行过滤器执行的逻辑

    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        try {
            this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse);
            RequestContext context = RequestContext.getCurrentContext();
            context.setZuulEngineRan();

            try {
                this.preRoute();
            } catch (ZuulException var13) {
                this.error(var13);
                this.postRoute();
                return;
            }

            try {
                this.route();
            } catch (ZuulException var12) {
                this.error(var12);
                this.postRoute();
                return;
            }

            try {
                this.postRoute();
            } catch (ZuulException var11) {
                this.error(var11);
            }
        } catch (Throwable var14) {
            this.error(new ZuulException(var14, 500, "UNHANDLED_EXCEPTION_" + var14.getClass().getName()));
        } finally {
            RequestContext.getCurrentContext().unset();
        }
    }
View Code

 

从以上代码可以看出首先执行pre过滤器,如果执行出错会执行error和post过滤器。如果没出错则接下来执行routing过滤器的route().最后执行post过滤器的postRoute()

6.3 Zuul服务实例

6.3.1 第4节基础上新增模块eureka-zuul-client

 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>

    <groupId>com.forezp</groupId>
    <artifactId>eureka-feign-client</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>eureka-zuul-client</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <artifactId>chapter5-2</artifactId>
        <groupId>com.forezp</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

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

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

</project>
View Code

 

application.yml

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
spring:
  application:
    name: service-zuul
server:
  port: 8900
zuul:
  routes:
    clientapi:
      path: /hiapi/**              
      serviceId: eureka-client  #指定到哪个微服务
    ribbonapi:
      path: /ribbonapi/**
      serviceId: eureka-ribbon-client
    feignapi:
      path: /feignapi/**
      serviceId: eureka-feign-client
View Code

 

通过zuul.routes.clientapi.path=/hiapi/**和zuul.routes.clientapi.serviceId=eureka-client这两个配置可以将以"hiapi"开头的url路由到eureka-client的微服务里

启动类

package com.corezp;

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;

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

 

依次启动eureka-server eureka-clinet eureka-client1 eureka-ribbon eureka-feign eureka-zuul-client,在浏览器输入 http://localhost:8900/hiapi/hi?name=eureka(hiapi为zuul.routes.clientapi.path内容,hi为eureka-client中api接口path),浏览器依次输出如下结果

hi eureka,i am from port:8762

hi eureka,i am from port:8763

6.3.2 不需要负载均衡

只需要将application.yml中serviceId换成对应url即可:

比如将

zuul:
routes:
clientapi:
path: /hiapi/**
serviceId: eureka-client

换成

zuul:
routes:
clientapi:
path: /hiapi/**
url: http://localhost:8763

就算多次访问http://localhost:8900/hiapi/hi?name=eureka 那么返回结果一直都是 hi eureka,i am from port:8763

6.3.3 指定url并且做负载均衡

比如启动三个eureka-client, 这三个eureka-client暴露端口号分别是http://localhost:8762 http://localhost:8763 http://localhost:8764 ,但访问时只想在8762 8763间路由,此时需要自己维护一个服务注册列表。首先将ribbon.eureka.enabled=false,然后自己维护一个注册列表,通过配置eureka-v1.listOfServers来配置多个负载均衡的url

6.3.4 zuul上配置API接口的版本号

比如想在http://localhost:8900/hiapi/hi?name=eureka 上加一个v1作为版本号(即http://localhost:8900/v1/hiapi/hi?name=eureka ),只需要在配置文件中增加zuul.prefix: /v1即可

6.3.5 zuul上配置熔断器 zuulFallbackProvider

zuul上实现熔断器功能需要实现ZuulFallbackProvider的接口,该接口有两个方法:getRoute():用于指定熔断功能应用于哪个服务;另一个方法fallbackResponse():进入熔断功能时执行逻辑

新建如下实现ZuulFallbackProvider接口的类

package com.corezp.service;

import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Service;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

@Service
public class MyFallbackProvider implements ZuulFallbackProvider {
    @Override
    public String getRoute() {
        //针对eureka-client服务的熔断器,当eureka-client服务出现故障进入熔断逻辑
        return "eureka-client";
    }

    @Override
    public ClientHttpResponse fallbackResponse() {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                return 200;
            }

            @Override
            public String getStatusText() throws IOException {
                return "OK";
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream("ops, error. I am fallback".getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders httpHeaders=new HttpHeaders();
                httpHeaders.setContentType(MediaType.APPLICATION_JSON);
                return httpHeaders;
            }
        };
    }
}
View Code

 

此时关闭eureka-client后访问http://localhost:8900/hiapi/hi?name=eureka输出结果:

ops, error. I am fallback

6.3.6 Zuul中使用过滤器 Zuulfilter

自定义过滤器只需要继承ZuulFilter,并实现ZuulFilter中的抽象方法,包括:

filterType(): 过滤器类型(pre routing post error)

filterOrder():过滤顺序,它为一个int类型,值越小越先执行

shouldFilter():表示该过v零是否去执行过滤逻辑,如果为true则执行run(),如果false则不执行run()

run():具体的过滤逻辑

示例:

在6.3.5代码基础上,新增如下类

package com.corezp.service;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;

import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;

@Service
public class MyFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return PRE_TYPE;
    }

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

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

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        Object token = request.getParameter("token");
        if (token == null) {
            System.out.println("token is empty");
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(401);
            try {
                ctx.getResponse().getWriter().write("token is empty");
            } catch (Exception e) {
            }
            return null;
        }
        System.out.println("ok");
        return null;
    }
}
View Code

 

浏览器访问 http://localhost:8900/hiapi/hi?name=eureka显示如下内容

token is empty

浏览器访问 http://localhost:8900/hiapi/hi?name=eureka&token=abc,显示内容如下

hi eureka,i am from port:8762

可见MyFilter这个bean注入IOC容器后,对请求进行过滤,并在请求路由转发之前进行逻辑判断。实际开发中可用此过滤器进行安全验证

 

代码地址: https://github.com/forezp/springcloud-book/tree/master/chapter9

 

7. Spring Cloud Config -- 分布式配置中心

7.1 Config Server从本地读取配置文件

Config Server可以从本地仓库读取配置文件,也可以从远程Git仓库读取

本地仓库是指将所有配置文件统一写在Config Server工程目录下,Config Server暴漏Http API接口,Config client通过调用Config Server的http API接口来读取配置文件

7.1.1 构建 Config server -- 配置文件提供者

pom.xml

spring-cloud-config-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>com.forezp</groupId>
    <artifactId>config-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>config-server</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>com.forezp</groupId>
        <artifactId>chapter10</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

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


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


</project>
View Code

 

application.yml

search-locations表示被读取的配置文件

spring:
  cloud:
    config:
      server:
        native:
          search-locations: classpath:/shared
  profiles:
     active: native
  application:
    name: config-server

server:
  port: 8769
View Code

 

在工程的Resources目录下新建一个shared文件夹,用于存放本地配置,这个配置可以被其他服务通过api调用

启动类

@EnableConfigServer注解开启Config Server

@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {

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

 

 

7.1.2 构建 Config client -- 远程文件调用者

pom.xml

spring-cloud-starter-config

<?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.forezp</groupId>
    <artifactId>config-client</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>config-client</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>com.forezp</groupId>
        <artifactId>chapter10</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>


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

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

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


</project>
View Code

 

bootstrap.yml

bootstrap相对application具有优先执行顺序。bootstrap.yml中spring.cloud.config.uri为指向Config Server的项目所暴露的api,如果从config server中没有读取成功则执行快速失败(fail-fast)读取dev文件。bootstrap.yml中spring.application.name和spring.profile.active两者以"-"相连,构成了向Config server读取的配置文件名。所以本例中读取的配置名为config-client-dev.yml

spring:
  application:
    name: config-client
  cloud:
    config:
      uri: http://localhost:8769
      fail-fast: true
  profiles:
    active: dev

 

 

Config Server和Config Client依次启动后,由于Coifng Client没有指定对应端口号,但从日志中可以看出Cofing Client启动的端口号是8762

 

 

同时,访问config client中api http://localhost:8762/foo 得到返回结果是config server 项目shared目录下config-client-dev.yml配置文件中的内容:foo version 1

 

代码地址:https://github.com/forezp/springcloud-book/tree/master/chapter10-2

 7.2 Config Server从远程Git仓库读取配置文件

Spring Cloud Config支持从远程Git仓库读取配置文件,即Config Client可以不从Config Server的本地配置文件读取,而是从Config Server配置的远程Git仓库读取。这样做的好处是将配置统一管理,并且通过Spring Cloud Bus在不人工启动程序的情况下对Config Clieng的配置进行刷新

7.3 Spring Cloud Bus

Spring Cloud Bus是用轻量级的消息代理将分布式的节点连接起来,可以用于广播配置文件的更改或者服务的监控管理。

使用Spring Cloud Bus刷新配置理由是:如果有十几个微服务,每个服务又有多个实例,当配置更改时,需要重新启动多个微服务实例会很麻烦,spring cloud bus的功能就是让这个过程变得简单。当远程git仓库配置更改后,只需要向某一个微服务实例发送一个post请求,通过消息组件通知其他微服务实例重新拉去配置文件

 

 

 

 

 

代码地址: https://github.com/forezp/springcloud-book

 

 
posted on 2023-02-07 11:40  colorfulworld  阅读(194)  评论(0编辑  收藏  举报