服务容错保护Hystrix&服务网关Zuul

1.服务容错保护Hystrix

1.1.背景

 在微服务架构中consumer调用provider的时候,provider在响应的时候,有可能会慢,如果provider 10s响应,那么consumer也会至少10s才响应。如果这种情况频度很高,那么就会整体降低consumer端服务的性能。这种响应时间慢的症状,就会像一层一层波浪一样,从底层系统一直涌到最上层,造成整个链路的超时。此时若有大量的请求涌入,服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。

如下图所示:服务超时像滚雪球一样放大,雪崩效应就形成了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6LFEnrjZ-1640248123839)(assets\1587888176837.png)]

1.2.如何解决雪崩效应

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L2fgaO7Q-1640248123841)(assets\1587888577908.png)]

Hystrix “豪猪”,又称为熔断器,具有自我保护的能力。Hystrix是Netflix开源的一个限流熔断的项目、主要有以下功能:

降级:

​ 整体资源不够用你了,忍痛将某些服务先关掉,待度过难关再开启。降级后可以配合降级接口返回托底数据。

熔断:

​ 当失败率达到阀值自动触发降级,熔断器触发的降级会进行快速恢复。

隔离:(线程池隔离和信号量隔离)

​ 限制调用分布式服务的资源使用,某一个调用的服务出现问题不会影响其他服务调用。

缓存:

​ 提供了请求缓存。

请求合并:

​ 提供请求合并。

1.3.Hystrix工作原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q4mTNZbU-1640248123842)(assets\1587889693379.png)]

以下三种情况将触发降级方法调用:

​ (1):熔断器开启

​ (2):线程池/信号量/错误率跑满

​ (3):业务逻辑异常

1.4.降级

​ 服务器高并发下,压力剧增的时候,根据当业务情况以及流量,对一些服务和页面有策略的降级(整体资源不够用了,先将某些服务关掉等度过难关再开启),以此缓解服务器资源的压力以保障核心任务的正常运行。

​ 双十一期间,支付宝很多功能都会提示,[双十一期间,保障核心交易,某某服务数据延迟](降级后可以配合降级接口返回托底数据)。

1.4.1.创建feign接口工程

1.4.1.1.创建工程

springcloud_hystrix_feign

1.4.1.2.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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
    </parent>
    <groupId>com.qf</groupId>
    <artifactId>springcloud_hystrix_feign</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <!--Spring Cloud OpenFeign Starter -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>com.qf</groupId>
            <artifactId>springcloud_common_pojo</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
    </dependencies>

</project>
1.4.1.3.fallback
package com.qf.fallback;


import com.qf.feign.UserFeign;
import com.qf.pojo.User;
import org.springframework.stereotype.Component;

@Component
public class UserServiceFallback implements UserFeign {
    @Override
    public User getUser() {
        return new User(1,"我是托底数据Fallback",0);
    }
}

1.4.1.4.feign
package com.qf.feign;

import com.qf.fallback.UserServiceFallback;
import com.qf.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

//获取eureka-provider的调用地址,且具有负载均衡的能力
@FeignClient(value="hystrix-provider",fallback= UserServiceFallback.class)
public interface UserFeign {

	@RequestMapping("/provider/getUser")
	public User getUser();
}

1.4.2.创建服务消费者

1.4.2.1.创建工程

springcloud_hystrix_consumer

1.4.2.2.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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
    </parent>

    <groupId>com.qf</groupId>
    <artifactId>springcloud_hystrix_consumer</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <!-- springBoot的启动器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--eureka-server客户端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>com.qf</groupId>
            <artifactId>springcloud_hystrix_feign</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>
1.4.2.3.application.properties
#向注册中心注册的名字
spring.application.name=hystrix-consumer
#设置服务注册中心地址
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:8761/eureka/

#开启hystrix
feign.hystrix.enabled=true
1.4.2.3.Controller
package com.qf.controller;

import com.qf.feign.UserFeign;
import com.qf.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

	@Autowired
	private UserFeign userFeign;

	@RequestMapping("/consumer/getUser")
	public User getUser(){
		return userFeign.getUser();
	}
}
1.4.2.4.App
package com.qf;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

