Spring Cloud学习教程之Zuul统一异常处理与回退

时间:2021-05-02

前言

zuul 是netflix 提供的一个开源组件,致力于在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。也有很多公司使用它来作为网关的重要组成部分,碰巧今年公司的架构组决定自研一个网关产品,集动态路由,动态权限,限流配额等功能为一体,为其他部门的项目提供统一的外网调用管理,最终形成产品(这方面阿里其实已经有成熟的网关产品了,但是不太适用于个性化的配置,也没有集成权限和限流降级)。

本文主要给大家介绍了关于spring cloud zuul统一异常处理与回退的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧。

一、filter中统一异常处理

  其实在springcloud的edgware sr2版本中对于zuulfilter中的错误有统一的处理,但是在实际开发当中对于错误的响应方式,我想每个团队都有自己的处理规范。那么如何做到自定义的异常处理呢?

我们可以先参考一下springcloud提供的senderrorfilter:

? 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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 /* * copyright 2013-2015 the original author or authors. * * licensed under the apache license, version 2.0 (the "license"); * you may not use this file except in compliance with the license. * you may obtain a copy of the license at * * http://www.apache.org/licenses/license-2.0 * * unless required by applicable law or agreed to in writing, software * distributed under the license is distributed on an "as is" basis, * without warranties or conditions of any kind, either express or implied. * see the license for the specific language governing permissions and * limitations under the license. */ package org.springframework.cloud.netflix.zuul.filters.post; import javax.servlet.requestdispatcher; import javax.servlet.http.httpservletrequest; import javax.servlet.http.httpservletresponse; import org.apache.commons.logging.log; import org.apache.commons.logging.logfactory; import org.springframework.beans.factory.annotation.value; import org.springframework.cloud.netflix.zuul.util.zuulruntimeexception; import org.springframework.util.reflectionutils; import org.springframework.util.stringutils; import com.netflix.zuul.zuulfilter; import com.netflix.zuul.context.requestcontext; import com.netflix.zuul.exception.zuulexception; import static org.springframework.cloud.netflix.zuul.filters.support.filterconstants.error_type; import static org.springframework.cloud.netflix.zuul.filters.support.filterconstants.send_error_filter_order; /** * error {@link zuulfilter} that forwards to /error (by default) if {@link requestcontext#getthrowable()} is not null. * * @author spencer gibb */ //todo: move to error package in edgware public class senderrorfilter extends zuulfilter { private static final log log = logfactory.getlog(senderrorfilter.class); protected static final string send_error_filter_ran = "senderrorfilter.ran"; @value("${error.path:/error}") private string errorpath; @override public string filtertype() { return error_type; } @override public int filterorder() { return send_error_filter_order; } @override public boolean shouldfilter() { requestcontext ctx = requestcontext.getcurrentcontext(); // only forward to errorpath if it hasn't been forwarded to already return ctx.getthrowable() != null && !ctx.getboolean(send_error_filter_ran, false); } @override public object run() { try { requestcontext ctx = requestcontext.getcurrentcontext(); zuulexception exception = findzuulexception(ctx.getthrowable()); httpservletrequest request = ctx.getrequest(); request.setattribute("javax.servlet.error.status_code", exception.nstatuscode); log.warn("error during filtering", exception); request.setattribute("javax.servlet.error.exception", exception); if (stringutils.hastext(exception.errorcause)) { request.setattribute("javax.servlet.error.message", exception.errorcause); } requestdispatcher dispatcher = request.getrequestdispatcher( this.errorpath); if (dispatcher != null) { ctx.set(send_error_filter_ran, true); if (!ctx.getresponse().iscommitted()) { ctx.setresponsestatuscode(exception.nstatuscode); dispatcher.forward(request, ctx.getresponse()); } } } catch (exception ex) { reflectionutils.rethrowruntimeexception(ex); } return null; } zuulexception findzuulexception(throwable throwable) { if (throwable.getcause() instanceof zuulruntimeexception) { // this was a failure initiated by one of the local filters return (zuulexception) throwable.getcause().getcause(); } if (throwable.getcause() instanceof zuulexception) { // wrapped zuul exception return (zuulexception) throwable.getcause(); } if (throwable instanceof zuulexception) { // exception thrown by zuul lifecycle return (zuulexception) throwable; } // fallback, should never get here return new zuulexception(throwable, httpservletresponse.sc_internal_server_error, null); } public void seterrorpath(string errorpath) { this.errorpath = errorpath; } }

