SpringCloud组件介绍及其使用方法

一、负载均衡 Robbin

在这里插入图片描述

1.1 启动多个服务的提供方

将原先的服务复制一份,并启动。
在这里插入图片描述

1.2 在服务的消费方配置负载均衡算法

01.在引导类上添加负载均衡算法注解:
在这里插入图片描述02.在Controller中直接使用服务提供方名称
在这里插入图片描述03.在配置文件中修改负载均衡策略
在这里插入图片描述

二、熔断器 Hystrix

作用:用于隔离访问远程服务、第三方库,防止出现级联失败。

2.1 雪崩问题

Hystix解决雪崩问题的手段有两个:

  • 线程隔离
  • 服务熔断

线程隔离

== Hystrix为每个依赖服务调用分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队.加速失败判定时间。

服务降级:优先保证核心服务,而非核心服务不可用或弱可用。

== 用户的请求故障时,不会被阻塞,更不会无休止的等待或者看到系统崩溃,至少可以看到一个执行结果(例如返回友好的提示信息) 。
== 服务降级虽然会导致请求失败,但是不会导致阻塞,而且最多会影响这个依赖服务对应的线程池中的资源,对其它服务没有响应。

2.2 Hystrix 服务降级

2.2.1. 在服务消费者中配置Hystrix

  1. 在配置文件pom.xml中添加熔断器的依赖
        <!-- 添加熔断器依赖 Hystrix -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
  1. 在引导类中添加依赖
@SpringBootApplication
@EnableDiscoveryClient  //开启服务注册中心 Eureka
@EnableCircuitBreaker   //开启熔断器服务 Hystrix
public class ConsumerApplication {
    /* 使用RestTemplate做远程调用方法 */
    @Bean
    @LoadBalanced  //开启负载均衡
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
        System.out.println("调用方启动成功!!");
    }
}
  1. 在对应的控制层添加熔断方法并进行绑定
    在这里插入图片描述4. 测试
    先将服务提供方,服务消费者停止;
    再重启服务消费者。
    在这里插入图片描述

2.2.2 如果没个方法都写一个熔断很麻烦,如何写全局的熔断?

在类上添加注解@DefaultProperties指定一个全局的熔断方法
在这里插入图片描述
在这里插入图片描述

Hystric服务降级小结:

  1. 引入Hystrix依赖
  2. 修改配置文件(这里没做修改)
  3. 在引导类上添加注解@EnableCircuitBreaker
  4. 定义熔断方法
    4.1 局部熔断方法:要和被熔断的方法返回值列表一致
    4.2 全局熔断方法:返回值类型要和被熔断的方法一致
  5. 声明被熔断的方法@HystrixCommand(fallbackMethod="局部熔断方法名")
  6. 在类上添加注解@DefaultProperties(defaultFallback = "全局熔断方法名")

2.2.3 设置熔断超时

在之前的案例中,请求在超过1秒后都会返回错误信息,这是因为Hystix的默认超时时长为1,我们可以通过在服务调用方的配置文件中配置修改这个值:

我们可以通过hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds来设置Hystrix超时时间。该配置没有提示。

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 6000 # 设置hystrix的超时时间为6000ms

2.3 Hystrix 服务熔断

熔断状态机3个状态:

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

三、声明式调用 Feign

为什么叫伪装?

Feign可以把Rest的请求进行隐藏,伪装成类似SpringMVC的Controller一样。你不用再自己拼接url,拼接参数等等操作,一切都交给Feign去做。

3.1. 改造服务消费者

  1. 添加 Feign 依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  1. 在引导类上添加注解
//@SpringBootApplication
//@EnableDiscoveryClient  //开启服务注册中心 Eureka
//@EnableCircuitBreaker   //开启熔断器服务 Hystrix
@SpringCloudApplication   //组合注解,相当于 @SpringBootApplication、@EnableDiscoveryClient、@EnableCircuitBreaker
@EnableFeignClients // 开启feign客户端
public class ConsumerApplication {