/*@EnableCircuitBreaker //开启熔断器 断路器
@EnableEurekaClient
@SpringBootApplication*/
@EnableFeignClients
@SpringCloudApplication
public class ConsumerApp {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApp.class, args);
    }
}

1.4.3.创建服务提供者

1.4.3.1.创建工程

springcloud_hystrix_provider

1.4.3.2.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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
    </parent>
    <groupId>com.qf</groupId>
    <artifactId>springcloud_hystrix_provider</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <!-- springBoot的启动器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--eureka-server客户端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>com.qf</groupId>
            <artifactId>springcloud_common_pojo</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>
1.4.3.3.application.properties
#向注册中心注册的名字
spring.application.name=hystrix-provider
server.port=8777

#设置注册中心的地址
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:8761/eureka/
1.4.3.3.service
package com.qf.service;

import com.qf.pojo.User;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {

	@Override
	public User getUser() {
		return new User(1,"王粪堆",18);
	}

}

1.4.3.3.Controller
package com.qf.controller;

import com.qf.pojo.User;
import com.qf.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ProviderController {
	
	@Autowired
	private UserService userService;

	@RequestMapping("/provider/getUser")
	public User getUser(){
		return userService.getUser();
	}
	
}
1.4.3.4.App
package com.qf;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@EnableEurekaClient//允许向服务端注册服务
@SpringBootApplication
public class ProviderApp {
	public static void main(String[] args) {
		SpringApplication.run(ProviderApp.class, args);
	}
}

1.4.4.测试

开启服务提供者:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9yraelW1-1640248123844)(assets\1587907280435.png)]

关闭服务提供者:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EP1aVXUa-1640248123845)(assets\1587907165582.png)]

1.4.5.降级后的异常记录

1.4.5.1.fallback
package com.qf.fallback;


import com.qf.feign.UserFeign;
import com.qf.pojo.User;
import feign.hystrix.FallbackFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
public class UserServiceFallback implements FallbackFactory<UserFeign> {

    private Logger logger = LoggerFactory.getLogger(UserServiceFallback.class);

    @Override
    public UserFeign create(Throwable throwable) {
        return new UserFeign() {
            @Override
            public User getUser() {
                logger.warn("====Fallback Exception=====: ",throwable);
                return new User(1,"我是托底数据Fallback",0);
            }
        };
    }
}

1.4.5.2.feign
package com.qf.feign;

import com.qf.fallback.UserServiceFallback;
import com.qf.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

//获取eureka-provider的调用地址,且具有负载均衡的能力
@FeignClient(value="eureka-provider",fallbackFactory= UserServiceFallback.class)
public interface UserFeign {

	@RequestMapping(value="/user",method= RequestMethod.GET)
	public User getUser();
}
1.4.5.3.测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Uut406Xc-1640248123846)(assets\1587910196033.png)]

1.5熔断

​ 防止直接调用那些很可能会调用失败的服务,当失败率线程池、信号量达到阀值自动触发降级,熔断触发的降级会快速恢复。

​ 熔断生效后,会在指定的时间后调用请求来测试依赖是否恢复,依赖的应用恢复后关闭熔断。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wrkss6OP-1640248123848)(assets\1587910521032.png)]

1.5.1.熔断参数

断路器属性控制HystrixCircuitBreaker的行为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w9lL36nm-1640248123849)(assets\1587910594886.png)]

1.4.1.创建feign接口工程

1.4.1.1.创建工程

springcloud_hystrix_feign_breaker

1.4.1.2.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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
    </parent>
    <groupId>com.qf</groupId>
    <artifactId>springcloud_hystrix_feign_breaker</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <!--Spring Cloud OpenFeign Starter -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>com.qf</groupId>
            <artifactId>springcloud_common_pojo</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
    </dependencies>

</project>
1.4.1.4.feign
package com.qf.feign;

import com.qf.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

//获取eureka-provider的调用地址,且具有负载均衡的能力
@FeignClient("eureka-provider")
public interface UserFeign {

    @RequestMapping("/provider/user/{id}")
    public User getUser(@PathVariable Integer id);
}

