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  阅读(311)  评论(0)    收藏  举报