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>
application.yml

spring: application: name: eureka-ribbon-client server: port: 8764 eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/
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); } }
注意点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/
通过配置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); } }
@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>
启动类中添加@EnableHystrix:

@SpringBootApplication @EnableEurekaClient @EnableHystrix public class EurekaRibbonClientApplication { public static void main(String[] args) { SpringApplication.run(EurekaRibbonClientApplication.class,args); } }
兜底类方法上添加@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> |
不需要 |
启动类 |
@EnableHystrix @SpringBootApplication |
不需要 |
配置文件 | 不需要 |
添加 feign: |
熔断器处理逻辑 |
HystrixCommand标注在需要被兜底的具体远程调用方法上 @HystrixCommand(fallbackMethod = "sayError") |
在 @Feign注解的fallback配置加上快速失败处理类 @FeignClient(value = "eureka-client",configuration = FeignConfig.class, fallback = HystrixCatchService.class) //value为服务提供者application name @Service |
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>
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/
启动类

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); } }
代码路径: 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(); } }
从以上代码可以看出首先执行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>
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
通过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); } }
依次启动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; } }; } }
此时关闭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; } }
浏览器访问 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>
application.yml
search-locations表示被读取的配置文件

spring: cloud: config: server: native: search-locations: classpath:/shared profiles: active: native application: name: config-server server: port: 8769
在工程的Resources目录下新建一个shared文件夹,用于存放本地配置,这个配置可以被其他服务通过api调用
启动类
@EnableConfigServer注解开启Config Server

@SpringBootApplication @EnableConfigServer public class ConfigServerApplication { public static void main(String[] args) { SpringApplication.run(ConfigServerApplication.class, args); } }
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>
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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?