1.4.2.创建服务消费者

1.4.2.1.创建工程

springcloud_hystrix_consumer_breaker

1.4.2.2.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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
    </parent>

    <groupId>com.qf</groupId>
    <artifactId>springcloud_hystrix_consumer_breaker</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <!-- springBoot的启动器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--eureka-server客户端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>com.qf</groupId>
            <artifactId>springcloud_hystrix_feign_breaker</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>
1.4.2.3.application.properties
#向注册中心注册的名字
spring.application.name=eureka-consumer
#设置服务注册中心地址
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:8761/eureka/

#开启熔断
feign.hystrix.enabled=true
1.4.2.3.Controller
package com.qf.controller;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager;
import com.qf.feign.UserFeign;
import com.qf.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

	@Autowired
	private UserFeign userFeign;

	@HystrixCommand(fallbackMethod = "fallback",
		commandProperties = {
			//错误百分比条件,达到熔断器最小请求数后错误率达到百分之多少后打开熔断器
			@HystrixProperty(name =                           HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE, value = "5"),
			//断容器最小请求数,达到这个值过后才开始计算是否打开熔断器
			@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD, value = "3"),
			// 默认5秒; 熔断器打开后多少秒后 熔断状态变成半熔断状态(对该微服务进行一次请求尝试,不成功则状态改成熔断,成功则关闭熔断器
			@HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS, value = "5000")
	})
	@RequestMapping("/consumer/getUser")
    public User getUser(@PathVariable Integer id){
        return userFeign.getUser(id);
    }

	// 返回托底数据的方法
	public User fallback(Integer id){
		return new User(0,"我是托底数据Fallback",0);
	}
}
1.4.2.4.App
package com.qf;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

/*@EnableCircuitBreaker //开启熔断器 断路器
@EnableEurekaClient
@SpringBootApplication*/
@EnableFeignClients
@SpringCloudApplication
public class ConsumerApp {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApp.class, args);
    }
}

1.4.3.创建服务提供者

1.4.3.1.创建工程

springcloud_hystrix_provider_breaker

1.4.3.2.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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
    </parent>
    <groupId>com.qf</groupId>
    <artifactId>springcloud_hystrix_provider_breaker</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <!-- springBoot的启动器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--eureka-server客户端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>com.qf</groupId>
            <artifactId>springcloud_common_pojo</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>
1.4.3.3.application.properties
#向注册中心注册的名字
spring.application.name=eureka-provider
server.port=8777

#设置注册中心的地址
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:8761/eureka/
1.4.3.3.service
package com.qf.service;

import com.qf.pojo.User;
import org.springframework.stereotype.Service;

import java.util.Date;

@Service
public class UserServiceImpl implements UserService {

	@Override
	public User getUser(Integer id) {
		System.out.println(id +"===>"+new Date());
		if (id == 1) {
			throw new RuntimeException("瞎胡搞!!!");
		}
		return new User(id,"王粪堆",18);
	}
}
1.4.3.3.Controller
package com.qf.controller;

import com.qf.pojo.User;
import com.qf.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ProviderController {
	
	@Autowired
	private UserService userService;

	@RequestMapping("/provider/user/{id}")
	public User getUser(@PathVariable("id") Integer id){
		return userService.getUser(id);
	}
	
}
1.4.3.4.App
package com.qf;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@EnableEurekaClient//允许向服务端注册服务
@SpringBootApplication
public class ProviderApp {
	public static void main(String[] args) {
		SpringApplication.run(ProviderApp.class, args);
	}
}

1.5.5.测试

测试超过3个请求5%的错误率是否打开熔断器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MqMhDPDT-1640248123851)(assets\1587912000870.png)]

1.5.6.配置文件方式配置熔断器

1.5.6.1.application.properties

修改消费者application.properties

#断容器最小请求数,达到这个值过后才开始计算是否打开熔断器
hystrix.command.default.circuitBreaker.requestVolumeThreshold=3
#错误百分比条件,达到熔断器最小请求数后错误率达到百分之多少后打开熔断器
hystrix.command.default.circuitBreaker.errorThresholdPercentage=5
#默认5秒; 熔断器打开后多少秒后 熔断状态变成半熔断状态(对该微服务进行一次请求尝试,不成功则状态改成熔断,成功则关闭熔断器
hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds=5000
1.5.6.2.feign

