1.前言

  刚入门 时,使用 ribbon + hystrix + restTemplate  ,实现了简单的 接口访问 + 客户端负载均衡 + 服务熔断保护 ;

然后学习了 feign ,整合了  ribbon + hystrix + restTemplate  的功能优点 并实现了上面功能 ;

上面的是实现服务与服务之间的服务熔断保护。

  如今 ,引入了 zuul ,API网关 ,外部用户统一访问zuul服务器,网关拦截请求 并作验证等操作通过后 路由到 指定的 内部 服务器集群 【zuul默认开启客户端负载均衡,类似ribbon】,

那么 ,如果 恰好 路由到的指定服务器时刚好宕机或者线程崩溃了怎么办?或者说当我们的后端服务出现异常的时候,我们不希望将异常抛出给最外层,期望服务可以自动进行降级处理。

这是网关与服务之间的服务熔断保护

  Zuul给我们提供了这样的支持。当某个服务出现异常时,直接返回我们预设的信息。【类似于 hystrix 的熔断处理】

2.准备一个服务提供者,端口 8001

目录结构

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cen.cloud</groupId>
<artifactId>cen-mycloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>provider-8001</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>provider-8001</name>
<description>Demo project for Spring Boot</description> <properties>
<java.version>1.8</java.version>
</properties> <dependencies>
<!-- spring boot web 组件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <!-- 测试组件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency> <!--eureka 注册中心依赖包 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency> <!-- 修改后立即生效,热部署 -->
<!-- 热修改后端-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
<version>1.2.4.RELEASE</version>
</dependency>
<!-- 热修改前端-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<!-- <optional>true</optional>-->
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> </project>

application.properties

# 服务名称
spring.application.name=provider-8001
# 端口
server.port=8001 #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址,
# [#找不到其他服务注册中心地址会报错]
eureka.client.serviceUrl.defaultZone=http://localhost:7001/eureka/
# ,http://localhost:7002/eureka/

controller层

package com.example.provider8001.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; @RestController
public class PRController { @RequestMapping(value = "/getname",method = RequestMethod.GET)
public String getname(String name){
System.out.println("接收名字="+name);
return "你大爷叫:"+name;
} }

启动类

package com.example.provider8001;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication
//开启发现服务
@EnableEurekaClient
public class Provider8001Application { public static void main(String[] args) {
SpringApplication.run(Provider8001Application.class, args);
} }

3.准备一个服务消费者,端口 9001

目录结构

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cen.cloud</groupId>
<artifactId>cen-mycloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>consumer-9001</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>consumer-9001</name>
<description>Demo project for Spring Boot</description> <properties>
<java.version>1.8</java.version>
</properties> <dependencies>
<!-- spring boot web 组件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <!-- 测试组件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency> <!--eureka 注册中心依赖包 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency> <!-- 修改后立即生效,热部署 -->
<!-- 热修改后端-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
<version>1.2.4.RELEASE</version>
</dependency>
<!-- 热修改前端-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<!-- <optional>true</optional>-->
</dependency> <!--feign依赖包-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency> <!--配置中心-客户端依赖包-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency> <!--健康检测管理中心 ,可刷新配置文件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency> <!--spring cloud bus,消息总线-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency> </dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> </project>

application.properties 为空

bootstrap.proterties  【配置内容有 消息中间件 rabbitmq 和bus 的配置,如果没有安装rabbitmq则不配置】

spring.application.name=consumer-9001
server.port=9001
#
# 当前微服务注册到eureka中(消费端),可不写 ,默认为true
#eureka.client.register-with-eureka=true
eureka.client.service-url.defaultZone=http://localhost:7001/eureka/
#
#配置中心客户端配置
#获取指定配置文件名称 ,多个则以英文符号 , 隔开,不可有空格
spring.cloud.config.name=gittest
#获取配置的策略 , 读取文件:dev开发环境、test测试、pro生产
spring.cloud.config.profile=dev
#获取配置文件的分支,默认是master。如果是是本地获取的话,则无用,
spring.cloud.config.label=master
#开启配置信息发现
spring.cloud.config.discovery.enabled=true
#指定配置中心服务端的service-id,便于扩展为高可用配置集群,不区分大小写
spring.cloud.config.discovery.serviceId=config-server-6001
#
#健康检测管理中心配置
#springboot 1.5.X 以上默认开通了安全认证,这里可加可不加,不影响
#management.security.enabled=false
#springboot 2.x 默认只开启了info、health的访问接口,*代表开启所有访问接口
management.endpoints.web.exposure.include=* #
#
## spring cloud bus 刷新配置
##rabbitmq 服务所在ip
#使用 localhost 会出错 ,使用 127.0.0.1 则没问题
spring.rabbitmq.host=127.0.0.1
#默认端口 5672
spring.rabbitmq.port=5672
#默认账户
spring.rabbitmq.password=guest
#默认密码
spring.rabbitmq.username=guest
##
##
## 开启消息跟踪
spring.cloud.bus.trace.enabled=true
#
#
#feign开启熔断器必须加这句话,不然无法使用,直接报500状态码
feign.hystrix.enabled=true
#设置连接超时时间
#feign.client.config.default.connect-timeout=10000
#feign.client.config.default.read-timeout=10000

