面向服务的路由
在刚才的路由规则中,我们把路径对应的服务地址写死了!如果同一服务有多个实例的话,这样做显然就不合理了。
我们应该根据服务的名称,去Eureka注册中心查找 服务对应的所有实例列表,然后进行动态路由才对!
添加Eureka客户端依赖
<!--添加eureka客户端--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
开启Eureka客户端发现功能
package com.czxy; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; @SpringBootApplication @EnableZuulProxy //开启zuul代理 @EnableEurekaClient //开启eureka客户端 public class ZuulApplication { public static void main(String[] args) { SpringApplication.run(ZuulApplication.class,args); } }
添加Eureka配置 获取服务信息
#eureka配置 eureka: client: service-url: defaultZone: http://localhost:10086/eureka registry-fetch-interval-seconds: 5 instance: prefer-ip-address: true #是否注册ip地址 ip-address: 127.0.0.1 #注册的IP地址
修改映射配置 通过服务名获取
因为已经有了Eureka客户端,我们可以从Eureka获取服务的地址信息,因此映射时无需指定IP地址,而是通过服务名称来访问
1.#路由配置 zuul: routes: service: path: /service/** #zuul拦截的路径 # url: http://localhost:9010 serviceId: service #将要访问的服务名
启动测试
简化的路由配置
在刚才的配置中,我们的规则是这样的:
- zuul.routes.<route>.path=/xxx/**: 来指定映射路径。<route>是自定义的路由名
- zuul.routes.<route>.serviceId=service:来指定服务名。
而大多数情况下,我们的<route>路由名称往往和 服务名会写成一样的。因此Zuul就提供了一种简化的配置语法:zuul.routes.<serviceId>=<path>
比方说上面我们关于user-service的配置可以简化为一条:
# zuul 网关配置路由配置 zuul: routes: classes-service: /classes-service/**
省去了对服务名称的配置。
默认的路由规则
在使用Zuul的过程中,上面讲述的规则已经大大的简化了配置项。但是当服务较多时,配置也是比较繁琐的。因此Zuul就指定了默认的路由规则:
默认情况下,一切服务的映射路径就是服务名本身。
例如服务名为:service,则默认的映射路径就是:/service/**
也就是说,刚才的映射规则我们完全不配置也是OK的,不信就试试看
路由前缀
配置示例:
1.#路由配置 zuul: prefix: /api routes: service: /service/** #zuul拦截的路径
我们通过zuul.prefix=/api来指定了路由的前缀,这样在发起请求时,路径就要以/api开头。
路径/api/student-service/student将会被代理到/student-service/student
过滤器
Zuul作为网关的其中一个重要功能,就是实现请求的鉴权。而这个动作我们往往是通过Zuul提供的过滤器来实现的。
ZullFilter
ZuulFilter是过滤器的顶级父类。在这里我们看一下其中定义的4个最重要的方法:
public abstract ZuulFilter implements IZuulFilter{ abstract public String filterType(); //过滤器类型 abstract public int filterOrder(); //执行顺序 boolean shouldFilter(); //是否需要执行 Object run() throws ZuulException; //具体业务逻辑 }
- shouldFilter:返回一个Boolean值,判断该过滤器是否需要执行。返回true执行,返回false不执行。
- run:过滤器的具体业务逻辑。
- filterType:返回字符串,代表过滤器的类型。包含以下4种:
- pre:请求在被路由之前执行
- routing:在路由请求时调用
- post:在routing和errror过滤器之后调用
- error:处理请求时发生错误调用
- filterOrder:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。
过滤器执行生命周期
这张是Zuul官网提供的请求生命周期图,清晰的表现了一个请求在各个过滤器的执行顺序。
- 正常流程:
请求到达首先会经过pre类型过滤器,而后到达routing类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。
- 异常流程:
整个过程中,pre或者routing过滤器出现异常,都会直接进入error过滤器,再error处理完毕后,会将请求交给POST过滤器,最后返回给用户。
如果是error过滤器自己出现异常,最终也会进入POST过滤器,而后返回。
如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和routing不同的时,请求不会再到达POST过滤器了。
所有内置过滤器列表:
使用场景
场景非常多:
请求鉴权:一般放在pre类型,如果发现没有访问权限,直接就拦截了
异常处理:一般会在error类型和post类型过滤器中结合来处理。
服务调用时长统计:pre和post结合使用。
自定义过滤器
接下来我们来自定义一个过滤器,模拟一个登录的校验。基本逻辑:如果请求中有authorization请求头,则认为请求有效,放行。
定义过滤器类
package com.czxy.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.springframework.http.HttpStatus; import javax.servlet.http.HttpServletRequest; /** * Created by liangtong. */ @Component public class LoginFilter extends ZuulFilter { @Override public String filterType() { return "pre"; //路由之前执行 } @Override public int filterOrder() { return 1; //排序 } @Override public boolean shouldFilter() { return true; //是否进行过滤,true将执行run()方法 } @Override public Object run() throws ZuulException { //1 获得上下文对象 RequestContext requestContext = RequestContext.getCurrentContext(); //2 获得请求对象 HttpServletRequest request = requestContext.getRequest(); //3 获得指定的请求头 String token = request.getHeader("authorization"); //如果没有token返回401 if(token == null || "".equals(token)) { requestContext.setSendZuulResponse(false); requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value()); } return null; } }
测试
没有token
有token
负载均衡和熔断
Zuul中默认就已经集成了Ribbon负载均衡和Hystix熔断机制。但是所有的超时策略都是走的默认值,比如熔断超时时间只有1S,很容易就触发了。因此建议我们手动进行配置:
#网关配置 zuul: retryable: true #开启重试(注意:检查之前是否配置zuul,如果配置需要合并配置项) #全局负载均衡配置 ribbon: ConnectTimeout: 250 # 连接超时时间(ms) ReadTimeout: 2000 # 通信超时时间(ms) OkToRetryOnAllOperations: true # 是否对所有操作重试 MaxAutoRetriesNextServer: 2 # 同一服务不同实例的重试次数 MaxAutoRetries: 1 # 同一实例的重试次数 #熔断 hystrix: command: default: execution: isolation: thread: timeoutInMillisecond: 6000 # 熔断超时时长:6000ms
后端代码
参考: