在微服务架构中,如何把拆分好的大量微服务服务对外提供统一的访问接口,从而将APP的具体实现细节对使用透明化是一个共同的挑战,Netflix zuul可以完美的解决这个问题。可以把它看做是微服务架构的门户,把请求代理到对应的后台应用,能够使浏览器、手机APP、或者是应用接口访问分布在不同主机的服务而不用考虑跨域访问问题( CORS)。zuul能够和其他的Netflix组件结合使用,如Hystrix一起提供熔断机制,利用Eureka做服务发现,也能够用来做路由过滤,负载均衡等。
应用
1. API网关模式
特点
- 通过增加API网关使得客户端和具体的实现隔离开来
- 客户端不用关心具体的服务部署的位置
- 可以把合并逻辑放在API网关层来减少客户端复杂性
- 引入网关层增加了系统的复杂性
- 相比直接访问,因为追加了一层路由跳跃,响应时间会增加
简单路由
zuul: routes: get: path: /portal/** url: http://portal links: path: /bd/** url: http://bdip images: path: /sd/** url: http://sd ribbon: eureka: enabled: false
上面的配置中包含三个命名路由,每个都包含一个path和一个url,其中path定义了在zull server中匹配的路径,path定义了所代理的服务的URL。
追加负载均衡
因为spring cloud zuul已经和客户端负载均衡组件ribbon,所以可以很方便的实现集成,我们只需要把它们启用就可以了。
zuul: routes: portal: path: /** serviceId: portal portal: ribbon: listOfServers: http://portal1.http://portal2 ribbon: eureka: enabled: false
利用服务发现
zuul: routes: portal: path: /portal/** serviceId: portal eureka: client: serviceUrl: defaultZone: ${EUREKA_URL:http://user:password@localhost:5000}/eureka/
其中portal就是真正的服务在eureka中的名字
2. 动态路由
zuul内置framework支持动态的read, compile, and run filters,
private void initGroovyFilterManager() { FilterLoader.getInstance().setCompiler(new GroovyCompiler()); String scriptRoot = System.getProperty("zuul.filter.root", ""); if (scriptRoot.length() > 0) scriptRoot = scriptRoot + File.separator; try { FilterFileManager.setFilenameFilter(new GroovyFileFilter()); FilterFileManager.init(5, scriptRoot + "pre", scriptRoot + "route", scriptRoot + "post"); } catch (Exception e) { throw new RuntimeException(e); } }
package filters.pre import com.netflix.zuul.ZuulFilter import com.netflix.zuul.context.RequestContext /** * @author mhawthorne */ class PreDecorationFilter extends ZuulFilter { @Override int filterOrder() { return 5 } @Override String filterType() { return "pre" } @Override boolean shouldFilter() { return true; } @Override Object run() { RequestContext ctx = RequestContext.getCurrentContext() // sets origin ctx.setRouteHost(new URL("http://httpbin.org")); // sets custom header to send to the origin ctx.addOriginResponseHeader("cache-control", "max-age=3600"); } }
3. zuul filter
4.配置重试:
@EnableZuulProxy->ZuulProxyConfiguration->HttpClientRibbonConfiguration(默认)->HttpClientRibbonCommandFactory创建HttpClientRibbonCommand(利用SpringClientFactory创建RetryableRibbonLoadBalancingHttpClient(调用execute方法是委托给RetryTemplate,若是发生Hystrix times out,则直接返回500,所以The hystrix timeout needs to be slightly greater than the aggregate timeouts of the original request and all retries(hsytrix timeout 需要比 原始请求时间*单个server的总请求次数*总请求server个数 的结果要长,总的请求次数为1+最大重试次数:MaxAutoRetries,总请求server个数为1+最大请求server个数:MaxAutoRetriesNextServer);在executeWithRetry方法中调用loadBalancedRetryPolicyFactory.create方法创建重试策略LoadBalancedRetryPolicy,此类的registerThrowable方法中,当同一个instance重试的次数达到最大值,并且能重试下一个instance时(nextServerCount<=设定的最大重试次数,如果成立LoadBalancedRetryPolicy类重新调用RetryableRibbonLoadBalancingHttpClient.choose()方法,委托调用ZoneAwareLoadBalancer.chooseServer(serverId)方法,在不分zone的时候,调用BaseLoadBalancer.chooseServer(serverId),之后委托调用PredicateBasedRule.choose(key),采用RoundRobin的方法获取下一个实例;否则调用LoadBalancedRetryContext.setExhaustedOnly()方法将RetryContextSupport中的terminate设置成true,retryTemplate退出doExecute的while循环,整个重试结束),配置信息如下
spring: application: name: zuul-gateway server: port: 8989 eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ instance: leaseRenewalIntervalInSeconds: 10 metadataMap: instanceId: ${vcap.application.instance_id:${spring.application.name}:${spring.application.instance_id:${server.port}}} cluster: MAIN zuul: routes: mcsas_bd: path: /UserServer/** serviceId: UserServer retryable: true management: security: enabled: false ribbon: MaxAutoRetriesNextServer: 1 MaxAutoRetries: 1 ReadTimeout: 3000 OkToRetryOnAllOperations: true #false to only allow get to retry hystrix: command: mcsas_bd: execution: isolation: strategy: THREAD thread: timeoutInMilliseconds: 13000
5.配置信息对应的类ZuulProperties,例如修改zuulServlet对应的path,可以在application.yml 文件中设置 zuul.servletPath=/