Prefácio
O Zuul é um componente de código aberto fornecido pela Netflix, comprometido em fornecer roteamento dinâmico, monitoramento, resiliência, segurança e outros serviços de borda na plataforma em nuvem. Também existem muitas empresas que o usam como uma parte importante do gateway. Este ano, o grupo de arquitetura da empresa decidiu desenvolver um produto de gateway, integrando o roteamento dinâmico, permissões dinâmicas, cotas limitados atuais e outras funções, fornecendo gerenciamento de chamadas de rede externa unificada para projetos em outros departamentos e, finalmente, formando um produto (a Ali realmente não tem produtos de gateway maduros a esse respeito, mas não é adequado para configurações personalizadas, e não possui um integração de gateway maduro.
Este artigo apresenta principalmente o conteúdo relevante sobre o Spring Cloud Zuul Unified Exception Maniplel and Fallback. É compartilhado para sua referência e aprendizado. Não vou dizer muito abaixo, vamos dar uma olhada na introdução detalhada juntos.
1. Manipulação de exceção unificada no filtro
De fato, na versão Edgware SR2 do SpringCloud, há um manuseio unificado de erros no Zuulfilter, mas no desenvolvimento real, acho que cada equipe tem suas próprias especificações de processamento para os métodos de resposta de erros. Então, como fazer o manuseio de exceção personalizado?
Podemos primeiro nos referir ao SendErrorFilter fornecido pelo SpringCloud:
/ * * Copyright 2013-2015 O autor original ou autores. * * Licenciado sob a licença Apache, versão 2.0 (a "licença"); * Você não pode usar esse arquivo, exceto em conformidade com a licença. * Você pode obter uma cópia da licença em * * http://www.apache.org/license/license-2.0 * *, a menos que exigido pela lei aplicável ou acordada por escrito, o software * distribuído sob a licença seja distribuído em uma base "como é" *, sem garantia ou condição de qualquer tipo, seja expresso ou implícito. * Consulte a licença para as permissões e limitações do idioma específico e * sob a licença. */package org.springframework.cloud.netflix.zuul.filters.post; importar javax.servlet.requestdispatcher; importar javax.servlet.httpommervletServletRetRetLeGlest; importSonsport; org.apache.commons.logging.logfactory; importar org.springframework.beans.factory.annotation.value; importar org.springframework.cloud.netflix.zuul.util.zuulruntime; org.springframework.util.stringutils; importar com.netflix.zuul.zuulfilter; importar com.netflix.zuul.context.requestContext; importar com.netflix.zuul.exception.zuulException; importar estático; org.springframework.cloud.netflix.zuul.filters.support.filterconstants.error_type; importar org.springframework.cloud.netflix.zuul.filters.supPort.filterconstants.sendror_filter_terrror_ilter_terror_terror_terror_terror_terror_terrror_terrror_terrror_terrror_ilters; encaminhe para /error (por padrão) se {@link requestContext#getThrowable ()} não for nulo. * * @Author Spencer Gibb * /// TODO: Mudar para o pacote de erros na classe Edgwarepublic senderrorFilter estende o zuulfilter {private static final log = logFactory.getLog (senderrorFilter.class); String estática final protegida 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 devefilter () {requestContext ctx = requestcontext.getCurrentContext (); // apenas avance para Errorpath se não tiver sido encaminhado para retornar ctx.gethrowable ()! = null &&! ctx.getboolean (send_error_filter_ran, false); } @Override public Object run () {try {requestContext ctx = requestContext.getCurrentContext (); Exceção de ZuuLexception = FindzuUlexception (ctx.gethrowable ()); HttpServletRequest request = ctx.getRequest (); request.setAttribute ("javax.servlet.error.status_code", excepcion.nstatuscode); log.warn ("erro durante a filtragem", exceção); request.setAttribute ("javax.servlet.error.exception", exceção); if (stringutils.hastext (excepcion.errorcause)) {request.setAttribute ("javax.servlet.error.message", excepcion.errorcause); } RequestDispatcher Dispatcher = request.getRequestDispatcher (this.errorporath); if (despachante! = null) {ctx.set (send_error_filter_ran, true); if (! ctx.getResponse (). isCommitd ()) {ctx.setResponsestatuscode (excepcion.nstatuscode); Dispatcher.forward (solicitação, ctx.getResponse ()); }}} catch (Exceção ex) {refletionUtils.rethrowRuntimeException (ex); } retornar nulo; } ZuulException findzuulException (arremesso de arremesso) {if (throwable.getcause () instância de zuulruntimeException) {// Esta foi uma falha iniciada por um dos filtros locais retornam (zuulexception) throwable.getCausa (). GetCause (); } if (throwable.getcause () instanceof zuulException) {// envolvendo a exceção Zuul Return (zuulException) throwable.getcause (); } if (Instância de arremesso de zuulexception) {// Exceção lançada por Zuul Lifecycle Return (zuulException) Throwsable; } // fallback, nunca deve chegar aqui, devolva a nova zuulexception (throwable, httpServletResponse.sc_internal_server_error, null); } public void setErRorPath (string errorpath) {this.errorPath = errorpath; }}Aqui podemos encontrar vários pontos -chave:
1) No código acima, podemos descobrir que o filtro colocou as informações de erro relevantes na solicitação:
request.setAttribute ("javax.servlet.error.status_code", excepcion.nstatuscode);
request.setAttribute ("javax.servlet.error.exception", exceção);
request.setAttribute ("javax.servlet.error.message", excepcion.errorcause);
2) Depois que o erro for processado, ele será encaminhado para o endereço XXX/Erro para processamento
Então podemos fazer um experimento. Criamos um filtro que lança exceções no módulo de projeto de serviço de gateway:
pacote com.hzgj.lyrk.springcloud.gateway.server.filter; importar com.netflix.zuul.zuulfilter; import lombok.extern.slf4j.slf4j; import org.springframe.terotype.component; [email protected]; componente@slf4 @Override public String filterType () {return "post"; } @Override public int filterOrder () {return 9; } @Override public boolean devefilter () {return true; } @Override public Object run () {log.info ("Executar teste de erro ..."); lançar novo RuntimeTeException (); // retorna nulo; }}
Em seguida, definimos um controlador para lidar com erros:
pacote com.hzgj.lyrk.springcloud.gateway.server.filter; importar org.springframework.http.httpstatus; importar org.springframework.http.Responseentity; import org.springframework.web.bind.bind.bind.notation.annotation; org.springframework.web.bind.annotation.restcontroller; importar javax.servlet.http.httpServletRequest; @restcontrollerpublic class = errorHandler {@GetMapping (value = "/erro") public Resgation <lessBean> Error (HTTERS (HTTPS {@GetMapp ("/Error") PublicEntity <lessBean> Error (HTTE request.getAttribute ("javax.servlet.error.message"). tostring (); Errorbean errorbean = new errorbean (); errorbean.setMessage (mensagem); errorbean.setReason ("Erro do programa"); Retornar nova ResponseEntity <> (ErrorBean, httpstatus.bad_gateway); } classe estática privada ErrorBean {private String message; Razão privada da string; public String getMessage () {return message; } public void setMessage (string message) {this.message = message; } public string getReason () {return Rotity; } public void setreason (string reason) {this.Reason = reason; }}}Depois de iniciar o projeto, vamos tentar acessá -lo através do gateway:
2. Perguntas sobre o fallback de Zuul
1. Em relação ao problema do tempo limite de Zuul:
Existem muitas soluções para esse problema on -line, mas também quero postar o código -fonte. Por favor, preste atenção a esta classe abstractribbonCommand, que integra Hystrix e Ribbon nesta classe.
/ * * Copyright 2013-2016 O autor original ou autores. * * Licenciado sob a licença Apache, versão 2.0 (a "licença"); * Você não pode usar esse arquivo, exceto em conformidade com a licença. * Você pode obter uma cópia da licença em * * http://www.apache.org/license/license-2.0 * *, a menos que exigido pela lei aplicável ou acordada por escrito, o software * distribuído sob a licença seja distribuído em uma base "como é" *, sem garantia ou condição de qualquer tipo, seja expresso ou implícito. * Consulte a licença para as permissões e limitações do idioma específico e * sob a licença. * */package org.springframework.cloud.netflix.zuul.filters.route.support; importar org.apache.commons.logging.log; importar org.apache.commons.Logging.LogFactory; import Org.sibration.cloud.netflix.netflix.Logging.logFactory; import Org.sibration.Cloud.Netflix.Netflix. org.springframework.cloud.netflix.ribbon.ribbonHttpResponse; importar org.springframework.cloud.netflix.ribbon.support.abstractloadbonancelcient; import org.springframework.cloud.netflix.RibBonancordE; 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; importar com.netflix.client.config.defaultClientConfigmpl; importflix; com.netflix.client.http.httpResponse; importação com.netflix.config.dynamicintproperty; importar com.netflix.config.dynamicPropertyFactory; import.hysltrix.hyStrix.hysTrixCommand; import.NetomMFLIX.HySTRIX.HYSTRIX.HYSTRIX.HYSTRIX.HYSTRIX.HYSTRIX.HYSTRIX.HYSTRIX.HYSTRIX.HYSTRIX.HYSTRIX.HYSTRIX.HYSTRIX.HYSTRIX.HYSTRIX.HYSTRIX.HYSTRIX.HYSTRIX.HYSTRIX.HYSTRIX. 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 Class REBTRACTRIBBONCOMMAND <lbc extensil) Extensão, rs. HystrixCommand <clienthttproponse> implementa o RibBonCommand {private estático final logger = logFactory.getLog (abstractribbonCommand.class); Cliente LBC final protegido; contexto protegido RibBonCommandContext; ZuulfallbackProvider protegido ZuulfallbackProvider; ICLIENTCONFIG Protected Config; public abbtractribbonCommand (cliente LBC, contexto de ribbonCommandContext, zuulproperties zuulproperties) {this ("padrão", cliente, contexto, zuulproperties); } public abbtractribbonCommand (String CommandKey, LBC Client, RibBonCommandContext Context, Zuulproperties zuulProperties) {this (CommandKey, Client, Context, Zuulproperties, NULL); } public abbtractribbonCommand (String CommandKey, cliente LBC, contexto RibBonCommandContext, Zuulproperties zuulproperties, ZuulfallbackProvider FallbackProvider) {this (CommandKey, cliente, contexto, zuulproperties, FallbackProvider, Null); } public abstractribbonCommand (String CommandKey, cliente LBC, contexto RibBonCommandContext, Zuulproperties ZuulProperties, ZuulfallbackProvider FallbackProvider, IclientConfig Config) {this (getSetterter, zuulProperties, config), cliente, cliente, cliente, cliente, cliente), cliente, cliente, cliente, cliente, cliente, contorno, cliente, config, contornk), contornk), contêntre, combate (getSetKerPrack, config); } protegido abstractribbonCommand (setter setter, cliente lbc, contexto ribbonCommandContext, zuulfallbackprovider FallbackProvider, iClientConfig config) {super (setter); this.client = client; this.Context = context; this.zuulfallbackProvider = FallbackProvider; this.config = config; } estática protegida HystrixCommandProperties.Setter CreateSetter (ICLIENTCONFIG Config, String CommandKey, ZuulProperties zuulProperties) {int HystrixTimeout = GethystrixTimeout (config, CommandKey); return hystrixCommandProperties.Setter (). WithExecutionIsolationstrategy (zuulproperties.getRibbonisolationstrategy ()). withExecutionOutinMillisEconds (HystrixTimeout); } estática protegida 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 ("O tempo limite da hystrix de" + hystrixtimeout + "ms para o comando" + commandKey + "é definido que a combinação da fita de leitura e conexão de tempo limite" + ribbontimeout + ms. "); } retornar hystrixtimeout; } estática protegida 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, ribbonlientConfiguration.default_read_timeout); int ribbonConnectTimeout = getTimeout (config, commandKey, "ConnectTimeout", iclientConfigKey.keys.connectTimeout, ribbonlientConfiguration.default_connect_timeout); int maxautoretries = getTimeout (config, commandKey, "maxautoretries", iclientConfigKey.keys.maxautores, defaultClientConfigimpl.default_max_auto_rettes); int maxautoreTriesNextServer = getTimeout (config, commandKey, "maxautoretriesnextServer", iclientConfigKey.keys.MaxaUtoreTriesNextServer, defaultClientConfigimpl.Default_Max_Auto_retriex_Next_server); ribbontimeout = (RibBonReadTimeout + RibBonConnectTimeout) * (MaxautoreTries + 1) * (MaxaUtoretriesNextServer + 1); } retornar ribbontimeout; } private static int getTimeout (iClientConfig Config, String CommandKey, String Property, ICLIENTCONFIGKEY <TEGER> ConfigKey, int defaultValue) {DynamicPropertyFactory DynamicPropertyFactory = DynamicPropertyFactory.getInStance (); retornar dynamicPropertyFactory.getIntProperty (CommandKey + "." + config.getNamespace () + "." + propriedade, config.get (configkey, defaultValue)). get (); } @Deprecated // TODO removido em 2.0.x Setter GetSetter protegido (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 da sequência de string = zuulConstants.zuul_eureka + commandKey + ".semaphore.maxsemaphores"; // queremos inadimpliar a isolação de semáforo, uma vez que isso envolve // 2 outros comandos que já estão isolados de threads dinâmica dinâmica Valor = dynamicPropertyFactory.getInstance () .getIntProperty (nome, zuulproperties.getSemaphore (). setter.withExecutionIsolationsEmAphoremaxCurrentRequests (value.get ()); } else if (zuulProperties.getThreadpool (). isUSeseParateThreadPools ()) {final string threadpoolkey = zuulproperties.getThreadpool (). getThreadpoolkeyPrefix () + commandKey; CommandSetter.AndThreadpoolKey (HystrixThreadpoolKey.Factory.askey (ThreadpoolKey)); } retornar commandSetter.andCommandPropertiesDefaults (setter); // @Formatter: ON} @Override protegido clientHttpResponse run () lança exceção {final requestContext context = requestContext.getCurrentContext (); RQ request = createrequest (); Resposta rs; BOOLEAN RETRYABLECLIENT = this.clientOf AbstractLoadBalancingClient && ((AbstractLoadBalancingClient) this.client) .IclientRettyable (((contextAwarerequest) solicitação); if (retratilableClient) {resposta = this.client.execute (request, config); } else {Response = this.client.executeWithLoadBalancer (solicitação, config); } context.set ("RibbonResponse", resposta); // Fechar explicitamente a resposta HTTPRPRESS se o comando hystrix cronometrou // liberar a conexão HTTP subjacente mantida pela resposta. // if (this.isResponsetimedout ()) {if (resposta! = null) {Response.close (); }} retornar novo ribbonHttpResponse (resposta); } @Override protegido clientHttpResponse getFallback () {if (zuulfallbackProvider! = Null) {return getFallbackResponse (); } retornar super.getfallback (); } cliente protegidoHTTPRESPONSE GetFallbackResponse () {if (ZuulFallbackProvider Instância do FallbackProvider) {Throwable Causa = getFailedExecutionException (); causa = causa == nulo? getExecutionException (): causa; if (causa == null) {zuulfallbackProvider.FallbackResponse (); } else {return ((FallbackProvider) ZuulfallbackProvider) .FallbackResponse (causa); }} retornar zuulfallbackProvider.FallbackResponse (); } public lbc getClient () {return client; } public ribbonCommandContext getContext () {return context; } Resumo protegido rq createrequest () lança exceção;}Observação: o método GetRibBOntimeout e o método GethystrixTimeout, onde o valor desses dois métodos CommandKey é o nome da rota. Por exemplo, se visitarmos: http: // localhost: 8088/order-sever/xxx para acessar o serviço de ordem-servidor, então o CommandKey é o pedido-servidor
De acordo com o código-fonte, primeiro definimos os parâmetros de timeout do Server Gateway:
#Configurações da fita Global Ribbon: ConnectTimeout: 3000 ReadTimeout: 3000HyStrix: Command: Padrão: Execução: Isolamento: Tópico: TimeoutInMillisEconds: 3000Zuul: Host: ConnectTimeOutMillis: 10000
Obviamente, você também pode definir os parâmetros de tempo limite da faixa de opções para o servidor de ordem separadamente: order-sever.ribbon.xxxx = xxx. Para demonstrar o efeito de fallback em Zuul, defina o tempo limite da Hystrix um pouco mais curto aqui. Obviamente, é melhor não definir o tempo limite padrão da Hystrix como menor que o tempo limite da fita. Essa situação foi avisada de nós no código -fonte.
Em seguida, adicionamos o seguinte método sob o servidor de ordem:
@GetMapping ("/Sleep/{SleepTime}") public String Sleep (@PathVariable Long SleepTime) lança interruptedException {timeUnit.seconds.sleep (SleepTime); retornar "sucesso"; }2. Método de Fallback de Zuul
Podemos implementar a interface ZuulfallbackProvider e implementar o código:
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; importar java.io.ioException; importar java.io.inputStream; importar java.time.localdateTime; importar java.time.localtime; @ComPONMENTPUBLIC Classe FallbackHandler IMPRESTIMENTOS ZUULBLABLABLABLABLES { @@Override retornar "*"; } @Override public clientHttpSponse FallbackResponse () {return new clientHttpSponse () {@Override public httpstatus getStatuscode () lança ioexception {return httpstatus.ok; } @Override public int getrawStatuscode () lança IoException {return 200; } @Override public String getStatUSTUSTEXT () lança IoException {return "OK"; } @Override public void Close () {} @Override public inputStream getBody () lança IoException {String result = new Gsonbuilder (). Create (). Tojson (imutableMap.of ("errorcode", 500, "content", "solicitar", "tempo", localDateTime.Now ", 500," conteúdo "," solicitar ",", tempo ", localDateM.Now", retornar novo byteArrayInputStream (resultado.getBytes ()); } @Override public httpheaders getheaders () {httpheaders headers = new httpheaders (); headers.setContentType (mediatype.application_json); cabeçalhos de retorno; }}; }}No momento, visitamos: http: // localhost: 8088/order-server/sleep/6 e obtenha os seguintes resultados:
Quando visitamos: http: // localhost: 8088/order-server/sleep/1, obtemos o seguinte resultado:
Resumir
O acima é o conteúdo inteiro deste artigo. Espero que o conteúdo deste artigo tenha certo valor de referência para o estudo ou trabalho de todos. Se você tiver alguma dúvida, pode deixar uma mensagem para se comunicar. Obrigado pelo seu apoio ao wulin.com.