SpringCloud02

发布时间 2023-06-26 22:02:02作者: 加包辣条多放辣椒

1. 负载均衡

  • 实际环境中,我们往往会开启很多个provider的集群。此时我们获取的服务列表中就会有多个,到底该访问哪一个呢?
  • springcloud 2020.0.1 版本之前使用的是Ribbon,springcloud 2020.0.1 版本之后将 Ribbon剔除了,改为使用Loadbalancer替代Ribbon。
  • springcloud 2020.0.1 版本之前Eureka中已经帮我们集成了负载均衡组件:Ribbon,简单修改代码即可使用。

image

  • Ribbon是 Netflix 提供的一个基于HTTP和TCP的客户端负载均衡工具
  • Ribbon主要有两个功能
  • 简化远程调用
  • 负载均衡
  • Loadbalancer和Ribbon的性质是一样的。

1.1. 服务端负载均衡和客户端负载均衡

服务端负载均衡

  • 负载均衡算法在服务端。
  • 由负载均衡器维护服务地址列表 。

image

客户端负载均衡

  • 负载均衡算法在客户端
  • 客户端维护服务地址列表

image

1.2. 负载均衡方式

  • 低版本Eureka中已经集成了Ribbon,所以我们无需引入新的依赖,直接修改代码,默认使用的是轮询的方式。
  • springcloud 2020.0.1 版本之后删除了eureka中的ribbon,替代ribbon的是spring cloud自带的LoadBalancer,默认使用的是轮询的方式。

1.3. 开启负载均衡

1.3.1. 启动两个服务提供方式实例

image

1.3.2. Loadbalancer实现负载均衡

修改springcloud-consumer的配置类,在RestTemplate的配置方法上添加 @LoadBalanced 注解

@Configuration
public class RestTemplateConfiguration {

    @Bean
    @LoadBalanced  //使用resttemplate方法时,进行客户端负载均衡操作
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

}

修改调用方式,不再手动获取ip和端口,而是直接通过服务名称调用

@RestController
public class ConsumerController {
    //浏览器 -> consumer,使用resttemplate -> provider
    @Autowired
    private RestTemplate restTemplate;
    @RequestMapping("/consumer")
    public String consumer(){
		//通过Ribbon或者loadbalancer实现客户端负载均衡,路径必须是提供者在注册
        中心中的名称,因为provider做集群操作,
		//节点名称在eureka注册中心中是一致的
        String url = "http://loadbalancer-provider/provider";
        String msg = restTemplate.getForObject(url, String.class);
        return msg;
    }
}

进行测试

第一次

image

第二次

image

1.3.3. 修改Loadbalancer负载均衡策略

LoadBalancer默认提供了两种负载均衡策略:

  • RandomLoadBalancer - 随机分配策略
  • (默认) RoundRobinLoadBalancer - 轮询分配策略

Ribbon也是一样的。

在springcloud-consumer中

  • 编写配置类
@Configuration
public class CustomeLoadBalanceConfiguration {

    @Bean
    public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,
                                                                                   LoadBalancerClientFactory loadBalancerClientFactory){
        //设置loadbalancer负载均衡方式为随机方式
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        RandomLoadBalancer randomLoadBalancer = new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
        return randomLoadBalancer;
    }

}
  • 修改配置类
@Configuration
@LoadBalancerClient(value = "loadbalancer-provider",configuration = CustomeLoadBalanceConfiguration.class)
public class RestTemplateConfiguration {

    @Bean
    @LoadBalanced  //使用resttemplate方法时,进行客户端负载均衡操作
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

}
  • 测试

重启springcloud-consumer,进行访问,发现会随机访问8082和8084端口的provider

1.3.4. Ribbon实现负载均衡策略(了解)

Ribbon默认的负载均衡策略是简单的轮询,也支持随机分配策略

  • 修改pom文件的版本号

springcloud的版本以前使用的是英国伦敦地铁站的名字命名,之后才改成时间作为版本号 名称

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.0.7.RELEASE</version> <!-- 修改springboot的版本号 -->
	<relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
	<java.version>1.8</java.version>
	<spring-cloud.version>Finchley.SR2</spring-cloud.version><!-- 修改springcloud的版本号 -->