controller层

package com.example.consumer9001.controller;

import com.example.consumer9001.feignInter.FeignService1;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import java.util.Date; @RefreshScope
@RestController
public class NameController {
@Autowired
private FeignService1 feignService1; @RequestMapping(value = "/doname", method = RequestMethod.GET)
public String doname(String name) {
System.out.println("接收名字=" + name + "==" + new Date());
return "我是消费者端口9001,微服务处理结果是:" + feignService1.getname(name);
} @Value("${yourname}")
private String namestr; @RequestMapping(value = "/getname", method = RequestMethod.GET)
public String getConfig() { String str = "我是消费者端口9001,获取远程配置文件信息:" + namestr + "===" + new Date();
System.out.println(str);
return str;
} // http://localhost:9001/getname }

feign 服务接口

package com.example.consumer9001.feignInter;

import com.example.consumer9001.feignInter.myFallbackFactory.FeignServuce1FallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam; //注入远程服务的应用名【不区分大小写】 ,以及熔断回调类
@FeignClient(name = "provider-8001", fallbackFactory = FeignServuce1FallbackFactory.class )
public interface FeignService1 { // 对应远程服务具体接口的名称和参数
@RequestMapping(value = "/getname", method = RequestMethod.GET)
// 需要添加 @RequestParam ,用于纠正参数映射 ,不然会报错 405 ,
// feign.FeignException$MethodNotAllowed: status 405 reading
public String getname(@RequestParam("name") String name); }

feign 服务降级回路操作

package com.example.consumer9001.feignInter.myFallbackFactory;

import com.example.consumer9001.feignInter.FeignService1;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component; import java.util.Date; /**
* feign使用断路器【熔断器】 ,当熔断发生后,运行这里的方法。类似于异常抛出
* 这里主要是处理异常出错的情况(降级/熔断时服务不可用,fallback就会找到这里来)
*/
@Component // 不要忘记添加,不要忘记添加,不加则无法使用熔断器
public class FeignServuce1FallbackFactory implements FallbackFactory<FeignService1> {
@Override
public FeignService1 create(Throwable throwable) {
return new FeignService1() {
@Override
public String getname(String name) {
//这里写熔断后的具体操作逻辑 return "输入参数是" + name + ",feign使用了断路器【熔断器】,限制服务处于熔断状态,运行了类似于抛出异常的方法,时间=" + new Date();
}
};
}
}

feign里的 Ribbon客户端负载均衡策略设置

package com.example.consumer9001.myconfig;

import com.netflix.loadbalancer.BestAvailableRule;
import com.netflix.loadbalancer.IRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; ///注解@Configuration千万千万不要忘记添加 ,不然无法使用这个静态配置类
@Configuration
public class ConfigBean { //设置负载均衡策略
@Bean
public IRule myRule() {
//其他看看 https://www.cnblogs.com/htyj/p/10705472.html
// //轮询策略,其实里面就是一个计数器
// return new RoundRobinRule(); //该策略通过遍历负载均衡器中维护的所有实例,会过滤调故障的实例,并找出并发请求数最小的一个,所以该策略的特征是选择出最空闲的实例
//如果集群有个服务器挂了,就可以过略的他,防止访问了故障服务器
return new BestAvailableRule(); } }

启动类

package com.example.consumer9001;

import com.example.consumer9001.myconfig.ConfigBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication
//服务客户端【发现服务】
@EnableEurekaClient
//@EnableDiscoveryClient ,也可以使用这个
//指定feign接口扫描范围 ,也可以不写
@EnableFeignClients(basePackages = {"com.example.consumer9001.feignInter"})
//开启客户端负载均衡自定义策略,参数name是该服务器的应用名字 ,configuration设置 策略配置类
@RibbonClient(name = "consumer-9001" ,configuration = ConfigBean.class)
public class Consumer9001Application { public static void main(String[] args) {
SpringApplication.run(Consumer9001Application.class, args);
} }

4.准备一个Zuul 网关 ,端口 5001

目录结构

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cen.cloud</groupId>
<artifactId>cen-mycloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>zuul-server-5001</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>zuul-server-5001</name>
<description>Demo project for Spring Boot</description> <properties>
<java.version>1.8</java.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--==========================================================================-->
<!--eureka 注册中心依赖包 -->
<!-- 这是服务中心端的依赖包-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>-->
<!-- </dependency>-->
<!-- 可是服务客户端的依赖包,两个包都有一样的功能-发现服务,但是下面这个包无 服务端的注解-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--==========================================================================-->
<!--zuul 网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency> </dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> </project>

application.properties

spring.application.name=zuul-server-5001
server.port=5001
#
eureka.client.service-url.defaultZone=http://localhost:7001/eureka/
#全局添加前缀,如 localhost:114/myzuul/test/bb ,用于识别是否需要转发路由操作
#不可使用 /zuul ,猜测这是保留字
zuul.prefix=/mzuul
#//默认是false,这里是全局配置
#zuul.strip-prefix: //是否将这个代理前缀去掉
#
#忽略所有的,表示禁用默认路由,只认我们自己配置的路由.
#zuul.ignored-services="*"
#
#自定义路由设置
#拦截路径
zuul.routes.bd.path=/bd/**
#拦截后访问的指定地址
zuul.routes.bd.url=https://www.baidu.com/
#
##拦截路径
#zuul.routes.CONSUMER-9001.path=/CONSUMER-9001/**
##拦截后访问的指定地址
#zuul.routes.CONSUMER-9001.service-id=CONSUMER-9001
#
#拦截后访问的指定服务,使用服务名,根据注册中心获取的服务列表映射具体服务ip地址
#zuul.routes.api-b.service-id=520LOVE #http://localhost:5001/mzuul/CONSUMER-9001/gettname # 心得 :如果使用 服务列表默认映射的 拦截路径 ,则写在 httpurl 的服务名必须小写 ,即便 远程服务名 有大小写字符 ,
# 但在请求路径也必须全部改成小写 ,否则报错 404

自定义拦截文件夹 myFilter 的两个过滤文件 ,随笔 有详细讲解   https://www.cnblogs.com/c2g5201314/p/12996687.html

package com.example.zuulserver5001.myFilter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.UnsupportedEncodingException; //注册bean ,不使用@Component注解则需要去启动类创建一个方法new一个LoginFilter类后
// 使用 @bean注解注册bean,一样的作用
@Component
public class LoginFilter extends ZuulFilter { /**
* 选择过滤器类型
*/
@Override
public String filterType() {
//一共有下面4种过滤器
// public static final String ERROR_TYPE = "error";
// public static final String POST_TYPE = "post";
// public static final String PRE_TYPE = "pre";
// public static final String ROUTE_TYPE = "route";
return FilterConstants.PRE_TYPE;
} /**
* 通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高
*/
@Override
public int filterOrder() {
return 0;
} /**
* 返回一个`Boolean`值,判断该过滤器是否需要执行。返回true执行,返回false不执行。
*/
@Override
public boolean shouldFilter() {
return true;
} /**
* 过滤器的具体业务逻辑
*/
@Override
public Object run() throws ZuulException {
System.out.println("进入zuul拦截-login拦截");
//获取上下文
RequestContext ctx = RequestContext.getCurrentContext();
//获取Request
HttpServletRequest request = ctx.getRequest();
//获取请求参数
String token = request.getParameter("token");
System.out.println("参数token=" + token);
//
//
if (StringUtils.isBlank(token)) {
//参数内容为空
//拦截,拒绝路由
ctx.setSendZuulResponse(false);
//返回状态码
ctx.setResponseStatusCode(401);
//返回的响应体信息
try {
//不可以直接写中文,前端会显示中文乱码,加上这就解决中文乱码问题
//以文本格式显示,字体比较大
ctx.getResponse().setContentType("text/html;charset=UTF-8");
//以json格式显示,字体比较小
// ctx.getResponse().setContentType("application/json;charset=UTF-8");
// 上一句等同于 ctx.getResponse().setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
//
ctx.getResponse().getWriter().write("token is 空的-------401");
} catch (IOException e) {
e.printStackTrace();
}
} return null;
}
}
package com.example.zuulserver5001.myFilter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.logging.Filter; @Component
public class LoginCheckFilter extends ZuulFilter {
//上下文,不可以设为 private
RequestContext ctx = null;
//Request
private HttpServletRequest request = null; @Override
public String filterType() {
return FilterConstants.PRE_TYPE;
} @Override
public int filterOrder() {
return 1;
} @Override
public boolean shouldFilter() {
System.out.println("进入zuul拦截-loginCheck拦截,判断是否开启该拦截");
//获取上下文
ctx = RequestContext.getCurrentContext();
// 判断上一层拦截是否通过
if(!ctx.sendZuulResponse()){
//上一层拦截不通过
System.out.println(" 上一层拦截不通过,不开启loginCheck拦截");
//该拦截不需要开启
return false;
}
//上层拦截通过
//获取Request
request = ctx.getRequest();
//获取请求路径
String urlStr = request.getRequestURI().toString();
//当访问路径含有/mzuul/bd/则开启该拦截
return urlStr.contains("/mzuul/bd");
} @Override
public Object run() throws ZuulException {
System.out.println("运行loginCheck拦截逻辑");
//获取请求参数
String token = request.getParameter("token");
System.out.println("---参数token=" + token);
if (StringUtils.isBlank(token) | (token != null && !token.equals("kk"))) {
// token 是空的 或者 不是 kk
//拦截
System.out.println("拦截,拒绝路由请求, token 是空的 或者 不是 kk");
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(7781);
try {
ctx.getResponse().setContentType("text/html;charset=UTF-8");
ctx.getResponse().getWriter().write("请求参数="+token+",当参数是kk才可以通过");
} catch (IOException e) {
e.printStackTrace();
}
} return null;
}
}

