应用场景
微服务在真实应用中一般都会有N多个服务节点组成的服务集群,每个服务都可能依赖多个服务进行本身的业务处理,比如:用户登录操作可能会调用 账号验证服务、身份鉴别服务、生成日志服务、路由转向服务、受权服务等一系列服务才能完成一次登录操作。这样会带来几个问题:
- 客户端多次请求不同的微服务,增加客户端代码或配置编写的复杂性
- 登录信息认证繁琐,访问每个服务都要进行一次认证
- 每个服务都通过http访问,导致http请求次数增加,效率不高拖慢系统性能
- 多个服务存在跨域请求问题,处理起来比较复杂
我们需要解决这些问题,尝试想一下,不要让前端客户端直接调用后台诸多微服务,我们的服务集群从整个应用系统层面划分,不管大小服务都是服务层的系统元素,对服务管理而言业务被拆分成无数小颗粒度松耦合的独立服务,但对于前端调用服务的用户来说,所有服务是一个整体。对于前台客户端应用而言后台服务应该仍然类似于单体应用一样,要求只有一个服务调度入口即可,于是我们可以在客户端和服务端之间增加一个API网关,所有的外部请求先通过这个微服务网关。前台客户端只需跟网关进行交互,而由网关进行各个微服务的调用。熟悉面向对象设计模式的人应该很容易想到这是“门户模式”的典型应用。经“门户模式”的设计改良后有如下优点:
- 减少客户端与微服务之间的调用次数,提高效率
- 便于监控,可在网关中监控数据,可以做统一切面任务处理
- 便于认证,只需要在网关进行认证即可,无需每个微服务都进行认证
- 降低客户端调用服务端的复杂度
SpringCloud已经为我们考虑到这一点需求并提供解决方案,SpringCloud通集成Zuul组件完成对服务的路由功能。
概念简介
zuul是Netflix设计用来为所有面向设备、web网站提供服务的所有应用的门面,zuul可以提供动态路由、监控、弹性扩展、安全认证等服务,他还可以根据需求将请求路由到多个应用中。
搭配使用
当然在Netflix的使用中,zuul也结合了Netflix的其他微服务组件一起使用。
• Hystrix:用来服务降级及熔断
• Ribbon:用来作为软件的负载均衡,微服务之间相互调用是通过ribbon作为软件负载均衡使用负载到微服务集群内的不同的实例
• Turbin:监控服务的运行状况
• Feign:用作微服务之间发送rest请求的组件,可以将rest调用类似spring的其他bean一样直接注入使用功能
• Eureka:服务注册中心
入门示例
我们用Idea来创建测试项目,用Spring Initializr可以顺利完成创建过程。
最终的项目代码框架如下图。
引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
系统配置
#访问端口
server:
port: 8766
#服务名称
spring:
application:
name: routing.zull.web
#安全认证信息
security:
user:
name: admin
password: 123456
#服务注册
eureka:
server:
register: localhost:8760
client:
serviceUrl:
defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@${eureka.server.register}/eureka/
zuul:
#如果是url的方式,则zuul.host开头的生效。
host:
connect-timeout-millis: 6000
socket-timeout-millis: 6000
#如果路由方式是serviceId的方式,那么ribbon的生效
ribbon:
ReadTimeout: 3000
SocketTimeout: 3000
routes:
#路由节点a
api-a:
path: /api-a/**
serviceId: EUREKA.CLIENT.FIRST.WEBAPI
#路由节点b
api-b:
path: /api-b/**
serviceId: EUREKA.CLIENT.FIRST.WEBAPI
#路由节点c
api-c:
path: /api-c/**
serviceId: EUREKA.CLIENT.FIRST.WEBAPI
启动类
@SpringBootApplication/*服务启动*/
@EnableZuulProxy/*服务路由*/
@EnableEurekaClient/*服务注册*/
@EnableDiscoveryClient/*服务发现*/
public class RoutingZullWebApplication {
public static void main(String[] args) {
SpringApplication.run(RoutingZullWebApplication.class, args);
}
}
启动测试
反向代理
Spring-Zuul作为一款优秀的路由网关组件支持反向代理功能,通过添加路由配置项url来实现网络资源重定向功能。
路由节点api-d对https://blog.csdn.net反向代理
api-d:
path: /proxy/**
url: https://blog.csdn.net
当客户端访问http://xxx.xxx.xxx.xxx:xx/proxy 的时候,网关路由自动重定向https://blog.csdn.net资源。
• api-d自定义的前缀
• path匹配的地址
• url路由到哪个服务
启动测试
http://localhost:8766/proxy/xxxlllbbb
过滤器
Zuul大部分功能都是通过过滤器来实现的,这些过滤器类型对应于请求的典型生命周期。
PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
ERROR:在其他阶段发生错误时执行该过滤器。
除了默认的过滤器类型,Zuul还允许我们创建自定义的过滤器类型。例如,我们可以定制一种STATIC类型的过滤器,直接在Zuul中生成响应,而不将请求转发到后端的微服务。
Zuul中默认实现的Filter。
下面是最常用的Filter作用。
/**
* 限流
*/
@Component
public class AccessFilter extends MyFilter {
// 每秒放多少个令牌 (100个)
private static final RateLimiter rateLimiter = RateLimiter.create(1);
public AccessFilter() {
super(FilterType.PRE, 0);
}
@Override
public Object execute(HttpServletRequest request, RequestContext ctx) throws Exception {
String msg = String.format("AccessFilter 流量限制>>%s: %s", request.getMethod(), request.getRequestURL().toString());
System.out.println(msg);
//限流、限速
if (!rateLimiter.tryAcquire()) {
String msgs="访问太频繁了,请稍后。。。";
System.out.println(msgs);
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
ctx.getResponse().setContentType("application/json; charset=UTF-8");
BaseData bdata = BaseData.result(401, "访问限制", msgs);
// 内容重新写入
ctx.setResponseBody(JSON.toJSONString(bdata));
return null;
}
return null;
}
}
/**
* @路由之前执行
* token 身份验证
*/
@Component
public class AuthenticateFilter extends MyFilter{
public AuthenticateFilter() {
super(FilterType.PRE, 1);
}
@Override
public Object execute(HttpServletRequest request, RequestContext ctx) throws Exception {
String msg = String.format("PreFilter 权限验证>>%s: %s", request.getMethod(), request.getRequestURL().toString());
System.out.println(msg);
Object accessToken = request.getParameter("token");
if (accessToken == null) {
System.out.println("token is empty");
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
ctx.getResponse().setContentType("application/json; charset=UTF-8");
BaseData bdata = BaseData.result(401, "权限验证", "token is empty");
ctx.setResponseBody(JSON.toJSONString(bdata));
}
return null;
}
}
**
* @发送错误调用
* 路由容错处理
*/
@Component
public class ErrorFilter extends MyFilter {
public ErrorFilter() {
super(FilterType.ERROR, -1);
}
@Override
public boolean shouldFilter() {
return RequestContext.getCurrentContext().containsKey("throwable");
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletResponse response = ctx.getResponse();
String msg = String.format("ErrorFilter>>%s: %s", ctx.getRequest().getMethod(), ctx.getRequest().getRequestURL().toString());
System.out.println(msg);
Object e = ctx.get("throwable");
if (e != null && e instanceof ZuulException) {
ZuulException zuulException = (ZuulException) e;
ctx.remove("throwable");
ctx.setSendZuulResponse(false);
ctx.getResponse().setContentType("application/json; charset=UTF-8");
BaseData bdata = BaseData.result(401, "服务故障", zuulException.getMessage());
ctx.setResponseBody(JSON.toJSONString(bdata));
}
return null;
}
}
/**
* @路由之后执行
* 对服务接果进行统一处理
*/
@Component
public class PostFilter extends MyFilter {
public PostFilter() {
super(FilterType.POST, 2);
}
@Override
public Object execute(HttpServletRequest request, RequestContext ctx) throws Exception {
String msg = String.format("PostFilter 返回结果>>%s: %s", request.getMethod(), request.getRequestURL().toString());
System.out.println(msg);
// 获取返回值内容,加以处理
InputStream stream = ctx.getResponseDataStream();
String body = StreamUtils.copyToString(stream, Charset.forName("UTF-8"));
BaseData bdata = BaseData.result(ctx.getResponseStatusCode(), "请求成功", body);
// 内容重新写入
ctx.setResponseBody(JSON.toJSONString(bdata));
return null;
}
}
禁用指定的Filter
可以在application.yml中配置需要禁用的filter,格式:
zuul:
FormBodyWrapperFilter:
pre:
disable: true
的高可用
我们实际使用Zuul的方式如上图,不同的客户端使用不同的负载将请求分发到后端的Zuul,Zuul在通过Eureka调用后端服务,最后对外输出。因此为了保证Zuul的高可用性,前端可以同时启动多个Zuul实例进行负载,在Zuul的前端使用Nginx或者F5进行负载转发以达到高可用性。
至此关于SpringCloud-Zuul常用路由网关就介绍完了。
源代码地址:https://github.com/crexlb/cre.springcloud.examples