在Spring Cloud微服务系统中,一种常见的负载均衡方式是,客户端的请求首先经过负载均衡(apache、Ngnix),再到达服务网关(zuul集群),然后再到具体的服。
Zuul的主要功能是路由转发和过滤器。路由功能是微服务的一部分,比如/api/user转发到到user服务,/api/shop转发到到shop服务。zuul默认和Ribbon结合实现了负载均衡的功能
在前文的基础上,重新创建一个工程,加入以下依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> </dependency> <dependency> <groupId>com.marcosbarbero.cloud</groupId> <artifactId>spring-cloud-zuul-ratelimit</artifactId> <version>1.3.4.RELEASE</version> </dependency>
|
在主类上加入注解@EnableZuulProxy
和@EnableEurekaClient
,开启zuul的功能
1 2 3 4 5 6 7 8
| @EnableZuulProxy @EnableEurekaClient @SpringBootApplication public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } }
|
编辑配置文件application.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ server: port: 8083 spring: application: name: gateway zuul: routes: service-sample: path: /sample/** serviceId: sample service-1: path: /service1/** serviceId: service1
|
在上面配置中,指定网关服务名为gateway,运行在8083端口,配置了两条路由规则。以/sample/开头的请求都转发到sample服务,以/service1/的请求都转发到service1服务。
在浏览器访问localhost:8083/sample/param?a=1
,返回结果
证明zuul起到了路由的作用。
过滤器
zuul的过滤器不是web服务的过滤器,它能实现如权限验证,身份校验,负载均衡、限流等一系列功能,一个简单的过滤器如下:
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| @Component public class AccessFilter extends ZuulFilter{ @Value("${resource.exception.extension}") private String exceptionExtension; @Override public String filterType() { return FilterConstants.PRE_TYPE; } @Override public int filterOrder() { return 1; } @Override public boolean shouldFilter() { if(StringUtils.isBlank(exceptionExtension)){ return true; } RequestContext ctx = RequestContext.getCurrentContext(); String requestPath = ctx.getRequest().getRequestURI(); String extension = UriUtils.extractFileExtension(requestPath); if(StringUtils.isBlank(extension)||StringUtils.equals(extension,"action")){ return true; } String []extensions = exceptionExtension.split(","); for(String ex:extensions){ if(StringUtils.equals(ex,extension)){ return false; } } return true; } @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); try { HttpServletRequest request = ctx.getRequest(); String token = request.getParameter("token"); IMapEntry params = HttpServletUtil.getRequestParameters(ctx); if(StringUtils.isBlank(token)){ ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(200); IMapEntry result = ResultUtil.resultFailure(CommonConstant.RESULT_CODE_FAIL,"token不能为空"); ctx.setResponseBody(JSONUtil.toJSONString(result)); } }catch (Exception e){ DICLogger.error("在权限验证时发生错误",e); ctx.set("error.status_code", HttpServletResponse.SC_INTERNAL_SERVER_ERROR); ctx.set("error.exception", e); ctx.set("error.message", "在权限验证时发生错误"); } return null; } }
|
上面实现的是一个访问的过滤器,作用是拦截接口请求,对rest请求和后缀是.action的请求做拦截,校验token。局部变量exceptionExtension
是例外列表,是从配置文件读取的,指定的文件不做过滤如jsp,css,js等静态文件。请求的判断逻辑在shouldFilter方法中。在run方法中写token校验逻辑,这里只是简单的判断了以下非空。
熔断处理
zuul转发请求是通过ribbon请求指定的服务,也会存在服务不可用的情况,zuul自己做了熔断,但是返回的数据格式不一定是我们需要的格式,我们可以自己写熔断逻辑
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| @Component public class HystrixFallback implements ZuulFallbackProvider{ @Override public String getRoute() { return "*"; } @Override public ClientHttpResponse fallbackResponse() { return new ClientHttpResponse() { @Override public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); MediaType type = new MediaType("application","json",Charset.forName("UTF-8")); headers.setContentType(type); return headers; } @Override public InputStream getBody() throws IOException { IMapEntry result = ResultUtil.resultFailure(CommonConstant.RESULT_CODE_FAIL,"服务不可用,请稍后重试"); return new ByteArrayInputStream(JSONUtil.toJSONString(result).getBytes("UTF-8")); } @Override public HttpStatus getStatusCode() throws IOException { return HttpStatus.OK; } @Override public int getRawStatusCode() throws IOException { return this.getStatusCode().value(); } @Override public String getStatusText() throws IOException { return this.getStatusCode().getReasonPhrase(); } @Override public void close() { } }; } }
|
getRoute方法返回一个路由名(如上面的service-sample或service-1),指定该熔断是处理哪一个服务的请求,返回null
或*
表示处理所有的服务请求。
统一错误处理
zuul同样统一处理了错误,在网上的一些版本中,需要写一个error过滤器,在新版本中已经不需要了,只需要实现一个bean就行,具体可参考 Spring Cloud Zuul中异常处理细节
1 2 3 4 5 6 7 8 9 10
| @Component public class CustomerErrorMsg extends DefaultErrorAttributes{ @Override public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) { Map<String, Object> result = super.getErrorAttributes(requestAttributes,includeStackTrace); result.put("code",result.get("status")); result.put("msg",result.get("error")); return result; } }
|
在上面的例子中,重写了getErrorAttributes方法,在原来的错误消息格式中,新加了两个字段code和msg。通过过滤器的方式可以参考这篇博客Spring Cloud实战小贴士:Zuul统一异常处理(三)【Dalston版】。
限流
zuul限流使用了GitHub上的一个模块spring-cloud-zuul-ratelimit,具体可参考文档
最后附上我的zuul配置
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| zuul: add-host-header: true routes: gatewayService: path: /gateway/** url: forward:/gateway service1: path: /service1/** serviceId: service1 ratelimit: enabled: true default-policy: limit: 10 quota: 1000 policies: service1: type: url quota: 2000 max: host: connections: 1000 host: socket-timeout-millis: 3000 connect-timeout-millis: 1000 ribbon: ReadTimeout: 1000 ConnectTimeout: 500 OkToRetryOnAllOperations: true MaxAutoRetries: 0 MaxAutoRetriesNextServer: 1 hystrix: command: default: execution: timeout: enabled: true isolation: thread: timeoutInMilliseconds: 5000 resource: exception: extension: html,jsp,css,js,json,txt,jpg,jpeg,png,bmp,svg,woff,eot,ttf,md,zip,doc,docx,ppt,pptx,xls,xlsx
|