路由熔断后的服务降级回路操作

package com.example.zuulserver5001.myProducerFallback;

import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
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; //注册bean
@Component
public class Consumer9001Fallback implements FallbackProvider { //测试请求1
// http://localhost:5001/mzuul/consumer-9001?token=kk
//测试请求2
// http://localhost:5001/mzuul/consumer-9001/doname?token=kk&name=lili //指定要处理的远程服务
@Override
public String getRoute() {
//必须是小写,即使 远程服务名的字符有大写,这里也必须换成小写,否则报错,无法执行服务降级操作,【与http网址一样,必须小写】
return "consumer-9001";
} /**
* 具体退路操作逻辑
*/
private ClientHttpResponse fallbackResponse(){
return new ClientHttpResponse() {
//返回http状态
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
//返回状态码
@Override
public int getRawStatusCode() throws IOException {
//200是正常
return 200;
}
//返回状态内容
@Override
public String getStatusText() throws IOException {
return "OK";
} //目前这里不清楚是干什么的
@Override
public void close() { }
//返回响应体
@Override
public InputStream getBody() throws IOException {
//以字节流形式返回
return new ByteArrayInputStream("beng--i can do nothing,崩溃了,11223344".getBytes());
} //返回响应头
@Override
public HttpHeaders getHeaders() {
HttpHeaders httpHeaders = new HttpHeaders();
//设置响应数据的编码类型
httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);
return httpHeaders;
}
};
} @Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
if (cause != null && cause.getCause() != null) {
String reason = cause.getCause().getMessage();
System.out.println("异常原因:\n"+reason);
// logger.info("Excption {}",reason);
} return this.fallbackResponse();
}
} /*
总结:
(1)zuul使用服务列表的默认映射,那么在网址访问的时候,在路径写远程服务名时必须字符全小写【不论远程服务名字符是否有大写】,否则找不到服务;
(2)当远程服务异常,zuul调用该服务的熔断/降级操作,在回路操作设置时指定的远程服务名必须字符全小写【不论远程服务名字符是否有大写】,
否则报错,无法执行该服务降级操作;
(3)使用 zuul --> 服务消费者 --> 服务提供者 的分布式微服务架构 。
当提供者出现异常时,消费者对其服务降级,执行回路操作 ;
但如果是zuul 路由到消费者去调用提供者服务,当提供者出现异常时,则将会执行zuul对消费者的服务降级,执行指定消费者的回路操作,
【打印原因 java.net.SocketTimeoutException: Read timed out】
但是过了一段时间后再次访问zuul 路由到消费者去调用该提供者服务,将会返回消费者对提供者执行的回路操作结果。【很是奇怪,原因还不清楚】
当消费者出现异常时,那么zuul将会执行对消费者的服务降级,执行该消费者的回路操作; 使用 zuul --> 服务消费者 --> 服务提供者 的分布式微服务架构 。
如果是zuul 路由到消费者去调用提供者服务,当提供者出现异常时,则将会执行zuul对消费者的服务降级,执行指定消费者的回路操作,
但是过了一段时间后再次访问zuul 路由到消费者去调用该提供者服务,将会返回消费者对提供者执行的回路操作结果。
【很是奇怪,原因还不清楚】
*/

启动类

package com.example.zuulserver5001;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.netflix.zuul.EnableZuulServer; @SpringBootApplication
//开启发现服务
@EnableEurekaClient
//开启zuul网关代理
@EnableZuulProxy
public class ZuulServer5001Application { public static void main(String[] args) {
SpringApplication.run(ZuulServer5001Application.class, args);
} }

5.其他准备

具体配置源码 这里 省略 ,可参考去我的其他随笔

提前准备一个 端口 7001 的 服务注册中心 ,参考随笔地址   https://www.cnblogs.com/c2g5201314/p/12877948.html

提前准备一个 端口 6001 的 服务配置中心 ,参考随笔地址  https://www.cnblogs.com/c2g5201314/p/12897753.html

6.测试

(1)依次启动 的工程端口号 7001 【注册中心】,6001【配置中心】 ,8001【提供者】,9001【消费者】,5001【Zuul网关】

(2)测试目的:检测服务消费者能否调用服务提供者的服务

访问端口  9001 , http://localhost:9001/doname?name=tom

调用成功

(3)测试目的:检测外部访问Zuul 能否路由到服务消费者 后调用 服务消费者 的服务

访问端口 5001  , http://localhost:5001/mzuul/consumer-9001/getname?token=gh

调用成功

(4)测试目的:检测外部访问Zuul 能否路由到服务消费者 后调用 服务提供者 的服务

访问端口 5001  ,http://localhost:5001/mzuul/consumer-9001/doname?token=kg&name=tom

调用成功

(5)测试目的:检测服务提供者崩溃后,消费者能否服务熔断后做服务降级回路操作

关闭端口8001 的工程 ,

访问端口  9001 , http://localhost:9001/doname?name=tom

回路操作成功

(6)测试目的:检测服务消费崩溃后,Zuul网关能否路由熔断后做服务降级回路操作

重新开启端口8001的工程  ,然后再关闭端口9001的工程

访问端口 5001  ,http://localhost:5001/mzuul/consumer-9001/doname?token=kg&name=tom

回路操作成功

再次访问访问端口 5001  ,http://localhost:5001/mzuul/provider-8001/getname?token=kg&name=tom   ,直接路由的 服务提供者服务 ,正常使用 ,因此回路操作仅局限于指定服务

(7)测试目的:检测服务提供者崩溃后,Zuul路由到 服务消费者后调用服务提供者到底会返回 服务消费者的服务降级结果 还是执行了 Zuul 网关对服务消费者路由熔断后的服务降级回路操作

重新开启端口9001的工程  ,然后再关闭端口8001的工程

访问端口 5001  ,http://localhost:5001/mzuul/consumer-9001/doname?token=kg&name=tom

显然 ,服务提供者崩溃后,执行了 Zuul 网关对服务消费者路由熔断后的服务降级回路操作

查看控制台

显示异常 java.net.SocketTimeoutException: Read timed out

可是,万万没想到 ,过了一段时间后 ,

再次访问端口 5001  ,http://localhost:5001/mzuul/consumer-9001/doname?token=kg&name=tom

竟然 返回 了 服务消费者对服务提供者的服务降级结果

查看控制台,并没有打印原因

奇怪,奇怪,真奇怪 !!!!!!目前还没有找到原因,因为没有老师,这就是自学的弊端!

