Prefacio
Zuul es un componente de código abierto proporcionado por Netflix, comprometido a proporcionar servicios dinámicos de enrutamiento, monitoreo, resistencia, seguridad y otros servicios en la plataforma en la nube. También hay muchas compañías que lo usan como una parte importante de la puerta de enlace. Este año, el grupo de arquitectura de la compañía decidió desarrollar un producto de puerta de enlace, integrar el enrutamiento dinámico, los permisos dinámicos, la cuota de límite actual y otras funciones, proporcionando una gestión de llamadas de red externa unificada para proyectos en otros departamentos, y finalmente formando un producto (Ali realmente tiene productos de puerta de enlace maduros en este sentido, pero no es adecuado para configuraciones personalizadas y no tiene integradas permisos y limitaciones actuales).
Este artículo presenta principalmente el contenido relevante sobre el manejo de excepciones de Spring Cloud Zuul Unified y el retroceso. Se comparte para su referencia y aprendizaje. No diré mucho a continuación, echemos un vistazo a la introducción detallada juntos.
1. Manejo de excepciones unificadas en el filtro
De hecho, en la versión edgware SR2 de SpringCloud, hay un manejo unificado de errores en Zuulfilter, pero en el desarrollo real, creo que cada equipo tiene sus propias especificaciones de procesamiento para los métodos de respuesta de errores. Entonces, ¿cómo hacer manejo de excepciones personalizadas?
Primero podemos referirnos al SendErrorFilter proporcionado por SpringCloud:
/ * * Copyright 2013-2015 El autor o autores originales. * * Licenciado bajo la licencia Apache, versión 2.0 (la "licencia"); * No puede usar este archivo, excepto de conformidad con la licencia. * Puede obtener una copia de la licencia en * * http://www.apache.org/licenses/license-2.0 * * A menos que sea requerido por la ley aplicable o acordado por escrito, el software * distribuido bajo la licencia se distribuye sobre una base "como es", * sin garantías o condiciones de ningún tipo, ya sea expresa o implícita. * Consulte la licencia para los permisos de gobierno específicos que rigen el idioma y * limitaciones bajo la licencia. */paquete org.springframework.cloud.netflix.zuul.filters.post; import javax.servlet.requestdispatcher; import javax.servlet.http.httpservletRequest; import javax.servlet.http.httpServletResponse; importador org.apache.commons.logging.logFactory; import org.springframework.beans.factory.annotation.value; import org.springframework.cloud.netflix.zuul.util.zuulruntimeexception; import og.springframework.util.reflections; import org.springframework.util.stringutils; import com.netflix.zuul.zuulfilter; import com.netflix.zuul.context.requestContex 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} /error (por defecto) si {@link requestContext#getThrowable ()} no es nulo. * * @author Spencer Gibb * /// TODO: Mover al paquete de error en la clase EdgWarepublic SendErRorFilter extiende Zuulfilter {private estático estático Log LOG = logFactory.getLog (sendErRorFilter.class); Cadena final estática protegida send_error_filter_ran = "senderrorFilter.ran"; @Value ("$ {error.path:/error}") privado cadena errorpath; @Override public String filtType () {return Error_type; } @Override public int filterOrder () {return send_error_filter_order; } @Override public boolean deberíafilter () {requestContext ctx = requestContext.getCurrentContext (); // Solo reenvía a ErrorPath si no se ha reenviado para que ya devuelva ctx.getThrowable ()! = null &&! ctx.getBoolean (send_error_filter_ran, falso); } @Override public object run () {try {requestContext ctx = requestContext.getCurrentContext (); Excepción de zuulexception = findzuulException (ctx.getThrowable ()); HttpservletRequest request = ctx.getRequest (); request.setAttribute ("javax.servlet.error.status_code", excepción.nstatuscode); log.warn ("error durante el filtrado", excepción); request.setAttribute ("javax.servlet.error.exception", excepción); if (stringUtils.hastext (excepción.errorCause)) {request.setattribute ("javax.servlet.error.message", excepcion.errorCause); } Requestdispatcher despachador = request.getRequestDispatcher (this.errorPath); if (despachador! = null) {ctx.set (send_error_filter_ran, true); if (! ctx.getResponse (). ISCommited ()) {ctx.setResponseStatUscode (excepcion.nstatuscode); despachador.forward (solicitud, ctx.getResponse ()); }}} Catch (Exception Ex) {ReflectionUtils.RethrowrUntimeException (ex); } return null; } ZuulException findzuulException (showable showable) {if (shrowable.getCause () instancia de zuulruntimeException) {// Esta fue una falla iniciada por uno de los filtros locales de retorno (zuulexception) throwable.getCause (). GetCause (); } if (showleable.getCause () instancia de ZuulException) {// envuelto Zuul Exception Return (ZuulException) Showable.GetCause (); } if (instancia lanzable de ZuulException) {// Excepción lanzada por Zuul Lifecycle Return (ZuulException) Throwable; } // Fallback, nunca debe llegar aquí a devolver la nueva ZuulException (lanzable, httpservletResponse.sc_internal_server_error, null); } public void setErrorPath (String ErrorPath) {this.errorPath = ErrorPath; }}Aquí podemos encontrar varios puntos clave:
1) En el código anterior, podemos encontrar que el filtro ha puesto la información de error relevante en la solicitud:
request.setAttribute ("javax.servlet.error.status_code", excepción.nstatuscode);
request.setAttribute ("javax.servlet.error.exception", excepción);
request.setAttribute ("javax.servlet.error.message", excepción.errorCause);
2) Después de procesar el error, se reenviará a la dirección XXX/ERROR para procesar
Entonces podemos hacer un experimento. Creamos un filtro que arroja excepciones en el módulo de proyecto de servicio de puerta de enlace:
paquete com.hzgj.lyrk.springcloud.gateway.server.filter; import com.netflix.zuul.zuulfilter; import lombok.extern.slf4j.slf4j; import org.springframework.stereotype.component;@componente@slf4jppublic de myzuulfiLf public String filtreType () {return "post"; } @Override public int filterOrder () {return 9; } @Override public boolean deberíafilter () {return true; } @Override Public Object run () {log.info ("Ejecutar prueba de error ..."); tirar nueva runtimeException (); // devolver nulo; }}Luego definimos un controlador para manejar errores:
paquete com.hzgj.lyrk.springcloud.gateway.server.filter; import org.springframework.http.httpstatus; importar org.springframework.http.ResponseSentity; import og.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 (mensaje); ErrorBean.SetRason ("Error del programa"); devuelve nueva respuesta de respuesta <> (ErrorBean, httpstatus.bad_gateway); } Clase estática privada ErrorBean {Mensaje de cadena privada; razón de cadena privada; public String getMessage () {Mensaje de retorno; } public void setMessage (mensaje de cadena) {this.message = mensaje; } public String getRason () {Return Razon; } public void setRason (string razon) {this.Rason = razon; }}}Después de comenzar el proyecto, intentemos acceder a él a través de la puerta de enlace:
2. Preguntas sobre Zuul Fallback
1. Con respecto al problema de tiempo de espera de Zuul:
Hay muchas soluciones a este problema en línea, pero también quiero publicar el código fuente. Preste atención a esta clase AbstractribbonCommand, que integra Hystrix y Cink en esta clase.
/ * * Copyright 2013-2016 El autor o autores originales. * * Licenciado bajo la licencia Apache, versión 2.0 (la "licencia"); * No puede usar este archivo, excepto de conformidad con la licencia. * Puede obtener una copia de la licencia en * * http://www.apache.org/licenses/license-2.0 * * A menos que sea requerido por la ley aplicable o acordado por escrito, el software * distribuido bajo la licencia se distribuye sobre una base "como es", * sin garantías o condiciones de ningún tipo, ya sea expresa o implícita. * Consulte la licencia para los permisos de gobierno específicos que rigen el idioma y * limitaciones bajo la licencia. * */paquete org.springframework.cloud.netflix.zuul.filters.route.support; import org.apache.commons.logging.log; importar.apache.commons.logging.logFactory; import org.springframework.cloud.netflix.ribbon.ribbonhtttpresponse; import org.springframework.cloud.netflix.ribbon.support.abstractOloadBalancingClient; import og.springwork.cloud.netflix.ribbon.support.contextwarerequest; import org. org.springframework.cloud.netflix.zuul.filters.route.zuulfallbackProvider; import org.springframework.cloud.netflix.zuul.filters.route.fallbackProvider; import og.springfrfframework.http.client.clienthttponse; importación; com.netflix.client.AbstractloadBalancerawareclient; import com.netflix.client.clientRequest; import com.netflix.client.config.defaultclientconfigiMpl; import com.netflix.client.config.iclientcig; import.netflix.client.config.iclient; com.netflix.client.http.httpponse; import com.netflix.config.dynamicintProperty; import com.netflix.config.dynamicpropertyfactory; import com.netflix.hystrix.hystrixCommand; import com.netflix.hystrix.hystrixcommandgroup Key; import com.netflix.hystrix.hystrixCommandKey; import com.netflix.hystrix.hystrixCommandProperties; import com.netflix.zuul.constants.zuulconstants; import com.netflix.zuul.context.requestContext;/** * @author spencer gibb */public abstract class abstractribbonCommand <lbc extiende abstractBalancerawareclient <rq, rs>, rq clientRequest, rs extiense extiende extiende extiens HystrixCommand <ClientHTTPResponse> implementa RibbonCommand {private estático final log logger = logFactory.getLog (abstribtribboncommand.class); Cliente LBC final protegido; Contexto protegido de RibbonCommandContext; Protegido ZuulfallbackProvider ZuulfallbackProvider; configuración de iclientconfig protegida; Public AbstribtribbonCommand (Cliente LBC, RibbonCommandContext Context, ZuulProperties ZuulProperties) {this ("predeterminado", cliente, contexto, zuulproperties); } public abstribtribbonCommand (String CommandKey, LBC Client, RibbonCommandContext Context, ZuulProperties ZuulProperties) {this (CommandKey, Cliente, contexto, ZuulProperties, NULL); } public abstribtribbonCommand (String CommandKey, LBC Client, RibbonCommandContext Context, ZuulProperties ZuulProperties, ZuulfallbackProvider FallbackProvider) {this (CommandKey, Client, context, ZuulProperties, FallbackProvider, Null); } public abstribtribbonCommand (String CommandKey, LBC Client, RibbonCommandContext Context, ZuulProperties ZuUlProperties, ZuulfallBackProvider FallbackProvider, iClientConfig Config) {this (GetSetter (CommandKey, ZuulProperties, config), context, contexto, nenfabioso, configuración); } Protected AbstracTribBonCommand (setter setter, LBC Client, RibbonCommandContext Context, ZuulfallBackProvider FallbackProvider, IClientConfig config) {super (setter); this.client = cliente; this.context = context; this.zuulfallbackProvider = fallbackProvider; this.config = config; } protegido static hytrixCommandProperties.setter CreateSeTter (iClientConfig Config, String CommandKey, ZuulProperties ZuUlProperties) {int hytrixtimeOut = gethystrImiMeOut (config, commandey); return hystrixCommandProperties.setter (). WithExecutionSolationStrategy (ZuulProperties.getRibbonisolationStrategy ()). WithExecutionTimeOutInMilliseConds (HystrIrmytimeOut); } protegido static int gethystrImiMeOut (iclientConfig config, string commandey) {int ribBonTimeOut = getRibBonTimeOut (config, commandey); DynamicPropertyFactory DynamicPropertyFactory = DynamicPropertyFactory.getInStance (); int defaulThystrItimeOut = DynamicPropertyFactory.getIntProperty ("Hystrix.command.default.execution.isolation.thread.timeoutinmilliseConds", 0) .get (); int commandHystrInstimeOut = DynamicPropertyFactory.getIntProperty ("Hystrix.command." + CommandKey + ".Execution.isolation.thread.TimeOutInMillisEconds", 0) .get (); int hystrixtimeut; if (commandHystrImiMeOut> 0) {hystrixiMeOut = commandHystrImiMeOut; } else if (defaulthystrImeout> 0) {hytrixtimeOut = defaulThystrInstimeOut; } else {hystrixiMeOut = ribBonTimeOut; } if (hystrixtimeout <ribBonTimeOut) {logger.warn ("El tiempo de espera de Hystrix de" + HystrixtimeOut + "MS para el comando" + CommandKey + "se establece más bajo que la combinación del tiempo de espera de lectura y conexión de cinta," + RibBonTimeOut + "MS"); } return hystrImmytimeOut; } protegido static int getRibBonTimeOut (iclientConfig config, string commandey) {int ribBonTimeOut; if (config == null) {ribBonTimeOut = ribbonClientConfiguration.default_read_timeout + ribbonClientConfiguration.default_connect_timeout; } else {int ribbonreadtimeout = getTimeOut (config, commandey, "ReadTimeOut", iclientConfigKey.Keys.ReadTimeout, RibbonClientConfiguration.default_read_timeout); int ribbonConnecttimeOut = getTerout (config, commandey, "ConnectTimeOut", iClientConfigKey.Keys.ConnectTimeOut, ribbonClientConfiguration.default_connect_timeout); int maxautorEtries = getTimeOut (config, commandkey, "maxaUtorEtries", iClientConfigKey.Keys.maxaUtReTries, defaultClientConfigImpl.default_max_auto_retries); int maxaUtRiesNextServer = getTerout (config, commandey, "maxaUtRiesNextServer", iclientConfigKey.Keys.maxaUtoretriesNextServer, defaultClientConfigImpl.default_max_auto_retres_next_server); ribBonTimeOut = (RibbonReadTimeOut + RibbonConnectTimeOut) * (maxautoretries + 1) * (maxaUtRiesNextServer + 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 () + "." + propiedad, config.get (configKey, defaultValue)). get (); } @Deprecated // toDo eliminado en 2.0.x setter estático protegido getSetter (final de string command skey, zuulproperties zuulproperties) {return getSetter (comandingkey, zuulproperties, null); } setter static protegido getSetter (final de cadena final CommandKey, ZuulProperties ZuulProperties, iclientConfig config) {// @formatter: Off setter commandsetter = setter.WithGroupKey (hytrixComandGroupKey.factory.askey ("ribbonbonmand") .andCommandKey (HystrixCMand.factory) final hystrixCommandProperties.setter setter = CreateSeTter (config, commandey, zuulProperties); if (zuulproperties.getRibbonisolationStrategy () == ExecutionSolationStrategy.semaphore) {Final String Name = Zuulconstants.zuul_eureka + CommandKey + ".semaphore.maxsemaphores"; // Queremos predeterminados a Semaphore-Isolation ya que esto envuelve // 2 Otros comandos que ya están aislados DynamicintProperty valor aislados de subprocesos = DynamicPropertyFactory.getInstance () .GetIntProperty (nombre, ZuulProperties.getSemaphore (). setter.withExecutionSolationEmaphoremaxConcurrentRequests (value.get ()); } else if (zuulProperties.getThreadPool (). ISUSESESEPARATETHREADPOOLS ()) {String final ThreadPoolkey = ZuUlProperties.getThreadPool (). CommandSetter.AndThreadPoolkey (hystrixThreadPoolkey.factory.askey (ThreadPoolkey)); } return commandsetter.andCommandPropertiesDefaults (setter); // @Formatter: ON} @Override Proteged ClientHTTPResponse run () lanza la excepción {final requestContext context = requestContext.getCurrentContext (); Request RQ = CreateRequest (); Respuesta RS; boolean retryableClient = this.client if (retryableClient) {respuesta = this.client.execute (solicitud, config); } else {respuesta = this.client.executewithloadBalancer (solicitud, config); } context.set ("RibbonResponse", respuesta); // Cierre explícitamente el httpResponse si el comando hytrix cronometró a // liberar la conexión HTTP subyacente mantenida por la respuesta. // if (this.isResponSetimEdout ()) {if (Respuesta! = NULL) {Response.Close (); }} return new RibbonHttPResponse (respuesta); } @Override ClientHTTPResponse GetFallBack () {if (ZuulfallBackProvider! = NULL) {return GetFallBackResponse (); } return super.getfallback (); } Proteged ClientHTTPResponse GetFallBackResponse () {if (ZuulfallbackProvider instancia de FallbackProvider) {showeable Cause = getFailedExecutionException (); causa = causa == nulo? getExecutionException (): Causa; if (causa == null) {zuulfallbackprovider.fallbackResponse (); } else {return ((fallbackProvider) ZuulfallbackProvider) .FallBackResponse (causa); }} return zuulfallbackProvider.fallbackResponse (); } public lbc getClient () {Return Client; } public RibbonCommandContext getContext () {return context; } Resumen protegido RQ CreateRequest () arroja una excepción;}Tenga en cuenta: el método GetRibBonTimeOut y el método de GethystrItimeOut, donde el valor de estos dos métodos CommandKey es el nombre de la ruta. Por ejemplo, si visitamos: http: // localhost: 8088/orden-server/xxx para acceder al servicio de servidor de pedidos, entonces CommandKey es servidor de pedidos
Según el código fuente, primero establecemos los parámetros de tiempo de espera del servidor de puerta de enlace:
#Configuración de la cinta de global Cinificación: ConnecttimeOut: 3000 de lectura: 3000hystrix: Comando: predeterminado: Ejecución: aislamiento: hilo: tiempo de tiempo de tiempo
Por supuesto, también puede establecer los parámetros de tiempo de espera de la cinta para el servidor de pedidos por separado: orden-server.ribbon.xxxx = xxx. Para demostrar el efecto respaldo en Zuul, establecí el tiempo de espera de Hystrix un poco más corto aquí. Por supuesto, es mejor no establecer el tiempo de espera predeterminado de Hystrix para que sea más corto que el tiempo de espera de la cinta. Esta situación ha sido advertida de nosotros en el código fuente.
Luego agregamos el siguiente método en Order-Server:
@Getmapping ("/sleep/{sleeptime}") public string sleep (@pathvariable long sleeptime) lanza interruptedexception {timeunit.seconds.sleep (sleeptime); devolver "éxito"; }2. Método de retroceso de Zuul
Podemos implementar la interfaz ZuulfallbackProvider e implementar el código:
paquete com.hzgj.lyrk.springcloud.gateway.server.filter; import com.google.common.collect.immutableMap; import com.google.gson.gsonbuilder; importar org.springframework.cloud.netflix.zuul.filters.route org. java.io.bytearrayInputStream; import java.io.ioException; import java.io.inputStream; import java.time.localdateTime; import java.time.localtime; @ComponentPublic Class FallbackHandler implementa ZuulbalbackPhallpalbackProvider {@Override Cadena pública GetRoute () "*"; } @Override public ClientHttPSponse FallbackResponse () {return New ClientHttPResponse () {@Override public HttpStatus getStatUscode () lanza IOException {return httpstatus.ok; } @Override public int getRawStatUscode () lanza ioexception {return 200; } @Override public String getStatusText () lanza ioexception {return "ok"; } @Override public void Close () {} @Override public inputStream getBody () lanza IOException {String result = new GsonBuilder (). Create (). TJson (ImmutableMap.of ("ErrorCode", 500, "Contenido", "Solicitar fallido", "Tiempo", localDateTime.Now ())); devolver nuevo bytearrayInputStream (result.getBytes ()); } @Override public httpheaders getHeaders () {httpheaders encabezados = new httpheaders (); Headers.setContentType (Mediatype.application_json); encabezados de devolución; }}; }}En este momento, visitamos: http: // localhost: 8088/orden-server/dormir/6 y obtener los siguientes resultados:
Cuando visitamos: http: // localhost: 8088/orden-server/sleep/1, obtenemos el siguiente resultado:
Resumir
Lo anterior es todo el contenido de este artículo. Espero que el contenido de este artículo tenga cierto valor de referencia para el estudio o el trabajo de todos. Si tiene alguna pregunta, puede dejar un mensaje para comunicarse. Gracias por su apoyo a Wulin.com.