添加fallback

@Component
public class UserServiceFallback implements FallbackFactory<UserFeign> {

    private Logger logger = LoggerFactory.getLogger(UserServiceFallback.class);

    @Override
    public UserFeign create(Throwable throwable) {
        return new UserFeign() {
            @Override
            public User getUser(Integer id) {
                logger.warn("====Fallback Exception=====: ",throwable);
                return new User(1,"我是托底数据Fallback",0);
            }
        };
    }
}

修改feign接口

//获取eureka-provider的调用地址,且具有负载均衡的能力
@FeignClient(value="eureka-provider",fallbackFactory= UserServiceFallback.class)
public interface UserFeign {
	... ... ... ... ... ...
}
1.5.6.3.controller

修改消费者controller

@RestController
public class UserController {

	@Autowired
	private UserFeign userFeign;

	@RequestMapping("/consumer/user/{id}")
	public User getUser(@PathVariable("id") Integer id){
		return userFeign.getUser(id);
	}
}

1.6.hystrix超时问题

#第一层 hystrix 超时时间设置
#默认情况下是线程池隔离,超时时间 1000ms
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=1000
#第二层 ribbon 超时时间设置:设置比第一层小
# 请求连接的超时时间
ribbon.ConnectTimeout=5000
# 请求处理的超时时间
ribbon.ReadTimeout=5000

2.服务网关Zuul

2.1.Zuul概述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tZpHr0wV-1640248123852)(assets\1587973747373.png)]

Zuul 是netflix开源的一个开源的微服务API网关, 作用有:路由、限流、过滤。

Zuul做为网关层,自身也是一个微服务,跟其它服务Service-1,Service-2, … Service-N一样,都注册在eureka server上

2.2.创建Zuul工程

2.2.1.创建工程

springcloud_zuul

2.2.1.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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
    </parent>

    <groupId>com.qf</groupId>
    <artifactId>springcloud_zuul</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
        <!-- springBoot的启动器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--eureka-server客户端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>
</project>

2.2.1.application.properties

#向注册中心注册的名字
spring.application.name=gateway
server.port=9527
#设置注册中心的地址
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:8761/eureka/