</properties>
  • Ribbon的其他负载均衡操作和Loadbalancer操作是一样的。
  • 修改yml配置文件
server:
	port: 8075
spring:
	application:
		name: eureka-consumer
eureka:
	client:
		service-url: # EurekaServer地址
			defaultZone: http://127.0.0.1:10086/eureka
# 设置负载均衡策略,默认为轮询
eureka-provider:
	ribbon:
		NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 改为
随机策略

2. 熔断机制Hystrix

  • Hystrix,英文意思是豪猪,全身是刺,看起来就不好惹,是一种保护机制。
  • Hystrix也是Netflix公司的一款组件。
  • Hystix是Netflix开源的一个延迟和容错库,用于隔离访问远程服务、第三方库,防止出现级 联失败。

2.1. 微服务雪崩问题

  • 微服务中,服务间调用关系错综复杂,一个请求,可能需要调用多个微服务接口才能实现, 会形成非常复杂的调用链路。

image

  • 如图,一次业务请求,需要调用A、P、H、I四个服务,这四个服务又可能调用其它服务。 如果此时,某个服务出现异常。

image

  • 微服务I发生异常,请求阻塞,用户不会得到响应,则tomcat的这个线程不会释放,于是越 来越多的用户请求到来,越来越多的线程会阻塞。

image

  • 服务器支持的线程和并发数有限,请求一直阻塞,会导致服务器资源耗尽,从而导致所有其它服务都不可用,形成雪崩效应。

2.2. Hystrix解决雪崩问题的手段有两个

2.2.1. 隔离

  • 线程池隔离(默认)

    • 用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,或者请求超时,则会进行降级处理。

    • 用户的请求故障时,不会被阻塞,更不会无休止的等待或者看到系统崩溃,至少可以看到一个执行结果(例如返回友好的提示信息) 。

    • 服务降级虽然会导致请求失败,但是不会导致阻塞,而且最多会影响这个依赖服务对应的线程池中的资源,对其它服务没有响应。

image

  • 信号量隔离

信号量,有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多 个关键代码段不被并发调用。

以一个停车场的运作为例。简单起见,假设停车场只有三个车位,一开始三个车位都是空 的。这时如果同时来了五辆车,看门人允许其中三辆直接进入,然后放下车拦,剩下的车则 必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入外面的一辆进去,如果又离开两辆,则又可以放入两辆,如此往复。

在这个停车场系统中,车位是公共资源,每辆车好比一个线程,看门人起的就是信号量的作用。

image

隔离只是对雪崩线程进行整理,避免因为一个服务出现问题导致服务雪崩,但是出现问题的服务是没有进行任何处理的。

2.2.2. 降级熔断

  • 降级

当访问服务中的一个资源,发生异常或调用超时,即时返回默认数据(错误信息) ,避免 请求线程阻塞。

image

  • 熔断

如果请求访问一个微服务的某个不正常的资源N多次,这个微服务都是在进行降级处理,那说明这个微服务短时间恢复不回来,这个时候就会触发熔断,会对这个微服务的所有的请求 (正常请求,不正常请求)全部禁止访问。等待一段时间,会对正常的请求放开访问,但是对不正常的请求继续进行降级处理。

image

2.3. 隔离降级

2.3.1. 环境搭建

  • springcloudhystrix-provider项目搭建

引入依赖

<properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>2021.0.7</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client
            </artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

application.yml

server:
  port: 8082
spring:
  application:
    name: hystrix-provider

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8081/eureka

controller

@RestController
public class ProviderController {

    @RequestMapping("/provider")
    public String test(){
        return "Hello Hystrix Consumer ";
    }

}

引导类

@SpringBootApplication
@EnableEurekaClient
public class SpringcloudDay02HystrixProviderApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringcloudDay02HystrixProviderApplication.class, args);
    }

}
  • springcloudhystrix-consumer项目搭建

引入依赖

<properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>2021.0.7</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client
            </artifactId>
        </dependency>

        <!--hystrix起步依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
            <version>2.2.10.RELEASE</version>
        </dependency>

        <!--Feign起步依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>2.2.6.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

