SpringCloud学习之Zuul统一异常处理及回退

小编 2026-06-04 阅读:299 评论:0
一、Filter中统一异常处理  其实在SpringCloud的Edgware SR2版本中对于...

一、Filter中统一异常处理

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

我们可以先参考一下SpringCloud提供的SendErrorFilter:

SpringCloud学习之Zuul统一异常处理及回退SpringCloud学习之Zuul统一异常处理及回退
/* * 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 Edgwarepublic 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;    }}
View Code

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

  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:

SpringCloud学习之Zuul统一异常处理及回退SpringCloud学习之Zuul统一异常处理及回退
package com.hzgj.lyrk.springcloud.gateway.server.filter;import com.netflix.zuul.ZuulFilter;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Component;@Component@Slf4jpublic 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;    }}
View Code

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

SpringCloud学习之Zuul统一异常处理及回退SpringCloud学习之Zuul统一异常处理及回退
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;@RestControllerpublic 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;        }    }}
View Code

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

SpringCloud学习之Zuul统一异常处理及回退

 

二、关于zuul回退的问题

1、关于zuul的超时问题:

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

SpringCloud学习之Zuul统一异常处理及回退SpringCloud学习之Zuul统一异常处理及回退
/* * 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;}
View Code

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

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

SpringCloud学习之Zuul统一异常处理及回退SpringCloud学习之Zuul统一异常处理及回退
#全局的ribbon设置ribbon:  ConnectTimeout: 3000  ReadTimeout: 3000hystrix:  command:    default:      execution:        isolation:          thread:            timeoutInMilliseconds: 3000zuul:  host:    connectTimeoutMillis: 10000
View Code

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

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

    @GetMapping("/sleep/{sleepTime}")    public String sleep(@PathVariable Long sleepTime) throws InterruptedException {        TimeUnit.SECONDS.sleep(sleepTime);        return "SUCCESS";    }

  

2、zuul的回退方法

我们可以实现ZuulFallbackProvider接口,实现代码:

SpringCloud学习之Zuul统一异常处理及回退SpringCloud学习之Zuul统一异常处理及回退
package com.hzgj.lyrk.springcloud.gateway.server.filter;import com.google.common.collect.ImmutableMap;import com.google.gson.GsonBuilder;import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider;import org.springframework.http.HttpHeaders;import org.springframework.http.HttpStatus;import org.springframework.http.MediaType;import org.springframework.http.client.ClientHttpResponse;import org.springframework.stereotype.Component;import java.io.ByteArrayInputStream;import java.io.IOException;import java.io.InputStream;import java.time.LocalDateTime;import java.time.LocalTime;@Componentpublic class FallBackHandler implements ZuulFallbackProvider {    @Override    public String getRoute() {        //代表所有的路由都适配该设置        return "*";    }    @Override    public ClientHttpResponse fallbackResponse() {        return new ClientHttpResponse() {            @Override            public HttpStatus getStatusCode() throws IOException {                return HttpStatus.OK;            }            @Override            public int getRawStatusCode() throws IOException {                return 200;            }            @Override            public String getStatusText() throws IOException {                return "OK";            }            @Override            public void close() {            }            @Override            public InputStream getBody() throws IOException {                String result = new GsonBuilder().create().toJson(ImmutableMap.of("errorCode", 500, "content", "请求失败", "time", LocalDateTime.now()));                return new ByteArrayInputStream(result.getBytes());            }            @Override            public HttpHeaders getHeaders() {                HttpHeaders headers = new HttpHeaders();                headers.setContentType(MediaType.APPLICATION_JSON);                return headers;            }        };    }}
View Code

此时我们访问:http://localhost:8088/order-server/sleep/6 得到如下结果:

SpringCloud学习之Zuul统一异常处理及回退

 当我们访问:http://localhost:8088/order-server/sleep/1 就得到如下结果:

SpringCloud学习之Zuul统一异常处理及回退

 

版权声明

本文仅代表作者观点,不代表百度立场。
本文系作者授权百度百家发表,未经许可,不得转载。

热门文章
  • 机房智能化温湿度解决方式之POE供电以太网温湿度传感器

    机房智能化温湿度解决方式之POE供电以太网温湿度传感器
    机房智能化温湿度解决方式之POE供电以太网温湿度传感器 北京盈创力和电子科技有限公司 智能型TCP网口温湿度记录仪 北京IP网络温湿度记录仪厂家,北京盈创力和 北京智能型TCP网口温湿度记录仪IP网络温湿度记录仪是一种新型的基于TCP/IP协议双绞线以太网标准温湿度采集模块,利用它可以实现现场温度值、相对湿度值的采集,同时利用其自身的RJ45通信接口可以方便地和机房监控主机或交换机集线器进行联网。 工作于-40℃~85℃工业级带...
  • Sequential Monte Carlo Methods (SMC) 序列蒙特卡洛/粒子滤波/Bootstrap Filtering

    Sequential Monte Carlo Methods (SMC) 序列蒙特卡洛/粒子滤波/Bootstrap Filtering
    Problem Statement 我们考虑一个具有马尔可夫性质、非线性、非高斯的状态空间模型(State Space Model):对于一个时间序列上的观测结果{yt,t∈N}\\{ y_t , t \\in N \\}{yt​,t∈N},我们认为每个观测结果yty_tyt​的生成依赖于一个无法直接观察的隐变量xt∈{xt,t∈N}x_t \\in \\{x_t , t \\in N \\}xt​∈{xt​,t∈N},即:p(...
  • HTTP状态保持的原理

    HTTP状态保持的原理
    a)在用户登录之后,浏览器返回响应的时候会在响应中添加上cookieb)浏览器接收到cookie之后会自动保存c)当用户再次请求同一服务器中的其他网页的时候,浏览器会自动带上之前保存的cookied)服务接收到请求之后可以请 request 对象中取到cookie 判断当前用户是否登录  Http是无状态的,就是连接时数据互通,关闭后...
  • Hive 系统函数及示例

    Hive 系统函数及示例
    查看所有系统函数 show functions; 函数分类 内置函数【系统函数】 数学函数: floor、round、ceil、cos、log2等 字符串函数: length、reverse、trim、lower、get_json_object、repeat等 收集函数: size 转换函数: cast 日期函数: year、month、datediff、date、date_add等 条件函数: coalesce、case…w...
  • CSRF的原理和防范措施

    CSRF的原理和防范措施
    a)攻击原理:i.用户C访问正常网站A时进行登录,浏览器保存A的cookieii.用户C再访问攻击网站B,网站B上有某个隐藏的链接或者图片标签会自动请求网站A的URL地址,例如表单提交,传指定的参数iii.而攻击网站B在访问网站A的时候,浏览器会自动带上网站A的cookieiv.所以网站A在接收到请求之后可判断当前用户是登录状态,所以...
标签列表