查阅了大量资料,出现这个现象的原因是 Zuul 路器熔断的超时时间小于远程服务消费者的feign熔断超时时间 导致的,

那么在 Zuul 工程 的 application.properties文件加入配置

完整的源码

spring.application.name=zuul-server-5001
server.port=5001
#
eureka.client.service-url.defaultZone=http://localhost:7001/eureka/
#全局添加前缀,如 localhost:114/myzuul/test/bb ,用于识别是否需要转发路由操作
#不可使用 /zuul ,猜测这是保留字
zuul.prefix=/mzuul
#//默认是false,这里是全局配置
#zuul.strip-prefix: //是否将这个代理前缀去掉
#
#忽略所有的,表示禁用默认路由,只认我们自己配置的路由.
#zuul.ignored-services="*"
#
#自定义路由设置
#拦截路径
zuul.routes.bd.path=/bd/**
#拦截后访问的指定地址
zuul.routes.bd.url=https://www.baidu.com/
#
##拦截路径
#zuul.routes.CONSUMER-9001.path=/CONSUMER-9001/**
##拦截后访问的指定地址
#zuul.routes.CONSUMER-9001.service-id=CONSUMER-9001
#
#拦截后访问的指定服务,使用服务名,根据注册中心获取的服务列表映射具体服务ip地址
#zuul.routes.api-b.service-id=520LOVE #http://localhost:5001/mzuul/CONSUMER-9001/gettname # 心得 :如果使用 服务列表默认映射的 拦截路径 ,则写在 httpurl 的服务名必须小写 ,即便 远程服务名 有大小写字符 ,
# 但在请求路径也必须全部改成小写 ,否则报错 404 #熔断超时时间设置
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=6000
spring.cloud.loadbalancer.retry.enabled=true
ribbon.ReadTimeout=60000
ribbon.ConnectTimeout=60000

将所有工程重新运行后,再次执行一次本次测试步骤 ,

服务提供者崩溃后,直接返回 服务消费者对服务提供者的服务降级结果

7.总结

(1)zuul使用服务列表的默认映射,那么在网址访问的时候,在路径写远程服务名时必须字符全小写【不论远程服务名字符是否有大写】,否则找不到服务;

(2)当远程服务异常,zuul调用该服务的熔断/降级操作,在回路操作设置时指定的远程服务名必须字符全小写【不论远程服务名字符是否有大写】,
否则报错,无法执行该服务降级操作;

(3)使用 zuul --> 服务消费者 --> 服务提供者 的分布式微服务架构 。
当提供者出现异常时,消费者对其服务降级,执行回路操作 ;
但如果是zuul 路由到消费者去调用提供者服务,当提供者出现异常时,则将会执行zuul对消费者的服务降级,执行指定消费者的回路操作,
【打印原因 java.net.SocketTimeoutException: Read timed out】
但是过了一段时间后再次访问zuul 路由到消费者去调用该提供者服务,将会返回消费者对提供者执行的回路操作结果。
  出现这个现象的原因是 Zuul 路器熔断的超时时间小于远程服务消费者的feign熔断超时时间 导致的,修改Zuul的熔断超时时间即可。
当消费者出现异常时,那么zuul将会执行对消费者的服务降级,执行该消费者的回路操作。

完整的项目我放在了GitHub仓库 ,在分支 branch5-31

https://github.com/cen-xi/test/tree/branch5-31

spring cloud Zuul + 路由熔断【服务降级】 --- 心得的更多相关文章

  1. Spring Cloud Zuul路由规则动态更新

    背景  Spring Cloud Zuul 作为微服务的网关,请求经过zuul路由到内部的各个service,由于存在着新增/修改/删除服务的路由规则的需求,zuul的路由规则的动态变更功能 提供了 ...

  2. 【spring cloud】spring cloud zuul 路由网关

    GitHub源码地址:https://github.com/AngelSXD/springcloud 版本介绍: <properties> <project.build.source ...

  3. Spring Cloud Zuul API服务网关之请求路由

    目录 一.Zuul 介绍 二.构建Spring Cloud Zuul网关 构建网关 请求路由 请求过滤 三.路由详解 一.Zuul 介绍 ​ 通过前几篇文章的介绍,我们了解了Spring Cloud ...

  4. Spring Cloud Zuul网关 Filter、熔断、重试、高可用的使用方式。

    时间过的很快,写springcloud(十):服务网关zuul初级篇还在半年前,现在已经是2018年了,我们继续探讨Zuul更高级的使用方式. 上篇文章主要介绍了Zuul网关使用模式,以及自动转发机制 ...

  5. 服务网关Spring Cloud Zuul

    Spring Cloud Zuul 开发环境 idea 2019.1.2 jdk1.8.0_201 Spring Boot 2.1.9.RELEASE Spring Cloud Greenwich S ...

  6. 第七章 API网关服务:Spring Cloud Zuul

    API网关是一个更为智能的应用服务器, 它的定义类似于面向对象设计模式中的Facade模式, 它的存在就像是整个微服务架构系统的门面一样,所有的外部客户端访问都需要经过它来进行调度和过滤.它除了要实现 ...

  7. SpringCloud---API网关服务---Spring Cloud Zuul

    1.概述 1.1 微服务架构出现的问题   及  解决: 1.1.1 前言 每个微服务应用都提供对外的Restful API服务,它通过F5.Nginx等网络设备或工具软件实现对各个微服务的路由与负载 ...

  8. Spring Cloud Zuul Filter 和熔断

    转一篇很不错的关于Spring Cloud Zuul 相关用法的文章,基本包含常用的一些场景,另外附上实际项目中的熔断.打印请求日志和登录验证的实例. 原文地址:https://www.cnblogs ...

  9. spring cloud深入学习(十一)-----服务网关zuul

    前面的文章我们介绍了,Eureka用于服务的注册于发现,Feign支持服务的调用以及均衡负载,Hystrix处理服务的熔断防止故障扩散,Spring Cloud Config服务集群配置中心,似乎一个 ...

随机推荐

  1. Pagination.js + Sqlite web系统分页

    前端使用 jquery pagination.js 插件. 环境准备:jquery.js.pagination.js.pagination.css 附件下载:https://files.cnblogs ...

  2. C++内存管理:new / delete 和 cookie

    new 和 delete C++的内存申请和释放是通过 new 和 delete 实现的, 而new 和 delete 其实就是通过 malloc 和 free 实现的. new 申请内存分为三个步骤 ...

  3. JavaXML解析的四种方法(连载)

    1. xml简介 XML:指可扩展标记语言, Extensible Markup Language:类似HTML.XML的设计宗旨是传输数据,而非显示数据. 一个xml文档实例: 1 <?xml ...

  4. Mysql报错合集

    目录 一.链接报错 客户端连接mysql出错 链接客户端出错 交互登陆mysql出现warning警告Using a password 导入数据到数据库报错ERROR 1050 登陆数据库提示-bas ...

  5. 简单的理解 Object.defineProperty()

    Object.defineProperty()的作用就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性. Object.defineProperty(obj,prop,descriptor ...

  6. IDEA中安装SVN

     下载svn和汉化安装包:   (下面安装过程中,运行这两个安装包需要管理员权限:使用管理员权限运行cmd,在cmd中运行这两个安装包)   1.安装SVN 安装SVN时这里要选择[will be i ...

  7. 小迪安全 Web安全 基础入门 - 第二天 - Web应用&架构搭建&漏洞&HTTP数据包&代理服务器

    一.网站搭建 1.域名.是由一串用点分隔的字符组成的互联网上某一台计算机或计算机组的名称,用于在数据传输时标识计算机的电子方位.域名可以说是一个IP地址的代称,目的是为了便于记忆后者. 2.子域名.在 ...

  8. CF1132B Discounts 题解

    Content 有一个长度为 \(n\) 的数组 \(a_1,a_2,a_3,...,a_n\).有 \(q\) 次询问,每次询问给定一个数 \(x\).对于每次询问,求出数组中去掉一个第 \(x\) ...

  9. 大学MOOC课程视频下载、流文件合并、批量重命名、b站视频下载及学习课程视频推荐

    计算机行业技术更新快,编程语言种类多,在当今大数据和人工智能的时代,为了能在相关领域有所成就,就必须掌握好python.R等语言,较好的数学基础和深入的行业背景知识.计算机从业人员务必践行" ...

  10. docker查看容器元数据、详细信息,查看容器挂载的目录

    通过 docker inspect 175f 查看容器元数据 我们启动docker的时候会挂载目录,但是挂载之后 后面就忘了 如何查看挂载的目录位置呢 可以通过 docker inspect a7a6 ...