    /* 使用RestTemplate做远程调用方法 */
/*
    @Bean
    @LoadBalanced  //开启负载均衡
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
*/
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
        System.out.println("调用方启动成功!!");
    }
}
  1. 新建 Feign 接口
    Feign 接口习惯以 client 结尾,并声明是一个Feign 接口,指定对应的微服务名称。声明被调用的方法(全局路径需要在每个类上添加)。
@FeignClient("service-provider")  //声明这是一个Feign接口,并指明微服务的名称
public interface BookClient {
    @GetMapping("/book/{id}")
    public String queryBookById(@RequestParam("id") int id);
}
  1. 修改 Controller 类
@Controller
@RequestMapping("consumer/book")
//@DefaultProperties(defaultFallback = "hystrixMethod")  //定义全局的熔断方法
public class BookController {    
    @Autowired
    private BookClient bookClient;
    
    public String queryBookById(@RequestParam("id") int id){
        return this.bookClient.queryBookById(id);
    }
}
  1. 访问
    在这里插入图片描述

3.2 声明式调用如何集成熔断器?

  1. feign 中集成了熔断 Robbin ,但是默认是关闭的,需要自己进行手动开启。
feign:
  hystrix:
    enabled: true # 开启Feign的熔断功能
  1. 需要在 Client 中定义方法,需要熔断哪个方法就实现哪个方法。
@Component
public class BookClientHystrix implements BookClient{
    @Override
    public String queryBookById(int id) {
        return "服务器正忙,请稍后再试!!!";
    }
}
  1. 对 Feign 接口添加熔断类
@FeignClient(value = "service-provider", fallback = BookClientHystrix.class)  //声明这是一个Feign接口,并指明微服务的名称
public interface BookClient {
    @GetMapping("/book/{id}")
    public String queryBookById(@RequestParam("id") int id);
}

四、网关路由 Zuul(Zuul的四种路由)

Zuul为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。

路由:分发给不同的微服务(服务名)
负载均衡:同一个微服务,不同的实体类

4.1 快速入门(通过路径进行访问)

  1. 构建SpringCloud项目
    在这里插入图片描述在这里插入图片描述
  2. 修改 zuul 网关的配置文件 application.yml
server:
  port: 10000

spring:
  application:
    name: xxacker-zuul

