SpringCloud 之 Zuul 基础配置与进阶
简介
基础使用
PS:zuul 基本需要配合 Eureka 使用,就不多介绍了:SpringCloud 之 Eureka 配置,Eureka 集群,Eureka 监听
准备
服务A
服务名:service-a
端口号:8080
服务B
服务名:service-b
端口号:8081
zuul 服务
服务名:zuul
端口号:8084
加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
启动器加注释
@EnableZuulProxy
配置
PS:Eureka 配置就不写了,和上面 Eureka 里面客户端配置一样
#配置端口号
server:
port: 8084
spring:
application:
#配置服务名
name: zuul
日志查看
# netflix ⽇志
logging:
level:
com.netflix: DEBUG
如无法掌握Zuul路由的规律,可将com.netflix包的日志级别设为DEBUG。这样,Zuul就会打印 转发的具体细节,从而帮助我们更好地理解Zuul的路由配置
意思:service-a
返回服务器:172.0.0.1:8080
的请求是 /api/1
不加额外配置
在不加多余的配置文件配置时, zuul 就已经可以使用了,我们可以通过 IP + 服务名
的方式结合 Eureka
去访问,如图:
自定义服务访问以及服务忽略
虽然说不加配置就可以使用了,但是 Eureka
中所有的服务都能访问到这显然很不合理,也不安全
因此可以通过自定义服务与服务忽略结合,来提高可用性
ignored-services: '*'
表示忽略所有服务的前提下,只对配置的服务进行路由
ignored-services: service-a,service-b
当然也可以指定服务,逗号隔开
ignoredPatterns: /**/admin/**
如果想使用通配符进行忽略,那就要换 ignoredPatterns 才行
这个是忽略所有包含 /admin/ 的路径
# zuul 配置
zuul:
# 使⽤'*'可忽略所有微服务
# 通过此配置使得只有通过 routes 配置的路由才生效,更安全
ignored-services: '*'
routes:
# 配置 service-a 服务的 访问路径为 /service-a/**
service-a: /service-a/**
现在来访问下 service-a
再来访问下 service-b
可以看到,由于我们忽略了所有的服务后,只配置了service-a
的配置,因此service-a
能请求到,但是service-b
就不行了
自定义路由名配置
这种配置效果和上面横写是一样的,访问路径同样是 http://127.0.0.1:8080/service-a/xxx
# zuul 配置
zuul:
ignored-services: '*'
routes:
# 给路由⼀个名称,可以任意起名
# 通过服务名配置路由
service-a:
service-id: service-a # 指定服务名
path: /service-a/** # 服务名对应的路径
直接通过 URL 配置(有缺陷)
我们也可以直接通过 URL 进行配置,以 service-b
为例
# zuul 配置
zuul:
ignored-services: '*'
routes:
# 通过 URL 配置路由
service-b:
url: http://127.0.0.1:8081 # 指定的url
path: /service-b/** # url对应的路径
PS:使用这种方式配置的路由不会作为 HystrixCommand
执行,同时也不能使用 Ribbon
来负载均衡多个URL
直接通过 URL 配置(无缺陷)
# zuul 配置
zuul:
ignored-services: '*'
routes:
# 改为通过服务名去匹配
service-b:
service-id: service-b # 指定的服务名
path: /service-b/** # url对应的路径
# 禁⽤掉ribbon的eureka使⽤
ribbon:
eureka:
# 详⻅:http://cloud.spring.io/spring-cloud-static/Camden.SR4/#_example_disable_eureka_use_in_ribbon
enabled: false
# 把 service-b 这个服务名与 IP 进行映射
service-b:
ribbon:
listOfServers: localhost:8081,localhost:8082
路由前缀
请求路径:http://127.0.0.1:8080/api/service-a/1
实际请求路径:http://127.0.0.1:8080/service-a/api/1
这种配置估计应用场景应该是不同版本的接口切换吧,切换版本的时候,前端直接在统一配置里把api
换掉,所有请求接口就切到新的版本上
# Zuul 配置
zuul:
ignored-services: '*'
# 路由前缀,把 /api/service-a/1 映射到 /service-a/api/1
prefix: /api
strip-prefix: false
routes:
service-a:
service-id: service-a # 指定服务名
path: /service-a/** # 服务名对应的路径
可以看到请求正常
再来看下日志输出
可以看到实际路径是http://127.0.0.1:8080/service-a/api/1
进阶配置
正则表达式指定Zuul的路由匹配规则
借助PatternServiceRouteMapper,实现从微服务到映射路由的正则配置
servicePattern
指定微服务的正则
routePattern
指定路由正则
正则怎么写,不会。。。
// zuul
@EnableZuulProxy
// Eureka 客户端
@EnableDiscoveryClient
// 由于没数据库,排除配置
@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
// 正则表达式指定Zuul的路由匹配规则
@Bean
public PatternServiceRouteMapper serviceRouteMapper() {
// 调⽤构造函数PatternServiceRouteMapper(String servicePattern, String routePattern)
// servicePattern指定微服务的正则
// routePattern指定路由正则
// 最后一个 "-" 后面以 v 打头的为 version,之前的都是 name
return new PatternServiceRouteMapper("(?<name>^.+)-(?<version>v.+$)", "${version}/${name}");
}
}
自定义 Zuul 拦截器
zuul 除了帮助我们进行路由转发的功能外,还给我们提供了一个过滤器,我们可以通过继承 ZuulFilter
来实现 zuul 过滤器
我们可以通过这个过滤器来实现授权拦截,限流,日志记录等功能
filterType
指定过滤器的调用时机,方便我们在需要的时间段调用过滤器
filterOrder
指定过滤器执行的先后顺便,比如有两个 pre 的过滤器,就可以通过这个返回值控制两个过滤器的先后顺序
shouldFilter
指定过滤器是否使用
run
具体的过滤逻辑
这里我以一个简单的授权过滤器为例子
/**
* Zuul 过滤器
* 授权验证示例
* @author: linjinp
* @create: 2019-09-12 09:42
**/
@Component
public class AuthFilter extends ZuulFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(AuthFilter. class);
/**
* 是否开启验证
* 正常项目里,这种属性应该放配置文件里
*/
private static final Boolean AUTH = Boolean.TRUE;
/**
* 指定过滤器的调用时机
* pre: 路由之前,如实现认证,记录调试信息等
* routing: 路由时
* post: 路由后,比如添加HTTP header
* error: 发生错误时调用
*
* @return
*/
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
/**
* 过滤器顺序
* 比如有两个 pre 的过滤器,可以通过设置数字大小,控制两个过滤器执行先后
*
* @return
*/
@Override
public int filterOrder() {
return 1;
}
/**
* 判断是否启用该过滤
*
* @return
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 过滤的具体逻辑
*
* @return
* @throws ZuulException
*/
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
// 启动验证
if (AUTH) {
String token = request.getHeader("Authorization");
if (token == null) {
LOGGER.info("该访问未进行授权");
// 路由失败
ctx.setSendZuulResponse(false);
// 返回错误码
ctx.setResponseStatusCode(401);
} else {
LOGGER.info("访问已授权");
// 验证成功
ctx.setSendZuulResponse(true);
ctx.setResponseStatusCode(200);
}
} else {
LOGGER.info("访问成功");
// 没启用就直接成功
ctx.setSendZuulResponse(true);
ctx.setResponseStatusCode(200);
}
return null;
}
}
看看日志打印
禁用自定义拦截器
除了在拦截器里直接关闭外,我们也可以通过在配置文件里配置关闭拦截器
zuul.<SimpleClassName>.<filterType>.disable=true
比如我要关闭上面的 AuthFilter
拦截器
zuul:
# AuthFilter 拦截器开关
AuthFilter:
pre:
disable: true
容错与回退
当我们使用 zuul 时,难免会出现接口因为各种原因请求不通的情况,比如服务宕机了之类的
为了保证系统的健壮性,我们可以通过实现 FallbackProvider
方法,来自定义返回内容
- Edgware版本写法:实现
FallbackProvider
- Edgware之前版本:实现
ZuulFallbackProvider
/**
* 容错与回退
* @author: linjinp
* @create: 2019-09-12 11:27
**/
@Component
public class CommonFallbackProvider implements FallbackProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(AuthFilter. class);
/**
* 需要回退的微服务,"*" 表示所有
*
* @return
*/
@Override
public String getRoute() {
return "*";
}
/**
* 回退逻辑
* @param route
* @param cause
* @return
*/
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
if (cause instanceof HystrixTimeoutException) {
return response(HttpStatus.GATEWAY_TIMEOUT);
} else {
return response(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
/**
* 自定义返回值内容
* @param status
* @return
*/
private ClientHttpResponse response(final HttpStatus status) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return status;
}
@Override
public int getRawStatusCode() throws IOException {
return status.value();
}
@Override
public String getStatusText() throws IOException {
return status.getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
LOGGER.info("服务不可⽤,请稍后再试");
return new ByteArrayInputStream("服务不可⽤,请稍后再试。".getBytes());
}
@Override
public HttpHeaders getHeaders() {
// headers设定
HttpHeaders headers = new HttpHeaders();
MediaType mt = new MediaType("application", "json", Charset.forName(
"UTF-8"));
headers.setContentType(mt);
return headers;
}
};
}
}
现在我们把 service-a
停掉,然后请求试试,已模拟服务宕机
转载:https://blog.csdn.net/qq_37143673/article/details/100736962