Spring Cloud之Zuul入门
为什么用微服务网关?
我们使用Spring Cloud Netflix中的Eureka实现了服务注册中心以及服务注册与发现;而服务间通过Ribbon或Feign实现服务的消费以及均衡负载;通过Spring Cloud Config实现了应用多环境的外部化配置以及版本管理。为了使得服务集群更为健壮,使用Hystrix的融断机制来避免在微服务架构中个别服务出现异常时引起的故障蔓延。
在该架构中,我们的服务集群包含:内部服务Service A和Service B,他们都会注册与订阅服务至Eureka Server,而Open Service是一个对外的服务,通过均衡负载公开至服务调用方。我们把焦点聚集在对外服务这块,直接暴露我们的服务地址,这样的实现是否合理,或者是否有更好的实现方式呢?
先来说说这样架构需要做的一些事儿以及存在的不足:
- 首先,破坏了服务无状态特点。
- 为了保证对外服务的安全性,我们需要实现对服务访问的权限控制,而开放服务的权限控制机制将会贯穿并污染整个开放服务的业务逻辑,这会带来的最直接问题是,破坏了服务集群中REST API无状态的特点。
- 从具体开发和测试的角度来说,在工作中除了要考虑实际的业务逻辑之外,还需要额外考虑对接口访问的控制处理。
- 其次,无法直接复用既有接口。
- 当我们需要对一个即有的集群内访问接口,实现外部服务访问时,我们不得不通过在原有接口上增加校验逻辑,或增加一个代理调用来实现权限控制,无法直接复用原有的接口。
面对类似上面的问题,我们要如何解决呢?答案是:服务网关!
微服务网关介绍
微服务网关封装了应用程序的内部结构,客户端只需要和网关交互,而无需调用特定的微服务接口。使开发可以得到简化。
网关的作用就是将分散的微服务,以一种概念统一起来(使外界看来是一个单体应用)
(外聚 内分)强大的架构目的就是让你感受不到他的存在。
使用
引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
会发现 包含了ribbon和hystrix
![屏幕快照 2018-11-22 下午10.03.49](Spring-Cloud之Zuul入门/屏幕快照 2018-11-22 下午10.03.49.png)
配置启动类
@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication
public class ZuulGateway {
public static void main(String[] args) {
SpringApplication.run(ZuulGateway.class);
}
}
更改全局配置文件
spring:
application:
name: gateway
eureka:
client:
service-url:
defaultZone : http://localhost:10087/eureka
instance:
prefer-ip-address: true
ip-address: 127.0.0.1
server:
port: 10010
zuul:
routes:
user-service: /user/**
ignored-services:
- consumer-demo
zuul配置详解
示例0
什么也不写!
默认路由就是服务名。
示例1
因为已经有了Eureka客户端,我们可以从Eureka获取服务的地址信息,因此映射时无需指定IP地址,而是通过服务名称来访问,而且Zuul已经集成了Ribbon的负载均衡功能。
zuul:
routes:
user-service: # 这里是路由id,随意写
path: /user-service/** # 这里是映射路径
serviceId: user-service # 指定服务名称
示例2
简化配置 功能同上
zuul:
routes:
user-service: /user-service/** # 这里是映射路径
示例3 (路由前缀)
zuul:
prefix: /api # 添加路由前缀
routes:
user-service: # 这里是路由id,随意写
path: /user-service/** # 这里是映射路径
service-id: user-service # 指定服务名称
strip-prefix: false #去除路径前缀
关于前缀的源码
默认为true 改为false会去除前缀
/**
* Flag saying whether to strip the prefix from the path before forwarding.
*/
private boolean stripPrefix = true;
Zuul过滤器
- 正常流程:
- 请求到达首先会经过pre类型过滤器,而后到达routing类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。
- 异常流程:
- 整个过程中,pre或者routing过滤器出现异常,都会直接进入error过滤器,再error处理完毕后,会将请求交给POST过滤器,最后返回给用户。
- 如果是error过滤器自己出现异常,最终也会进入POST过滤器,而后返回。
- 如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和routing不同的时,请求不会再到达POST过滤器了。
简单demo示例(过滤请求参数是否有access-token值)
@Component
public class LoginFilter extends ZuulFilter {
@Override
public String filterType() {
//相当于 return "pre"
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
//设置过滤器的优先级
return FilterConstants.PRE_DECORATION_FILTER_ORDER -1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
//过滤操作
//获取请求上下文
RequestContext currentContext = RequestContext.getCurrentContext();
//获取请求request对象
HttpServletRequest request = currentContext.getRequest();
//通过request对象获取请求参数
String accessToken = request.getParameter("access-token");
//判断token是否为空 包括null和空字符
if(StringUtils.isBlank(accessToken)){
currentContext.setSendZuulResponse(false);
currentContext.setResponseStatusCode(HttpStatus.FORBIDDEN.value());
}
return null;
}
}
利用Zuul在网关中配置负载均衡和熔断
zuul:
retryable: true
ribbon:
ConnectTimeout: 250 # 连接超时时间(ms)
ReadTimeout: 2000 # 通信超时时间(ms)
OkToRetryOnAllOperations: true # 是否对所有操作重试
MaxAutoRetriesNextServer: 2 # 同一服务不同实例的重试次数
MaxAutoRetries: 1 # 同一实例的重试次数
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMillisecond: 6000
注意:由于MaxAutoRetriesNextServer的默认值为1 根据源代码可以发现当ribbon(ConnectTimeout+ReadTimeout)*(0+MaxAutoRetriesNextServer)< hystrix 的 timeoutInMillisecond时候才会起作用 。否则ribbon几乎无用,会被hystrix熔断。