SpringCloudZuul
Zuul 基本使用
@EnableEurekaClient:注册到 Eureka 服务去
@EnableDiscoveryClient:注册到任意注册中心
Nginx + Lua
Lua:控制规则(A/B Test)
Spring Cloud 学习技巧:
善于定位应用:Feign、Config Server、Eureka、Zuul 、Ribbon定位应用,配置方式是不同
激活 Zuul 代理
增加 @EnableZuulProxy
@SpringBootApplication
@EnableZuulProxy
public class SpringCloudZuulDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudZuulDemoApplication.class, args);
}
}
配置路由规则
基本模式:zuul.routes.${app-name} = /${app-url-prefix}/**
zuul.routes.person-service=/person-service/**
整合 Ribbon
启动应用
spring-cloud-feign-eureka:feign 一章的 eureka 服务器
person-service:服务提供者
调用链路
zuul -> person-service
配置方式
## Zuul 服务端口
server.port = 7070
## Zuul 基本配置模式
# zuul.routes.${app-name}: /${app-url-prefix}/**
## Zuul 配置 person-service 服务调用
zuul.routes.person-service = /person-service/**
# 整合 Ribbon
## Ribbon 取消 Eureka 整合
ribbon.eureka.enabled = false
## 配置 "person-service" 的负载均衡服务器列表
person-service.ribbon.listOfServers = \
http://localhost:9090
注意:http://localhost:7070/person-service/person/find/all
person-service 的 app-url-prefix : /person-service/
/person/find/all 是 person-service 具体的 URI
测试:
直接访问服务提供者:

使用 zuul 代理访问:

整合 Eureka
配置方式
引入 eureka-client 依赖
引入 spring-cloud-starter-eureka 依赖
<!-- 增加 Eureka 客户端的依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>激活服务注册、发现客户端
@SpringBootApplication @EnableZuulProxy @EnableDiscoveryClient public class SpringCloudZuulDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringCloudZuulDemoApplication.class, args); } }配置服务注册、发现客户端
# Zuul 服务端口 server.port=7070 # 基本配置模式 #zuul.routes.${app-name} = /${app-url-prefix}/** # Zuul 配置 person-service 服务调用 zuul.routes.person-service=/person-service/** # 整合 Ribbon ## Ribbon 取消与 Eureka 整合 #ribbon.eureka.enabled=false ## 配置 person-service 的负载均衡服务器列表 #person-service.ribbon.listOfServers = \ # http://localhost:9090 # 整合Eureka # Eureka Server 服务 URL,用于客户端注册 eureka.client.service-url.defaultZone=\ http://localhost:12345/eureka
效果测试
直接访问服务提供者:

使用 zuul 代理访问:

整合 Hystrix
服务端提供方:person-service
激活 Hystrix
@SpringBootApplication
@EnableEurekaClient
@EnableHystrix
public class PersonServiceProviderApplication {
public static void main(String[] args) {
SpringApplication.run(PersonServiceProviderApplication.class, args);
}
}
配置 Hystrix 规则
@RestController
public class PersonServiceController {
private final ConcurrentMap<Long, Person> persons = new ConcurrentHashMap<>();
private final Random random = new Random();
@PostMapping(value = "/person/save")
public boolean save(@RequestBody Person person){
return persons.putIfAbsent(person.getId(), person) == null;
}
@GetMapping(value = "/person/find/all")
@HystrixCommand(fallbackMethod = "fallbackForFindAll",
commandProperties = {@HystrixProperty(
name = "execution.isolation.thread.timeoutInMilliseconds",
value = "100"
)}
)
public Collection<Person> findAll() throws InterruptedException {
// 如果随机时间 大于 100 ,那么触发容错
int value = random.nextInt(200);
System.err.println("findAllPersons() costs " + value + " ms.");
Thread.sleep(value);
return persons.values();
}
public Collection<Person> fallbackForFindAll() {
System.err.println("fallbackForFindAll() is invoked!");
return Collections.emptyList();
}
}
效果测试:

整合 Feign
person-client
调用链路
spring-cloud-zuul -> person-client -> person-service
注册到 EurekaServer
端口信息:
spring-cloud-zuul 端口:7070
person-client 端口:8080
person-service 端口:9090
Eureka Server 端口:12345
spring.application.name = person-client
server.port = 8080
## Eureka Server 服务 URL,用于客户端注册
eureka.client.serviceUrl.defaultZone=\
http://localhost:12345/eureka
management.endpoint.health.show-details=always
management.endpoints.web.exposure.include=*
spring-cloud-zuul
配置消费端路由
增加路由应用到 person-client
## Zuul 配置 person-client 服务调用
zuul.routes.person-client = /person-client/**
效果测试
测试地址:
http://localhost:7070/person-client/person/find/all
测试链路:
spring-cloud-zuul(7070) -> person-client(8080) -> person-service(9090)
eureka 注册中心:

person-service 访问:

person-client 访问:

spring-cloud-zuul 访问:

等价的 Ribbon(不走注册中心)
## Ribbon 取消 Eureka 整合
ribbon.eureka.enabled = false
## 配置 "person-service" 的负载均衡服务器列表
person-service.ribbon.listOfServers = \
http://localhost:9090
## 配置 "person-client" 的负载均衡服务器列表
person-client.ribbon.listOfServers = \
http://localhost:8080
整合 Config Server
前面的例子展示 Zuul 、Hystrix、Eureka 以及 Ribbon 能力,可是配置相对是固定,真实线上环境需要一个动态路由,即需要动态配置。
spring-cloud-zuul-config-server
端口信息:
spring-cloud-zuul 端口:7070
person-client 端口:8080
person-service 端口:9090
Eureka Server 端口:12345
Config Server 端口:10000
调整配置项
spring.application.name=spring-cloud-config-server
#定义http服务端口
server.port=10000
#本地仓库的 GIT URI 配置
spring.cloud.config.server.git.uri=\
file:///${user.dir}/spring-cloud/spring-cloud-zuul-assemble/spring-cloud-zuul-config-server/src/main/resources/configs
# 整合Eureka
# Eureka Server 服务 URL,用于客户端注册
eureka.client.service-url.defaultZone=\
http://localhost:12345/eureka
management.endpoint.health.show-details=always
management.endpoints.web.exposure.include=*
增加配置文件
为 spring-cloud-zuul 增加配置文件
三个 profile 的配置文件:
zuul.properties
# 应用 spring-cloud-zuul 默认配置项(profile 为空) # 基本配置模式 #zuul.routes.${app-name} = /${app-url-prefix}/** # Zuul 配置 person-service 服务调用 zuul.routes.person-service=/person-service/**zuul-test.properties
# 应用 spring-cloud-zuul 默认配置项(profile="test") # 基本配置模式 #zuul.routes.${app-name} = /${app-url-prefix}/** # Zuul 配置 person-client 服务调用 zuul.routes.person-client=/person-client/**zuul-prod.properties
# 应用 spring-cloud-zuul 默认配置项(profile="prod") # 基本配置模式 #zuul.routes.${app-name} = /${app-url-prefix}/** # Zuul 配置 person-service 服务调用 zuul.routes.person-service=/person-service/** # Zuul 配置 person-client 服务调用 zuul.routes.person-client=/person-client/**
加入 Git 版本控制
初始化 ${user.dir}/spring-cloud/spring-cloud-zuul-assemble/spring-cloud-zuul-config-server/src/main/resources/configs 为 git 根目录
初始化
$ git init Initialized empty Git repository in E:/Develop/Record/IdeaWork/gupao-projects/20191001-microservice/spring-cloud/spring-cloud-zuul-assemble/spring-cloud-zuul-config-server/src/main/resources/configs/.git/增加上述三个配置文件到 git 仓库
$ git add *.properties提交到本地 git 仓库
$ git commit -m "temp commit" [master (root-commit) 6e4a8f7] temp commit 3 files changed, 20 insertions(+) create mode 100644 zuu-prod.properties create mode 100644 zuu-testl.properties create mode 100644 zuul.properties
以上操作为了让 Spring Cloud Git 配置服务器实现识别 Git 仓库,否则添加以上三个文件也没有效果。
注册到 Eureka 服务器
增加 maven 依赖
增加 spring-cloud-starter-eureka 依赖
<!-- 增加 Eureka 客户端的依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency>激活服务注册、发现客户端
@SpringBootApplication @EnableConfigServer @EnableDiscoveryClient public class SpringCloudZuulConfigServerApplication { public static void main(String[] args) { SpringApplication.run(SpringCloudZuulConfigServerApplication.class, args); } }调整配置项
## Eureka Server 服务 URL,用于客户端注册 eureka.client.serviceUrl.defaultZone=\ http://localhost:12345/eureka
测试 config server
测试地址:
http://localhost:10000/zuul/default

http://localhost:10000/zuul/test

http://localhost:10000/zuul/prod

spring-cloud-zuul-demo
将 spring-cloud-zuul-demo 作为 config-client ,从 config-server 上获取配置,实现配置动态管理,从而使用 zuul 动态路由更加多功能化
端口信息:
spring-cloud-zuul 端口:7070
person-client 端口:8080
person-service 端口:9090
Eureka Server 端口:12345
增加 config-client 依赖
增加 spring-cloud-starter-config 依赖
将之前:
zuul.routes.person-service
zuul.routes.person-client
配置注释
<!-- 增加 配置客户端的依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
创建 bootstrap.properties
配置 config 客户端信息
## 整合 Eureka
## Eureka Server 服务 URL,用于客户端注册
## application.properties 会继承bootstrap.properties 属性
## 因此,application.properties 没有必要配置 eureka.client.serviceUrl.defaultZone
eureka.client.serviceUrl.defaultZone=\
http://localhost:12345/eureka
### bootstrap 上下文配置
# 配置客户端应用名称: zuul , 可当前应用是 spring-cloud-zuul
spring.cloud.config.name = zuul
# profile 是激活配置
spring.cloud.config.profile = prod
# label 在Git中指的分支名称
spring.cloud.config.label = master
# 采用 Discovery client 连接方式
## 激活 discovery 连接配置项的方式
spring.cloud.config.discovery.enabled=true
## 配置 config server 应用名称
spring.cloud.config.discovery.serviceId = spring-cloud-config-server
整合效果
测试链路:
http://localhost:7070/person-client/person/find/all
spring-cloud-zuul -> person-client -> person-service
http://localhost:7070/person-service/person/find/all
spring-cloud-zuul -> person-service
eureka 注册中心:

zuul -> person-client

zuul -> person-service

注:如果无数据显示,说明请求超过了 100ms,发生了熔断

zuul 其他实践
abc.acme.com -> abc
def.acme.com -> def
需要自定义实现 ZuulFilter
通过 Groovy 来实现动态配置规则
问答部分
看下来过程是:通过url去匹配zuul中配置的serviceId然后没整合ribbon时,直接去eureka中找服务实例去调用,如果整合了ribbon时,直接从listofService中取得一个实例,然后调用返回,对不?
答:大致上可以这么理解,不过对应的listOfServicers 不只是单个实例,而可能是一个集群,主要可以配置域名。
为什么要先调用client而不直接调用server,还是不太理解
答:这个只是一个演示程序,client 在正式使用场景中,并不是一简单的调用,它可能是一个聚合服务。
zuul 是不是更多的作为业务网关
答:是的,很多企业内部的服务通过 Zuul 做个服务网关
渡劫RequestContext已经存在ThreadLocal中了,为什么还要使用ConcurrentHashMap?
答:
ThreadLocal只能管当前线程,不能管理子线程,子线程需要使用InheritableThreadLocal。ConcurrentHashMap实现一下,如果上下文处于多线程线程的环境,比如传递到子线程。比如:T1 在管理 RequestContext,但是 T1 又创建了多个线程(t1、t2),这个时候,把上下文传递到了子线程 t1 和 t2 .
Java 的进程所对应的线程 main 线程(group:main),main 线程是所有子线程的父线程,main线程 T1 ,T1 又可以创建 t1 和 t2 :
@Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); // T1 线程 ServiceExecutor executor = ...; executor.submit(new MyRunnable(ctx){ public void run(){ ctx // t1 线程 } }); return null; }ZuulServlet已经管理了RequestContext的生命周期了,为什么ContextLifecycleFilter还要在做一遍?
答:
ZuulServelt最终也会清理掉RequestContext:} finally { RequestContext.getCurrentContext().unset(); }为什么
ContextLifecycleFilter也这么干?} finally { RequestContext.getCurrentContext().unset(); }不要忽略了
ZuulServletFilter,也有这个处理:}finally { RequestContext.getCurrentContext().unset(); }RequestContext是 任何 Servlet 或者 Filter 都能处理,那么为了防止不正确的关闭,那么ContextLifecycleFilter相当于兜底操作,就是防止ThreadLocal没有被remove 掉。ThreadLocal对应了一个 Thread,那么是不是意味着者Thread 处理完了,那么ThreadLocal也随之 GC?所有 Servlet 均采用线程池,因此,不清空的话,可能会出现意想不到的情况。除非,每次都异常!(这种情况也要依赖于线程池的实现)。