#/gateway/开头的url请求,将转发到eureka-provider这个微服务上
zuul.routes.eureka-provider.path=/gateway/**

2.2.1.App

package com.qf;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@EnableZuulProxy//开启网关
@EnableEurekaClient//允许向服务端注册服务
@SpringBootApplication
public class ZuulApp {
	public static void main(String[] args) {
		SpringApplication.run(ZuulApp.class, args);
	}
}

2.3.路由

2.3.1.通过网关请求服务

2.3.1.1. 开启网关和服务

1、开启springcloud_hystrix_provider_breaker

2、开启springcloud_zuul

3、开启springcloud_eureka_server

2.3.1.1. 使用路由规则

http://127.0.0.1:9527/gateway/user/2

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lBrrH2As-1640248123854)(assets\1587985612158.png)]

2.3.1.1. 不使用路由规则

http://127.0.0.1:9527/eureka-provider/user/2
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AMrpFxc7-1640248123855)(assets\1587985657833.png)]

问题:

现在不论是否使用路由规则都可以正常访问,如果需要隐藏真实的服务名称进行访问(只能使用代理路由进行访问)如何实现?

2.3.2. 路由规则

2.3.2.1.隐藏真实服务名
#向注册中心注册的名字
spring.application.name=gateway
server.port=9527
#设置注册中心的地址
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:8761/eureka/

#/gateway/开头的url请求,将转发到eureka-provider这个微服务上
zuul.routes.eureka-provider.path=/gateway/**
#隐藏的服务,多个可以用*
#zuul.ignored-services=eureka-provider
zuul.ignored-services=*
2.3.1.2.使用路由规则

http://127.0.0.1:9527/gateway/user/2

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YT5CZCXB-1640248123856)(assets\1587985612158.png)]

2.3.1.3.不使用路由规则

http://127.0.0.1:9527/eureka-provider/user/2

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AkTJ2OWP-1640248123857)(assets\1587985901374.png)]

2.3.3.设置统一公共前缀

#向注册中心注册的名字
spring.application.name=gateway
server.port=9527
#设置注册中心的地址
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:8761/eureka/

#/gateway/开头的url请求,将转发到eureka-provider这个微服务上
zuul.routes.eureka-provider.path=/gateway/**
#排除的服务,多个可以用*
#zuul.ignored-services=eureka-provider
zuul.ignored-services=*

#/qf开头的url请求,才会转发到微服务上
zuul.prefix=/qf
2.3.3.1.使用路由规则

http://127.0.0.1:9527/qf/gateway/user/2

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-72jdUkZP-1640248123858)(assets\1587986236362.png)]	在这里插入图片描述

2.3.3.2.不使用路由规则

http://127.0.0.1:9527/gateway/user/2

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Il9WIBD9-1640248123859)(assets\1587986304678.png)]

2.4.过滤

2.4.1.过滤器

2.4.1.1.ZuulFilter抽象类

过滤器两个功能:

1、其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础;

2、过滤器功能则负责对请求的处理过程进行预干预,是实现请求校验等功能的基础。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LJOzk4p6-1640248123860)(assets\1587986423941.png)]

有4类可重写的方法来自定义过滤器,如下:

  • filterType:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下:

    • pre:在请求被路由之前调用,这种过滤器可以实现身份验证、记录调试信息等。
    • routing:在路由请求时候被调,这种过滤器用于构建发送给微服务的请求
    • post:在routing和error过滤器之后被调用,这种过滤器可用来为响应添加标准的HTTP Header、收集统计 信息和指标、将响应从微服务发送给客户端等。
    • error:在其他阶段发送错误时被调用
  • filterOrder:通过int值来定义过滤器的执行顺序

  • shouldFilter:返回一个boolean类型来判断该过滤器是否要执行,所以通过此函数可实现过滤器的开关。

  • run:过滤器的具体逻辑。

2.4.1.1.生命周期

根据自己的需要在不同的生命周期中去实现不同类型的过滤器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4Obao2Su-1640248123862)(assets\QQ图片20200427192347.png)]

2.4.2.网关过滤器实现身份验证

需求:在网关过滤器中通过Token 判断用户是否登录

2.4.2.1.创建过滤器

在服务网关中定义过滤器只需要继承ZuulFilter抽象类实现其定义的四个抽象函数就可对请求进行拦截与过滤。

package com.qf.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

@Component
public class PreFilter extends ZuulFilter {

	@Override
	public Object run() {
		// 获取请求上下文
		RequestContext rc = RequestContext.getCurrentContext();
		HttpServletRequest request = rc.getRequest();
		// 获取表单中的 token
		String token = request.getParameter("token");
		// 对 token 做判断
		if (token == null) {
			System.out.println("token is null............");
			rc.setSendZuulResponse(false);// 代表请求结束。不在继续向下请求
			rc.setResponseStatusCode(401);// 添加一个响应的状态码
			rc.setResponseBody("请登录后再访问!!!");// 响应内容
			rc.getResponse().setContentType("text/html;charset=utf-8");// 响应类型
		} else {
			// 访问 redis 服务 进行验证
			System.out.println("token is OK");
		}
		return null;
	}

	// 是否开启过滤器:默认为 false 不开启
	@Override
	public boolean shouldFilter() {
		return true;
	}

	// 过滤器的执行顺序:通过整数表示顺序,数值越小,优先级越高
	@Override
	public int filterOrder() {
		return 0;
	}

	// 过滤器类型:通过过滤器类型决定了过滤器执行的时机
	@Override
	public String filterType() {
		return "pre";
	}
}

2.4.2.2.测试

不加token:http://127.0.0.1:9527/qf/gateway/user/2

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XQbtRxwb-1640248123863)(assets\1587987487025.png)]

加token:http://127.0.0.1:9527/qf/gateway/user/2?token=12345

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t8KCV9Yc-1640248123864)(assets\1587987522909.png)]

2.5.限流

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xnup3IiO-1640248123865)(assets\1587991998002.png)]

2.5.1.令牌桶原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M6XV27l1-1640248123867)(assets\1587992038925.png)]

2.5.2.使用令牌桶算法实现限流

package com.qf.filter;

import com.google.common.util.concurrent.RateLimiter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletResponse;

/**
 * 限流器
 */