zuul:
  routes:
    service-provider:   #路由名称,可以随便写,习惯上写服务名
      path: /service-provider/**    #此处为路由到服务的提供方,也可以路由到服务的消费方
      url: http://localhost:8190    #服务提供方的地址
  1. 为引导类添加注解
@SpringBootApplication
@EnableZuulProxy  //启用zuul网关组件
public class XxackerZuulApplication {
    public static void main(String[] args) {
        SpringApplication.run(XxackerZuulApplication.class, args);
        System.out.println("Zuul启动成功!!!");
    }
}
  1. 启动zuul网关项目,并进行访问
    在这里插入图片描述

4.2 面向服务的路由(通过服务Id进行访问)

在原先路由规则中,路由的路径对应的服务地址写死了!如果同一服务有多个实例的话,这样做显然就不合理了。应该根据服务的名称,去Eureka注册中心查找服务对应的所有实例列表,然后进行动态路由
对 xxackerZuul 服务进行优化:

  1. 添加 Eureka 的客户端依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
  1. 添加 Eureka配置,修改路由的方式,获取服务信息
zuul:
  routes:
    service-provider:   #路由名称,可以随便写,习惯上写服务名
      path: /service-provider/**    #此处为路由到服务的提供方,也可以路由到服务的消费方
      #url: http://localhost:8190    #服务提供方的地址
      serviceId: service-provider    #服务名
      
eureka:
  client:
    registry-fetch-interval-seconds: 5 # 获取服务列表的周期:5s
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
  1. 开启Eureka客户端发现功能
@SpringBootApplication
@EnableZuulProxy  //启用zuul网关组件
@EnableDiscoveryClient  //启用 Eureka 的客户端
public class XxackerZuulApplication {
    public static void main(String[] args) {
        SpringApplication.run(XxackerZuulApplication.class, args);
        System.out.println("Zuul启动成功!!!");
    }
}
  1. 访问
    在这里插入图片描述

4.3 简化路由配置

zuul:
  routes:
    service-provider: /service-provider/**  #路由名称,可以随便写,习惯上写服务名
      #path: /service-provider/**    #此处为路由到服务的提供方,也可以路由到服务的消费方
      #url: http://localhost:8190    #服务提供方的地址
      #serviceId: service-provider    #服务名

4.4 默认的路由规则

在使用Zuul的过程中,上面讲述的规则已经大大的简化了配置项。但是当服务较多时,配置也是比较繁琐的。因此Zuul就指定了默认的路由规则:

  • 默认情况下,一切服务的映射路径就是服务名本身。例如服务名为:service-provider,则默认的映射路径就 是:/service-provider/**

也就是说,刚才的映射规则我们完全不配置也是OK的,不信就试试看。

添加路由前缀:

zuul:
  routes:
    service-provider: /service-provider/**
    service-consumer: /service-consumer/**
  prefix: /api # 添加路由前缀

4.5 Zuul过滤器

Zuul作为网关的其中一个重要功能,就是实现请求的鉴权。而这个动作我们往往是通过Zuul提供的过滤器来实现的。

4.5.1 ZuulFilter

ZuulFilter是过滤器的顶级父类。其中定义的4个最重要的方法:

public abstract ZuulFilter implements IZuulFilter{

    abstract public String filterType();

    abstract public int filterOrder();
    
    boolean shouldFilter();// 来自IZuulFilter

    Object run() throws ZuulException;// IZuulFilter
}
  • shouldFilter:返回一个Boolean值,判断该过滤器是否需要执行。返回true执行,返回false不执行。
  • run:过滤器的具体业务逻辑。
  • filterType:返回字符串,代表过滤器的类型。包含以下4种:
    • pre:请求在被路由之前执行
    • route:在路由请求时调用
    • post:在route和errror过滤器之后调用
    • error:处理请求时发生错误调用
  • filterOrder:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。

4.5.2 自定义过滤器

模拟一个登录的校验。基本逻辑:如果请求中有access-token参数,则认为请求有效,放行。

  1. 定义过滤器类
    在这里插入图片描述
  2. 内容
@Component
public class LoginFilter extends ZuulFilter {
    /*过滤器的类型: pre route post error*/
    @Override
    public String filterType() {
        return "pre";
    }

    /*执行顺序:返回值越小,优先级越高*/
    @Override
    public int filterOrder() {
        return 5;
    }

    /*是否执行该过滤器:true:执行run;false:不执行run*/
    @Override
    public boolean shouldFilter() {
        return true;
    }

    /*编写过滤器的业务逻辑*/
    @Override
    public Object run() throws ZuulException {
        //初始化context上下文对象,servlet spring
        RequestContext context = RequestContext.getCurrentContext();

        //获取request对象
        HttpServletRequest request = context.getRequest();

        //获取参数
        String token = request.getParameter("token");

        if(StringUtils.isBlank(token)){
            //拦截
            context.setSendZuulResponse(false);  //是否转发请求,false:表示不转发请求
            context.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED);  //401-身份未认证 设置相应状态码
            context.setResponseBody("Request Error ! ! !");  //设置相应的提示
        }
        // 校验通过,把登陆信息放入上下文信息,继续向后执行
        context.set("token", token);
        /*返回null,表示过滤器什么都不做*/
        return null;
    }
}
  1. 访问
    在这里插入图片描述
发布了92 篇原创文章 · 获赞 49 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/Xxacker/article/details/104392806