application.yml

server:
  port: 8083

spring:
  application:
    name: hystrix-consumer

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8081/eureka

controller

@RestController
public class ConsumerController {
    //浏览器 -> consumer,使用resttemplate -> provider
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private DiscoveryClient discoveryClient; //注册中心发现客户端
    @RequestMapping("/consumer")
    public String consumer(){
        //从注册中心中获取provider的主机地址和端口
        List<ServiceInstance> instances =
                discoveryClient.getInstances("HYSTRIX-PROVIDER");//参数:客户端在eureka
        注册中心的名称
        ServiceInstance serviceInstance = instances.get(0);
        String host = serviceInstance.getHost(); //获取对应的客户端的主
        机地址
        int port = serviceInstance.getPort(); //获取对应的客户端的端口
        String url = "http://"+host+":"+port+"/provider";
        String msg = restTemplate.getForObject(url, String.class);
        return msg;
    }
}

引导类

@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
public class SpringcloudDay02HystrixConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringcloudDay02HystrixConsumerApplication.class, args);
    }

}

运行测试

image

停止springcloudhystrix-provider,重新测试,会发现,再次访问,需要等待一会

那如果有很多请求访问,都会等待,每次等待请求都会在内存中停留一会,当请求足够多 时,就会导致springcoudhystrix-consumer请求阻塞,从而出现服务雪崩问题。

2.3.2. 降级配置

  • springcoudhystrix-consumer中引入相关依赖
<!--hystrix起步依赖-->
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
	<version>2.2.10.RELEASE</version>
</dependency>
  • 引导类中开启降级
//低版本可以使用@SpringCloudApplication代替以下所有注解,但高版本已经弃用。
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient //开启discoveryclient支持
@EnableHystrix //springcloud低版本可以使用@EnableCircuitBreaker,但高版本已经
弃用了
public class SpringcloudhystrixConsumerApplication {
	public static void main(String[] args) {
	SpringApplication.run(SpringcloudhystrixConsumerApplication.class,
args);
	}
}
  • controller中完成降级逻辑编写

我们改造consumer,当目标服务的调用出现故障,我们希望快速失败,给用户一个友好提示。 因此需要提前编写好失败时的降级处理逻辑,要使用HystixCommond来完成

@RestController
public class ConsumerController {
    //浏览器 -> consumer,使用resttemplate -> provider
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private DiscoveryClient discoveryClient; //注册中心发现客户端
    @RequestMapping("/consumer")
    @HystrixCommand(fallbackMethod = "failBack") //hystrix熔断隔离降级设置,
    fallbackMethod表示降级方法
    public String consumer(){
//从注册中心中获取provider的主机地址和端口
        List<ServiceInstance> instances =
                discoveryClient.getInstances("HYSTRIX-PROVIDER");//参数:客户端在eureka注册
        中心的名称
        ServiceInstance serviceInstance = instances.get(0);
        String host = serviceInstance.getHost(); //获取对应的客户端的主机地址
        int port = serviceInstance.getPort(); //获取对应的客户端的端口
        String url = "http://"+host+":"+port+"/provider";
        String msg = restTemplate.getForObject(url, String.class);
        return msg;
    }
    //返回值类型需要和熔断隔离降级方法返回值类型一致
    public String failBack(){
        return "请求繁忙,请稍后再试";
    }
}
  • 正常测试、把提供者服务停掉之后,重新访问,会发现不会在出现原来的请求等待问题,而 是直接返回错误信息。

image

全局降级配置

@RestController
@DefaultProperties(defaultFallback = "failBack")
public class ConsumerController {
    //浏览器 -> consumer,使用resttemplate -> provider
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private DiscoveryClient discoveryClient; //注册中心发现客户端
    @RequestMapping("/consumer")
    //@HystrixCommand(fallbackMethod = "failBack") //hystrix熔断隔离降级设置,fallbackMethod表示降级方法
    @HystrixCommand