在这里我们可以找到几个关键点:

  1)在上述代码中,我们可以发现filter已经将相关的错误信息放到request当中了:

    request.setattribute("javax.servlet.error.status_code", exception.nstatuscode);

    request.setattribute("javax.servlet.error.exception", exception);

    request.setattribute("javax.servlet.error.message", exception.errorcause);

  2)错误处理完毕后,会转发到 xxx/error的地址来处理

  那么我们可以来做个试验,我们在gateway-service项目模块里,创建一个会抛出异常的filter:

? 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 package com.hzgj.lyrk.springcloud.gateway.server.filter; import com.netflix.zuul.zuulfilter; import lombok.extern.slf4j.slf4j; import org.springframework.stereotype.component; @component @slf4j public class myzuulfilter extends zuulfilter { @override public string filtertype() { return "post"; } @override public int filterorder() { return 9; } @override public boolean shouldfilter() { return true; } @override public object run() { log.info("run error test ..."); throw new runtimeexception(); // return null; } }

  紧接着我们定义一个控制器,来做错误处理:

? 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 package com.hzgj.lyrk.springcloud.gateway.server.filter; import org.springframework.http.httpstatus; import org.springframework.http.responseentity; import org.springframework.web.bind.annotation.getmapping; import org.springframework.web.bind.annotation.restcontroller; import javax.servlet.http.httpservletrequest; @restcontroller public class errorhandler { @getmapping(value = "/error") public responseentity<errorbean> error(httpservletrequest request) { string message = request.getattribute("javax.servlet.error.message").tostring(); errorbean errorbean = new errorbean(); errorbean.setmessage(message); errorbean.setreason("程序出错"); return new responseentity<>(errorbean, httpstatus.bad_gateway); } private static class errorbean { private string message; private string reason; public string getmessage() { return message; } public void setmessage(string message) { this.message = message; } public string getreason() { return reason; } public void setreason(string reason) { this.reason = reason; } } }

  启动项目后,我们通过网关访问一下试试:

二、关于zuul回退的问题

1、关于zuul的超时问题:

  这个问题网上有很多解决方案,但是我还要贴一下源代码,请关注这个类 abstractribboncommand,在这个类里集成了hystrix与ribbon。

? 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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 /* * copyright 2013-2016 the original author or authors. * * licensed under the apache license, version 2.0 (the "license"); * you may not use this file except in compliance with the license. * you may obtain a copy of the license at * * http://www.apache.org/licenses/license-2.0 * * unless required by applicable law or agreed to in writing, software * distributed under the license is distributed on an "as is" basis, * without warranties or conditions of any kind, either express or implied. * see the license for the specific language governing permissions and * limitations under the license. * */ package org.springframework.cloud.netflix.zuul.filters.route.support; import org.apache.commons.logging.log; import org.apache.commons.logging.logfactory; import org.springframework.cloud.netflix.ribbon.ribbonclientconfiguration; import org.springframework.cloud.netflix.ribbon.ribbonhttpresponse; import org.springframework.cloud.netflix.ribbon.support.abstractloadbalancingclient; import org.springframework.cloud.netflix.ribbon.support.contextawarerequest; import org.springframework.cloud.netflix.zuul.filters.zuulproperties; import org.springframework.cloud.netflix.zuul.filters.route.ribboncommand; import org.springframework.cloud.netflix.zuul.filters.route.ribboncommandcontext; import org.springframework.cloud.netflix.zuul.filters.route.zuulfallbackprovider; import org.springframework.cloud.netflix.zuul.filters.route.fallbackprovider; import org.springframework.http.client.clienthttpresponse; import com.netflix.client.abstractloadbalancerawareclient; import com.netflix.client.clientrequest; import com.netflix.client.config.defaultclientconfigimpl; import com.netflix.client.config.iclientconfig; import com.netflix.client.config.iclientconfigkey; import com.netflix.client.http.httpresponse; import com.netflix.config.dynamicintproperty; import com.netflix.config.dynamicpropertyfactory; import com.netflix.hystrix.hystrixcommand; import com.netflix.hystrix.hystrixcommandgroupkey; import com.netflix.hystrix.hystrixcommandkey; import com.netflix.hystrix.hystrixcommandproperties; import com.netflix.hystrix.hystrixcommandproperties.executionisolationstrategy; import com.netflix.hystrix.hystrixthreadpoolkey; import com.netflix.zuul.constants.zuulconstants; import com.netflix.zuul.context.requestcontext; /** * @author spencer gibb */ public abstract class abstractribboncommand<lbc extends abstractloadbalancerawareclient<rq, rs>, rq extends clientrequest, rs extends httpresponse> extends hystrixcommand<clienthttpresponse> implements ribboncommand { private static final log logger = logfactory.getlog(abstractribboncommand.class); protected final lbc client; protected ribboncommandcontext context; protected zuulfallbackprovider zuulfallbackprovider; protected iclientconfig config; public abstractribboncommand(lbc client, ribboncommandcontext context, zuulproperties zuulproperties) { this("default", client, context, zuulproperties); } public abstractribboncommand(string commandkey, lbc client, ribboncommandcontext context, zuulproperties zuulproperties) { this(commandkey, client, context, zuulproperties, null); } public abstractribboncommand(string commandkey, lbc client, ribboncommandcontext context, zuulproperties zuulproperties, zuulfallbackprovider fallbackprovider) { this(commandkey, client, context, zuulproperties, fallbackprovider, null); } public abstractribboncommand(string commandkey, lbc client, ribboncommandcontext context, zuulproperties zuulproperties, zuulfallbackprovider fallbackprovider, iclientconfig config) { this(getsetter(commandkey, zuulproperties, config), client, context, fallbackprovider, config); } protected abstractribboncommand(setter setter, lbc client, ribboncommandcontext context, zuulfallbackprovider fallbackprovider, iclientconfig config) { super(setter); this.client = client; this.context = context; this.zuulfallbackprovider = fallbackprovider; this.config = config; } protected static hystrixcommandproperties.setter createsetter(iclientconfig config, string commandkey, zuulproperties zuulproperties) { int hystrixtimeout = gethystrixtimeout(config, commandkey); return hystrixcommandproperties.setter().withexecutionisolationstrategy( zuulproperties.getribbonisolationstrategy()).withexecutiontimeoutinmilliseconds(hystrixtimeout); } protected static int gethystrixtimeout(iclientconfig config, string commandkey) { int ribbontimeout = getribbontimeout(config, commandkey); dynamicpropertyfactory dynamicpropertyfactory = dynamicpropertyfactory.getinstance(); int defaulthystrixtimeout = dynamicpropertyfactory.getintproperty("hystrix.command.default.execution.isolation.thread.timeoutinmilliseconds", 0).get(); int commandhystrixtimeout = dynamicpropertyfactory.getintproperty("hystrix.command." + commandkey + ".execution.isolation.thread.timeoutinmilliseconds", 0).get(); int hystrixtimeout; if(commandhystrixtimeout > 0) { hystrixtimeout = commandhystrixtimeout; } else if(defaulthystrixtimeout > 0) { hystrixtimeout = defaulthystrixtimeout; } else { hystrixtimeout = ribbontimeout; } if(hystrixtimeout < ribbontimeout) { logger.warn("the hystrix timeout of " + hystrixtimeout + "ms for the command " + commandkey + " is set lower than the combination of the ribbon read and connect timeout, " + ribbontimeout + "ms."); } return hystrixtimeout; } protected static int getribbontimeout(iclientconfig config, string commandkey) { int ribbontimeout; if (config == null) { ribbontimeout = ribbonclientconfiguration.default_read_timeout + ribbonclientconfiguration.default_connect_timeout; } else { int ribbonreadtimeout = gettimeout(config, commandkey, "readtimeout", iclientconfigkey.keys.readtimeout, ribbonclientconfiguration.default_read_timeout); int ribbonconnecttimeout = gettimeout(config, commandkey, "connecttimeout", iclientconfigkey.keys.connecttimeout, ribbonclientconfiguration.default_connect_timeout); int maxautoretries = gettimeout(config, commandkey, "maxautoretries", iclientconfigkey.keys.maxautoretries, defaultclientconfigimpl.default_max_auto_retries); int maxautoretriesnextserver = gettimeout(config, commandkey, "maxautoretriesnextserver", iclientconfigkey.keys.maxautoretriesnextserver, defaultclientconfigimpl.default_max_auto_retries_next_server); ribbontimeout = (ribbonreadtimeout + ribbonconnecttimeout) * (maxautoretries + 1) * (maxautoretriesnextserver + 1); } return ribbontimeout; } private static int gettimeout(iclientconfig config, string commandkey, string property, iclientconfigkey<integer> configkey, int defaultvalue) { dynamicpropertyfactory dynamicpropertyfactory = dynamicpropertyfactory.getinstance(); return dynamicpropertyfactory.getintproperty(commandkey + "." + config.getnamespace() + "." + property, config.get(configkey, defaultvalue)).get(); } @deprecated //todo remove in 2.0.x protected static setter getsetter(final string commandkey, zuulproperties zuulproperties) { return getsetter(commandkey, zuulproperties, null); } protected static setter getsetter(final string commandkey, zuulproperties zuulproperties, iclientconfig config) { // @formatter:off setter commandsetter = setter.withgroupkey(hystrixcommandgroupkey.factory.askey("ribboncommand")) .andcommandkey(hystrixcommandkey.factory.askey(commandkey)); final hystrixcommandproperties.setter setter = createsetter(config, commandkey, zuulproperties); if (zuulproperties.getribbonisolationstrategy() == executionisolationstrategy.semaphore){ final string name = zuulconstants.zuul_eureka + commandkey + ".semaphore.maxsemaphores"; // we want to default to semaphore-isolation since this wraps // 2 others commands that are already thread isolated final dynamicintproperty value = dynamicpropertyfactory.getinstance() .getintproperty(name, zuulproperties.getsemaphore().getmaxsemaphores()); setter.withexecutionisolationsemaphoremaxconcurrentrequests(value.get()); } else if (zuulproperties.getthreadpool().isuseseparatethreadpools()) { final string threadpoolkey = zuulproperties.getthreadpool().getthreadpoolkeyprefix() + commandkey; commandsetter.andthreadpoolkey(hystrixthreadpoolkey.factory.askey(threadpoolkey)); } return commandsetter.andcommandpropertiesdefaults(setter); // @formatter:on } @override protected clienthttpresponse run() throws exception { final requestcontext context = requestcontext.getcurrentcontext(); rq request = createrequest(); rs response; boolean retryableclient = this.client instanceof abstractloadbalancingclient && ((abstractloadbalancingclient)this.client).isclientretryable((contextawarerequest)request); if (retryableclient) { response = this.client.execute(request, config); } else { response = this.client.executewithloadbalancer(request, config); } context.set("ribbonresponse", response); // explicitly close the httpresponse if the hystrix command timed out to // release the underlying http connection held by the response. // if (this.isresponsetimedout()) { if (response != null) { response.close(); } } return new ribbonhttpresponse(response); } @override protected clienthttpresponse getfallback() { if(zuulfallbackprovider != null) { return getfallbackresponse(); } return super.getfallback(); } protected clienthttpresponse getfallbackresponse() { if (zuulfallbackprovider instanceof fallbackprovider) { throwable cause = getfailedexecutionexception(); cause = cause == null ? getexecutionexception() : cause; if (cause == null) { zuulfallbackprovider.fallbackresponse(); } else { return ((fallbackprovider) zuulfallbackprovider).fallbackresponse(cause); } } return zuulfallbackprovider.fallbackresponse(); } public lbc getclient() { return client; } public ribboncommandcontext getcontext() { return context; } protected abstract rq createrequest() throws exception; }

  请注意:getribbontimeout方法与gethystrixtimeout方法,其中这两个方法 commandkey的值为路由的名称,比如说我们访问:http://localhost:8088/order-server/xxx来访问order-server服务, 那么commandkey 就为order-server

  根据源代码,我们先设置gateway-server的超时参数:

? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #全局的ribbon设置 ribbon: connecttimeout: 3000 readtimeout: 3000 hystrix: command: default: execution: isolation: thread: timeoutinmilliseconds: 3000 zuul: host: connecttimeoutmillis: 10000

  当然也可以单独为order-server设置ribbon的超时参数:order-server.ribbon.xxxx=xxx , 为了演示zuul中的回退效果,我在这里把hystrix超时时间设置短一点。当然最好不要将hystrix默认的超时时间设置的比ribbon的超时时间短,源码里遇到此情况已经给与我们警告了。

  那么我们在order-server下添加如下方法:

? 1 2 3 4 5 @getmapping("/sleep/{sleeptime}") public string sleep(@pathvariable long sleeptime) throw

声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。

相关文章