@Component
public class RateLimitFilter extends ZuulFilter {

    /**
     * 创建令牌桶
     *  RateLimiter.create(1)1: 是每秒生成令牌的数量,数值越大代表处理请求量越多
     */
    private static final RateLimiter RATE_LIMIT = RateLimiter.create(1);

    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    /**
     * 限流器的优先级应为最高
     * DEBUG_FILTER_ORDER = 1;
     * FORM_BODY_WRAPPER_FILTER_ORDER = -1;
     * PRE_DECORATION_FILTER_ORDER = 5;
     * RIBBON_ROUTING_FILTER_ORDER = 10;
     * SEND_ERROR_FILTER_ORDER = 0;
     * SEND_FORWARD_FILTER_ORDER = 500;
     * SEND_RESPONSE_FILTER_ORDER = 1000;
     * SIMPLE_HOST_ROUTING_FILTER_ORDER = 100;
     * SERVLET_30_WRAPPER_FILTER_ORDER = -2;
     * SERVLET_DETECTION_FILTER_ORDER = -3;
     */
    @Override
    public int filterOrder() {
        return FilterConstants.SERVLET_DETECTION_FILTER_ORDER;
    }

    /**
     /**
     * 指定需要执行该Filter的规则
     *  返回true则执行run()
     *  返回false则不执行run()
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        // 是否能从令牌桶中获取到令牌
        if (!RATE_LIMIT.tryAcquire()) {
            // 获取请求上下文
            RequestContext rc = RequestContext.getCurrentContext();
            rc.setSendZuulResponse(false);// 代表请求结束。不在继续向下请求
            rc.setResponseStatusCode(401);// 添加一个响应的状态码
            rc.setResponseBody("限流了,稍后再访问!!!");// 响应内容
            rc.getResponse().setContentType("text/html;charset=utf-8");// 响应类型
        }
        return null;
    }
}

2.5.3.测试

快速访问:http://127.0.0.1:9527/qf/gateway/user/2?token=12345

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OjMRn03a-1640248123868)(assets\1587992796301.png)]

2.6.网关容错

2.6.1.什么是网关容错

当consumer调用provider时由于各种原因会出现无法调用的情况,此时可以使用hystrix进行provider降级。那么客户端通过zuul无法调用consumer时,zuul也可以对consumer进行降级。
在这里插入图片描述

2.6.2.zuul和hystrix无缝结合

在zuul的启动器中包含了hystrix的jar,所有我们无需在项目中添加hystrix的启动器。

2.6.3.在zuul中实现服务降级

package com.qf.fallback;
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;

/**
 * 降级类:设置zuul无法调用consumer时的降级逻辑
 */
@Component
public class UserProviderFallBack implements FallbackProvider {
    /**
     * 指定要降级的服务
     * @return
     */
    @Override
    public String getRoute() {
        return "*";//*:所有服务
    }

    /**
     *
     * @param route
     * @param cause
     * @return
     */
    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        return new ClientHttpResponse(){

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.setContentType(MediaType.APPLICATION_JSON);
                return httpHeaders;
            }

            /**
             * 返回托底数据
             * @return
             * @throws IOException
             */
            @Override
            public InputStream getBody() throws IOException {
                String msg = route+" 服务不可用,请联系管理员,联系电话:110";
                return new ByteArrayInputStream(msg.getBytes());
            }

            /**
             * 返回相应的状态码:
             *  zuul转请求失败了,但是客户端向zuul发送请求是成功的,所以不应该把404、500等问题抛给客
             *  户端
             * @return
             * @throws IOException
             */
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                return this.getStatusCode().value();
            }

            @Override
            public String getStatusText() throws IOException {
                return this.getStatusCode().getReasonPhrase();
            }

            @Override
            public void close() {

            }
        };
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值