    public String consumer() {
        //从注册中心中获取provider的主机地址和端口
        List<ServiceInstance> instances =
                discoveryClient.getInstances("HYSTRIX-PROVIDER");//参数:客户端在eureka注册
        中心的名称
        ServiceInstance serviceInstance = instances.get(0);
        String host = serviceInstance.getHost(); //获取对应的客户端的主机地址
        int port = serviceInstance.getPort(); //获取对应的客户端的端口
        String url = "http://"+host+":"+port+"/provider";
        String msg = restTemplate.getForObject(url, String.class);
        return msg;
    }
    //返回值类型需要和熔断隔离降级方法返回值类型一致
    public String failBack(){
        return "请求繁忙,请稍后再试";
    }
}

2.3.3. 设置超时

  • 实验中如果请求在超过1秒后都会返回错误信息。
  • Hystix的默认超时时长为1秒。
  • 修改springcloudhystrix-consumer超时时间。
hystrix:
	command:
		default:
			execution:
				isolation:
					thread:
						timeoutInMilliseconds: 6000 # 设置hystrix的超时时间为6000ms
  • 改造springcloudhystrix-provider服务提供方的Controller接口,随机休眠一段时间,以触 发熔断。
@RestController
public class HystrixTestController {
	@RequestMapping("/consumer")
	public String test(){
		try {
				Thread.sleep(8000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			return "Hello SpringCloud Hystrix";
	}
}
  • 进行测试,这是会发现,即使springcloudhystrix-provider没有停机,也会触发降级

image

2.4. 服务熔断

2.4.1. 什么是熔断

image

2.4.2. 熔断的3个状态

  • Closed:关闭状态,所有请求都正常访问。
  • Open:打开状态,所有请求都会被降级。Hystix会对请求情况计数,当一定时间内失败请 求百分比达到阈值,则触发熔断,断路器会完全打开。默认失败比例的阈值是50%,请求次 数最少不低于20次。
  • Half Open:半开状态,open状态不是永久的,打开后会进入休眠时间(默认是5S)。随 后断路器会自动进入半开状态。此时会释放部分请求通过,若这些请求都是健康的,则会完 全关闭断路器,否则继续保持打开,再次进行休眠计时。

image

2.4.3. 熔断实现

  • 将springcloudhystrix-provider的controller中的睡眠操作去掉,并重启 springcloudhystrix-provider
@RestController
public class ProviderController {

    @RequestMapping("/provider")
    public String test(){

        //耗时操作
        /*try {
            Thread.sleep(8000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/

        return "Hello Hystrix Consumer ";
    }

}
  • 修改springcloudhystrix-consumer的controller,重启springcloudhystrix-consumer
@RestController
@DefaultProperties(defaultFallback = "failBack")
public class ConsumerController {
    //浏览器 -> consumer,使用resttemplate -> provider
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private DiscoveryClient discoveryClient; //注册中心发现客户端
    @RequestMapping("/consumer/{id}")
//@HystrixCommand(fallbackMethod = "failBack") //hystrix熔断隔离降级设
    置,fallbackMethod表示降级方法
    @HystrixCommand
    public String consumer(@PathVariable("id") Integer id){
//表示当传递的参数是1的时候,请求失败,不是1的时候请求成功
        if (id==1){
            throw new RuntimeException("访问出错了");
        }
//从注册中心中获取provider的主机地址和端口
        List<ServiceInstance> instances =
                discoveryClient.getInstances("HYSTRIX-PROVIDER");//参数:客户端在eureka注册
        中心的名称
        ServiceInstance serviceInstance = instances.get(0);
        String host = serviceInstance.getHost(); //获取对应的客户端的主机地址
        int port = serviceInstance.getPort(); //获取对应的客户端的端口
        String url = "http://"+host+":"+port+"/provider";
        String msg = restTemplate.getForObject(url, String.class);
        return msg;
    }
    //返回值类型需要和熔断隔离降级方法返回值类型一致
    public String failBack(){
        return "请求繁忙,请稍后再试";
    }
}
  • 进行测试

image

  • 为了测试方便,我们可以通过配置修改熔断策略。
hystrix:
	command:
	default:
		# 修改熔断策略
		circuitBreaker:
			requestVolumeThreshold: 5 #触发熔断的最小请求次数,默认20
			sleepWindowInMilliseconds: 10000 #休眠时长,默认是5000毫秒
			errorThresholdPercentage: 50 #触发熔断的失败请求最小占比,默认50%

3. Feign

image

  • Feign 是一个声明式的 REST 客户端,它用了基于接口的注解方式,很方便实现客户端配置。(替代restTemplate)
  • Feign 最初由 Netflix 公司提供,但不支持SpringMVC注解,后由 SpringCloud 对其封装, 支持了SpringMVC注解,让使用者更易于接受。
  • Feign使用代理的方式实现REST客户端操作
  • Feign可以替代RestTemplate完成请求操作。

3.1. 使用feign改造consumer工程

1.引入依赖

<!--Feign起步依赖-->
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-openfeign</artifactId>
	<version>2.2.6.RELEASE</version>
</dependency>

2.开启feign功能

添加@EnableFeignClients 注解

//低版本可以使用@SpringCloudApplication代替以下所有注解,但高版本已经弃用。
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient //开启discoveryclient支持
@EnableHystrix //springcloud低版本可以使用@EnableCircuitBreaker,但高版本已经
弃用了
@EnableFeignClients //开启feign客户端
public class SpringcloudhystrixConsumerApplication {
	public static void main(String[] args) {
		SpringApplication.run(SpringcloudhystrixConsumerApplication.class,
args);
	}
}

3.删除RestTemplate

  • feign已经自动集成了Ribbon负载均衡的RestTemplate。所以,此处不需要再注册 RestTemplate。
@Configuration
public class MyConfiguration {
	/*@Bean
	public RestTemplate restTemplate(){
	return new RestTemplate();
	}*/
}

4.创建Feign客户端

@FeignClient(value = "hystrix-provider") //value属性的值为服务提供方在eureka
注册中心中的注册名称
public interface HystrixFeignClent {
	//FeignClient中的方法,必须和服务提供方中要调用的方法保持一致(请求注解、请
求路径、请求参数、方法修饰符、方法返回值、方法名、方法参数要一致)
	@RequestMapping("/hystrix")
	public String test();
}

5.修改consumer的controller

@RestController
@DefaultProperties(defaultFallback = "testFailBack")//设置整个controller中的
请求降级
public class HystrixTextController {
	@Autowired
	private HystrixFeignClent hystrixFeignClent;
	@RequestMapping("/consumer/{id}")
	@HystrixCommand//降级设置
	public String test(@PathVariable("id") Integer id){
		if (id==1){
			throw new RuntimeException("访问出错了");
		}
		String s = hystrixFeignClent.test();
		return s;
	}
        //返回值类型需要和降级方法返回值类型一致
        public String testFailBack(){
        	return "请求繁忙,请稍后再试";
	}
}

6.进行测试

image

注意:工作中一般使用Feign完成服务消费方访问服务提供方操作。

3.2. Ribbon的支持(了解)

低版本中的Feign中本身已经集成了Ribbon依赖和自动配置。

image

高版本中已经不再集成Ribbon,需要单独使用。

image

3.3. Hystrix支持(了解)

3.3.1. 低版本Feign默认也有对Hystrix的集成。

image

高版本中已经不再集成Hystrix,使用时需要单独引入使用。

image

3.3.2. 低版本中使用Feign中的Hystrix进行降级操作(了解)

1.先将springcloudhystrix-consumer中的hystrix配置。

2.开启Feign中的hystrix

feign:
	hystrix:
		enabled: true # 开启Feign的熔断功能

2.编写Feign使用的降级类

@Component
public class FeignFallBack implements HystrixFeignClent{
	@Override
	public String test() {
		return "系统繁忙,请求稍后再试!";
	}
}

3.修改Feign注解

@FeignClient(value = "hystrix-provider",fallback = FeignFallBack.class)
//value属性的值为服务提供方在eureka注册中心中的注册名称
public interface HystrixFeignClent {
	//FeignClient中的方法,必须和服务提供方中要调用的方法保持一致(请求注解、请求路径、请求参数、方法修饰符、方法返回值、方法名、方法参数要一致)
	@RequestMapping("/hystrix")
	public String test();
}