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

前言

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

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

一、Filter中统一异常处理

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

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

/*
 * 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:

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;
 }
}

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

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。

/*
 * 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的超时参数:

#全局的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下添加如下方法:

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

2、zuul的回退方法

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

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;
@Component
public 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;
   }
  };
 }
}

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

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

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

您可能感兴趣的文章:

  • 深入理解Spring Cloud Zuul过滤器
  • spring cloud如何修复zuul跨域配置异常的问题
  • SpringCloud实战小贴士之Zuul的路径匹配
  • Spring Cloud zuul自定义统一异常处理实现方法
  • 浅谈SpringCloud之zuul源码解析
(0)

相关推荐

  • Spring Cloud zuul自定义统一异常处理实现方法

    Zuul在springcloud微服务体系中提供filer和router功能,是微服务不可或缺的部分.filer处理默认实现的外还可以自定义进行授权.限流.安全校验等,router完全可以替代Nginx反向代理.Zuul异常处理就是由SendErrorFilter完成. 在我们应用过程我们发现使用默认的异常filter有两个问题不是很友好: 1.无法快速识别出是否是请求路由的服务超时还是没有任何可用节点,发生错误只能查看日志通过堆栈去定位: 2.无法兼容自定义的譬如{code:500,msg:"

  • 深入理解Spring Cloud Zuul过滤器

    前言 过滤器是Zuul的核心组件,这篇文章我们来详细讨论Zuul的过滤器.下面话不多说,来看看详细的介绍吧. 过滤器类型与请求生命周期 Zuul大部分功能都是通过过滤器来实现的.Zuul中定义了四种标准过滤器类型,这些过滤器类型对应于请求的典型生命周期. (1) PRE:这种过滤器在请求被路由之前调用.我们可利用这种过滤器实现身份验证.在集群中选择请求的微服务.记录调试信息等. (2) ROUTING:这种过滤器将请求路由到微服务.这种过滤器用于构建发送给微服务的请求,并使用Apache Htt

  • 浅谈SpringCloud之zuul源码解析

    zuul各版本实现存在一些微小的变化,总的实现思想未改变,以spring-cloud-netflix-core-1.3.6.RELEASE为例 一.zuul的重要的初始化类 org.springframework.cloud.netflix.zuul.ZuulServerAutoConfiguration org.springframework.cloud.netflix.zuul.ZuulProxyAutoConfiguration org.springframework.cloud.netf

  • SpringCloud实战小贴士之Zuul的路径匹配

    不论是使用传统路由的配置方式还是服务路由的配置方式,我们都需要为每个路由规则定义匹配表达式,也就是上面所说的 path 参数.在Zuul中,路由匹配的路径表达式采用了Ant风格定义. Ant风格的路径表达式使用起来非常简单,它一共有下面这三种通配符: 通配符 说明 ? 匹配任意的单个字符 * 匹配任意数量的字符 ** 匹配任意数量的字符,支持多级目录 我们可以通过下表的示例来进一步理解这三个通配符的含义并参考着来使用: URL路径 说明 /user-service/? 它可以匹配 /user-s

  • spring cloud如何修复zuul跨域配置异常的问题

    前言 本文主要给大家介绍一下在zuul进行跨域配置的时候出现异常该如何解决的方法,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 异常 The 'Access-Control-Allow-Origin' header contains multiple values '*, *', but only one is allowed 实例 Access-Control-Allow-Credentials:true Access-Control-Allow-Credentials:t

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

    前言 Zuul 是Netflix 提供的一个开源组件,致力于在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架.也有很多公司使用它来作为网关的重要组成部分,碰巧今年公司的架构组决定自研一个网关产品,集动态路由,动态权限,限流配额等功能为一体,为其他部门的项目提供统一的外网调用管理,最终形成产品(这方面阿里其实已经有成熟的网关产品了,但是不太适用于个性化的配置,也没有集成权限和限流降级). 本文主要给大家介绍了关于Spring Cloud Zuul统一异常处理与回退的相关内容,分享出来供大家

  • Spring Cloud入门教程之Zuul实现API网关与请求过滤

    简介 Zuul是Netflix基于JVM的路由器和服务器端负载均衡器.最常用的场景是替换Nginx反向代理后台微服务供前端UI访问. Zuul使用Ribbon来定位一个通过发现转发的实例,所有请求都以hystrix命令执行,所以故障将显示在Hystrix指标中. 注:Zuul不包括发现客户端,因此对于基于服务ID的路由,需要在类路径中提供其中一个路由 Zuul是Spring Cloud提供的api网关和过滤组件,它提供如下功能: 认证 过滤 压力测试 Canary测试 动态路由 服务迁移 负载均

  • spring cloud学习教程之config修改配置详解

    之前我们讲过了spring cloud之config配置的相关内容,那么在Git端修改配置后如何让客户端生效?下面来一起看看详细的介绍吧. 访问接口修改 refresh post方式执行http://localhost/refresh 会刷新env中的配置 restart 如果配置信息已经注入到bean中,由于bean是单例的,不会去加载修改后的配置 需要通过post方式去执行http://localhost/restart, 需要通过application.properties中配置endpo

  • Spring Cloud学习教程之DiscoveryClient的深入探究

    前言 当我们使用@DiscoveryClient注解的时候,会不会有如下疑问:它为什么会进行注册服务的操作,它不是应该用作服务发现的吗?下面我们就来深入的探究一下其源码. 一.Springframework的LifeCycle接口 要搞明白这个问题我们需要了解一下这个重要的接口: /* * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (

  • Spring Cloud Alibaba教程之Sentinel的使用

    什么是Sentinel Sentinel,中文翻译为哨兵,是为微服务提供流量控制.熔断降级的功能,它和Hystrix提供的功能一样,可以有效的解决微服务调用产生的"雪崩"效应,为微服务系统提供了稳定性的解决方案.随着Hytrxi进入了维护期,不再提供新功能,Sentinel是一个不错的替代方案.通常情况,Hystrix采用线程池对服务的调用进行隔离,Sentinel才用了用户线程对接口进行隔离,二者相比,Hystrxi是服务级别的隔离,Sentinel提供了接口级别的隔离,Sentin

  • Spring MVC学习教程之RequestMappingHandlerAdapter详解

    前言 RequestMappingHandlerAdapter实现了HandlerAdapter接口,顾名思义,表示handler的adapter,这里的handler指的是Spring处理具体请求的某个Controller的方法,也就是说HandlerAdapter指的是将当前请求适配到某个Handler的处理器.RequestMappingHandlerAdapter是HandlerAdapter的一个具体实现,主要用于将某个请求适配给@RequestMapping类型的Handler处理.

  • Spring MVC学习教程之RequestMappingHandlerMapping匹配

    前言 对于RequestMappingHandlerMapping,使用Spring的同学基本都不会陌生,该类的作用有两个: 通过request查找对应的HandlerMethod,即当前request具体是由Controller中的哪个方法进行处理: 查找当前系统中的Interceptor,将其与HandlerMethod封装为一个HandlerExecutionChain. 本文主要讲解RequestMappingHandlerMapping是如何获取HandlerMethod和Interc

  • jQuery插件学习教程之SlidesJs轮播+Validation验证

    SlidesJs(轮播支持触屏)--官网(http://slidesjs.com) 1.简介 SlidesJs是基于Jquery(1.7.1+)的响应幻灯片插件.支持键盘,触摸,css3转换. 2.代码 <!doctype html> <head> <style> /* Prevents slides from flashing */ #slides { display:none; } </style> <script src="http:/

  • ES6学习教程之Promise用法详解

    前言 promise用了这么多年了,一直也没有系统整理过.今天整理整理promise的相关东西,感兴趣的可以一起看一看.我尽量用更容易理解的语言来剖析一下promise 我准备分两篇文章来说明一下promise 一篇来理解和使用promise(本篇) 另一篇来从promise使用功能的角度来剖析下promise的源码(下一篇) 1.什么是Promise 我的理解是:实现让我们用同步的方式去写异步代码的一种技术.是异步解决方案的一种. 他可以将多个异步操作进行队列化,让它们可以按照我们的想法去顺序

  • PHP内核学习教程之php opcode内核实现

    opcode是计算机指令中的一部分,用于指定要执行的操作, 指令的格式和规范由处理器的指令规范指定. 除了指令本身以外通常还有指令所需要的操作数,可能有的指令不需要显式的操作数. 这些操作数可能是寄存器中的值,堆栈中的值,某块内存的值或者IO端口中的值等等. 通常opcode还有另一种称谓:字节码(byte codes). 例如Java虚拟机(JVM),.NET的通用中间语言(CIL: Common Intermeditate Language)等等. 1. Opcode简介 opcode是计算

随机推荐