15-服务网关和路由
服务网关和路由
服务网关
分布式系统的横切关注点
cross-cutting concern
安全
日志记录
用户跟踪
服务网关(service gateway)
服务网关
服务网关位于服务客户端和相应的服务实例之间
所有服务调用(内部和外部)都应流经服务网关
服务网关提供的能力
静态路由
动态路由
验证和授权
度量数据收集和日志记录
服务网关的实现Zuul
Zuul is a gateway service that provides dynamic routing, monitoring, resiliency, security, and more.
- 将应用程序中的所有服务的路由映射到一个URL
- 过滤器
分布式系统关系图

网关作用:一个中转站(外部访问、内部调用都要通过网关),所以普通用户调用时用的是网关的端口加上zuul.prefix
通过服务发现自动映射路由
服务ID
需要访问Eureka,有服务才会创建路由
默认的映射路由为:(通过GET
http://localhost:5555/routes
获取)/服务ID/**: 服务ID
使用服务发现手动映射路由
手动映射:confsvr中,修改
zuulservice.yml
配置:1
2
3
4zuul.prefix: /api
zuul.routes.organizationservice: /organization/**
zuul.routes.licensingservice: /licensing/**
zuul.routes.authenticationservice: /auth/**此时的映射路由会添加(注意原先的还在,而且加上了前缀
api
)/api/服务ID去掉service后缀/**:服务ID
也就是对URL完成了缩短
使用静态URL手动映射路由
适用场景:非Java开发的项目(没有注册到服务发现中)
静态URL是指向未通过Eureka服务发现引擎注册的服务的URL
禁用Ribbon与Eureka集成,手动指定负载均衡的服务实例
1 | zuul.routes.licensestatic.path: /注意加上zuul.prefix(如果有的话)/otherService/** |
- 负载均衡
1
2
3
4
5
6 # 定义一个 服务ID
zuul.routes.licensestatic.serviceId: licensestatic
# 多个实例(可能是非Java开发的)
zuul.routes.licensestatic.ribbon.listOfServers: http://licenseservice-static1:8081, http://licenseservice-static2:8082
# 此时就不需要ribbon了,所以要加上false
ribbon.eureka.enabled: false
动态重新加载路由配置
如果属性文件放在git仓库中,那么可以到仓库中来手动修改配置,且让Zuul能够重新加载这些配置,就可以发送POST请求到
POST:http://localhost:5555/refresh
来让微服务主动刷新配置
设置超时
原因:
- Hystrix的超时时间为1秒
- Ribbon的超时时间为5秒
- Ribbon的懒加载导致第一次调用慢
剩下的这些配置没讲
1
2
3
4
5
6 zuul.sensitiveHeaders: Cookie,Set-Cookie
zuul.debug.request: true
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 2500
#hystrix.command.licensingservice.execution.isolation.thread.timeoutInMilliseconds: 2
#licensingservice.ribbon.ReadTimeout: 2
signing.key: "345345fsdfsf5345"
过滤器
使用Zuul和Zuul过滤器允许开发人员为通过Zuul路由的所有服务实现横切关注点
ZuulFilter
- 前置过滤器,在Zuul将实际请求发送到目的地之前被调用
- 后置过滤器,在目标服务被调用并将响应发送回客户端后被调用
- 路由过滤器,用于在调用目标服务之前拦截调用
前置过滤器
1 |
|
路由过滤器
动态路由
没细讲,跳过
后置过滤器
通过filterType
来区分前置与后置
1 |
|
过滤器总结
主要功能:
- 当用户请求头中没有关联ID时,zuulservice会在前置过滤器随机生成一个(设为
X
),并传给licensing - licensing的filter会获取到
X
并保存到线程中。- 若licensing需要向organization调用服务(我们只考虑这种情况,否则也不需要关联ID了),则在发送请求之前会由Hystrix获取线程中的
X
(见[2.2 licensing-service](### licensing-service)的第二点),然后再发送请求给zuulservice让他帮忙联系organization。(如果organization长时间未响应则会触发@HystrixCommand
中具体参数的响应)
- 若licensing需要向organization调用服务(我们只考虑这种情况,否则也不需要关联ID了),则在发送请求之前会由Hystrix获取线程中的
- zuulservice会在前置过滤器再次判断,但是这时候有关联ID了,就是这个
X
,然后带着这个X
向organization发送请求 - organization接收到请求,像licensing一样保存这个ID
X
,返回数据,且带上X
给zuulservice - zuulservice此时会在后置过滤器中处理这个
X
,并传给licensing - licensing处理完用户请求,并再次带着
X
返回结果给zuulservice - licensing的处理结果会带着
X
再次进入zuulservice的后置过滤器,zuulservice会在最终返回给用户的response带上这个X
- 若用户请求头有关联ID也同理
具体实现
zuulsvr
添加依赖
1
2
3
4<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>启动类加注解
@EnableZuulProxy
编写相关配置
application.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17server:
port: 5555
#Setting logging levels
logging:
level:
com.netflix: WARN
org.springframework.web: WARN
com.thoughtmechanix: DEBUG
eureka:
instance:
preferIpAddress: true
client:
registerWithEureka: true # 服务网关也要注册到服务发现(erueka)中去
fetchRegistry: true
serviceUrl:
defaultZone: http://localhost:8761/eureka/bootstrap.yml
1
2
3
4
5
6
7
8
9spring:
application:
name: zuulservice
profiles:
active:
default
cloud:
config:
enabled: true
licensing-service
在调用organization-service的地方做出一些修改
原先的调用代码:
1
2
3
4
5
6
7
8
9public Organization getOrganization(String organizationId){
ResponseEntity<Organization> restExchange =
restTemplate.exchange(
"http://organizationservice/v1/organizations/{organizationId}",
HttpMethod.GET,
null, Organization.class, organizationId);
return restExchange.getBody();
}修改成:
1
2
3
4
5
6
7
8
9
10
11
12public Organization getOrganization(String organizationId){
ResponseEntity<Organization> restExchange =
restTemplate.exchange(
// 这个URL要根据具体情况来改变
// 如是否有api(即zuul.prefix)
// 如organization是否有后缀(即手动配置路由))
// 但是/v1及以后的是固定的(即真正的organization服务地址)
"http://zuulservice/api/organization/v1/organizations/{organizationId}",
HttpMethod.GET,
null, Organization.class, organizationId);
return restExchange.getBody();
}为了让Hystrix能够获取到线程中的关联ID,需要取消上节课中【支持Ribbon的RestTemplate的拦截器】(即
UserContextInterceptor
)前取消@Component
注解;然后在启动类中主动添加@Bean
与@LoadBalanced
。看不懂也没关系,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Application {
public RestTemplate getRestTemplate(){
RestTemplate template = new RestTemplate();
List interceptors = template.getInterceptors();
if (interceptors==null){
template.setInterceptors(Collections.singletonList(new UserContextInterceptor()));
}
else{
interceptors.add(new UserContextInterceptor());
template.setInterceptors(interceptors);
}
return template;
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}