欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring Cloud

程序员文章站 2022-05-06 08:00:31
...
  • Spring Cloud简介
  • Eureka服务注册
  • Feign负载均衡
  • Hystrix熔断
  • Zuul网关
  • Config分布式配置
  • Bus消息总线

Spring Cloud简介

Spring Cloud 和Spring Boot的关系?

1、Spring boot 是 Spring 的一套快速配置脚手架,可以基于spring boot 快速开发单个微服务;Spring Cloud是一个基于Spring Boot实现的云应用开发工具;
2、Spring boot专注于快速、方便集成的单个个体,Spring Cloud是关注全局的服务治理框架;
3、Spring boot使用了默认大于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置,Spring Cloud很大的一部分是基于Spring boot来实现。
4、Spring boot可以离开Spring Cloud独立使用开发项目,但是Spring Cloud离不开Spring boot,属于依赖的关系。

SpringCloud主要框架

  • 服务发现——Netflix Eureka
  • 服务调用——Netflix Feign
  • 熔断器——Netflix Hystrix
  • 服务网关——Netflix Zuul
  • 分布式配置——Spring Cloud Config
  • 消息总线 —— Spring Cloud Bus

注意:以下知识基于SpringCloud的Finchley.M9版本 对应SpringBoot版本2.0.x

SpringCloud与Dubbo的对比

- Dubbo Spring Cloud
服务注册中心 Zookeeper SpringCloud Netflix Eureka
服务调用方式 RPC REST API
服务网关 SpringCloud Netflix Zuul
熔断器 不完善 SpringCloud Netflix Hystrix
分布式配置 SpringCloud Config
服务跟踪 SpringCloud Sleuth
消息总线 SpringCloud Bus
数据流 SpringCloud Stream
批量任务 SpringCloud Task

RPC底层是二进制 效率优于REST API

Eureka服务发现注册

Eureka是Netflix开发的服务发现框架,SpringCloud将它集成在自己的子项目spring-cloud-netflix中,实现SpringCloud的服务发现功能。Eureka包含两个组件:Eureka Server和Eureka Client。
Eureka Server提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。
Eureka Client是一个java客户端,用于简化与Eureka Server的交互,客户端同时也就别一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳,默认周期为30秒,如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)。
Eureka Server之间通过复制的方式完成数据的同步(常见于高可用注册中心即注册中心集群),Eureka还提供了客户端缓存机制,即使所有的Eureka Server都挂掉,客户端依然可以利用缓存中的信息消费其他服务的API。
综上,Eureka通过*心跳检查、*客户端缓存等机制,确保了系统的高可用性、灵活性和可伸缩性。

创建服务中心Eureka

  1. 项目中微服务都要注册到eureka服务中心,故而在父工程中引入依赖
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring‐cloud‐dependencies</artifactId>
            <version>Finchley.M9</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

  1. 在子模块项目中添加eureka服务端依赖
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
</dependencies>

  1. 启动类添加启动服务注册中心注解@EnableEurekaServer
@SpringBootApplication
@EnableEurekaServer //开启Eureka Server
public class EurekaApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class, args);
    }
}

  1. 通过url直接访问注册中心管理界面http://localhost:6868/

微服务注册

  1. 项目中的微服务注册到eureka服务中心,修改application.yml配置文件
    eureka:
        client:
            service-url:
                defaultZone: http://localhost:6868/eureka #Eureka注册中心地址
    
  2. 启动类需要添加eureka客户端注解@EnableEurekaClient
    @SpringBootApplication
    @EnableCaching
    @EnableEurekaClient
    public class BaseApplication{
        
    }
    

Feign声明式服务调用

整合了Ribbon和Hystrix,且还提供了一种声明式的Web服务客户端定义方式

Feign实现服务间调用

  1. 依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    <version>2.0.0.M2</version>
</dependency>

  1. 启动类上使用注解@EnableFeignClients
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
  1. 创建伪装接口com.tensquare.qa.clients.LabelClient
@FeignClient("tensquare-base")//指定调用微服务的名称  接口的所有抽象类会与此微服务项目的controller下的方法进行映射  基于RESTful API
public interface LabelClient{
    @GetMapping("label/{labelId}")//用于被调用的微服务进行url地址映射。注意@PathVariable注解一定要指定参数名称,否则出错
    public Result findById(@PathVariable("labelId") String labelId);
}
//此抽象方法映射tensquare-base微服务下的get请求路径为label/{labelId}控制器方法
  1. 在Controller层中调用
@Autowired
private LabelClient labelClient;

@GetMapping("/label/{id}")
public Result findLabeByid(@PathVariable String id) {
    return new Result(true, StatusCode.OK, "查询成功", labelClient.findById(id));
}

注意:Feign提供负载均衡的功能因为Feign封装了Ribbon,自动提供负载均衡的功能,轮询调用服务。

Hystrix熔断

熔断器的好处

在微服务架构中通常会有多个服务层调用,基础服务的故障可能会导致级联故障,进而造成整个系统不可用的情况,这种现象被称为服务雪崩效应。
服务雪崩效应是一种因“服务提供者”的不可用导致“服务消费者”的不可用,并将不可用逐渐放大的过程。

正常

graph LR
C-->B;
D-->B;
B-->A;

A不可用

graph LR
C-->B;
D-->B;
B-->A故障;

B不可用

graph LR
C-->B故障;
D-->B故障;
B故障-->A故障;

系统不可用

graph LR
C故障-->B故障;
D故障-->B故障;
B故障-->A故障;

Hystrix介绍

Hystrix对应的中文名字是“豪猪”,豪猪周身长满了刺,能保护自己不受天敌的伤害,代表了一种防御机制,这与hystrix本身的功能不谋而合,因此Netflix团队将该框架命名为Hystrix;
在一个分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,如何能够保证在一个依赖出问题的情况下,不会导致整体服务失败,这个就是Hystrix需要做的事情。
Hystrix提供了熔断功能,能够在一个、或多个依赖同时出现问题时保证系统依然可用。
熔断时间窗中会周期性的调用熔断微服务处于半开状态,若调用成功则返回正常状态关闭熔断,失败则继续开启熔断器

Hystrix入门

  1. 配置文件开启Hystrix支持(Feign本身支持Hystrix引入Feign依赖即可)
feign.hystrix.enabled=true
  1. com.tensquare.qa.clients.impl.LabelClientImpl类实现LabelClient接口
@Component
public class LabelClientImpl implements LabelClient{
    @Override
    public Result findById(String labelId){
        return new Result(false,StatusCode.ERROR,"熔断器启动,服务暂不可用");
    }
}
  1. 修改LabelClient的注解
@FeignClient(value="tensquare-base", fallback="LabelClientImpl.class")//fallback指定哪个熔断器处理
public interface LabelClient{
    @GetMapping("label/{labelId}")//用于被调用的微服务进行url地址映射。注意@PathVariable注解一定要指定参数名称,否则出错
    public Result findById(@PathVariable("labelId") String labelId);
}

Zuul网关

为什么需要微服务网关

不同的微服务一般有不同的网络地址,而外部的客户端可能需要调用多个服务的接口才能完成一个业务需求。比如一个电影购票的收集APP,可能回调用电影分类微服务,用户微服务,支付微服务等。
如果客户端直接和微服务进行通信,会存在一下问题:
# 客户端会多次请求不同微服务,增加客户端的复杂性
# 存在跨域请求,在一定场景下处理相对复杂
# 认证复杂,每一个服务都需要独立认证
# 难以重构,随着项目的迭代,可能需要重新划分微服务,如果客户端直接和微服务通信,那么重构会难以实施
# 某些微服务可能使用了其他协议,直接访问有一定困难
上述问题,都可以借助微服务网关解决。微服务网关是介于客户端和服务器端之间的中间层,所有的外部请求都会先经过微服务网关。

什么是Zuul

请求转发、过滤器

Zuul是Netflix开源的微服务网关,他可以和Eureka,Ribbon,Hystrix等组件配合使用。Zuul组件的核心是一系列的过滤器,这些过滤器可以完成以下功能:
# 身份认证和安全: 识别每一个资源的验证要求,并拒绝那些不符的请求
# 审查与监控:
## 动态路由:动态将请求路由到不同后端集群
# 压力测试:逐渐增加指向集群的流量,以了解性能
# 负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求
# 静态响应处理:边缘位置进行响应,避免转发到内部集群
# 多区域弹性:跨域AWS Region进行请求路由,旨在实现ELB(ElasticLoad Balancing)使用多样化
Spring Cloud对Zuul进行了整合和增强。使用Zuul后,架构图演变为以下形式

Zuul路由转发

  1. 创建微服务网关
  2. 导入依赖eureka和zuul
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        <version>2.0.0.M8</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        <version>2.0.0.M8</version>
    </dependency>
  1. 微服务网关注册到eureka
server:
  port: 9012
spring:
  application:
    name: tensquare-web
eureka:
  client:
    service-url:
      defaultZone: http://localhost:6868/eureka
  1. 请求转发(配置路由规则)
zuul:
  routes:
    tensquare-base: #该名称可以任意
      path: /base/** #请求路径 采用通配符
      serviceId: tensquare-base #微服务名称
    tensquare-friend:
      path: /friend/**
      serviceId: tensquare-friend
  1. 启动类添加注解@EnableZuulProxy //使用该注解 开启zuul代理 可以省略@EnableEurekaClient

Zuul过滤器

  1. 创建过滤器继承ZuulFilter
@Component
public class ManagerZuulFilter extends ZuulFilter{
    /**
     * //过滤器类型
     * @return pre : 可以在请求被路由之前调用
     *         route :在路由请求时候被调用
     *         post:在route和error过滤器之后被调用
               error:路由请求发生异常时调用
     */
    public String filterType() {
        return "pre";
    }
    /**
     * 过滤器执行顺序,如果有多个过滤器 数值越大 ,执行顺序靠后
     * @return
     */
    public int filterOrder() {
        return 0; // 优先级为0,数字越大,优先级越低
    }

    public boolean shouldFilter() {
        return true; //是否执行该过滤器.如果设置为false发送任何请求并不会经过过滤器
    }

    /**
     * 过滤器的处理逻辑,可以将转发token的工作在此方法实现
     * @throws ZuulException
     */
    public Object run() throws ZuulException {
        System.out.println("经过了前台系统的zuul过滤器");
        return null;
    }
}
  1. 启动项目,发出请求发现过滤器中run方法执行,但请求转发到指定微服务后,请求头信息丢失
解决方法:在run方法中获取request对象并将头信息写入zuul过滤的请求中
@Override
public Object run() throws ZuulException {
    System.out.println("经过了前台系统的zuul过滤器");
    RequestContext requestContext = RequestContext.getCurrentContext();//可以获得request,response
    HttpServletRequest request =  requestContext.getRequest();
    String header = request.getHeader("header");
    if(header!=null){
        requestContext.addZuulRequestHeader("header", header);
    }
    return null;
}

  1. 处理非法请求
...
RequestContext requestContext = RequestContext.getCurrentContext();
requestContext.setSendZuulResponse(false);  //令zuul过滤该请求,不对其进行路由即停止转发
requestContext.setResponseStatusCode(401);  //返回错误码
requestContext.getResponse().setContentType("text/html;charset=utf-8");//指定返回数据的格式 即返回json数据还是普通文本text
requestContext.setResponseBody("无权访问");//响应的数据

Config分布式配置中心

概念

在Spring Cloud中,有分布式配置中心组件spring cloudconfig ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。在spring cloud config 组件中,分两个角色,一是config server,二是config
client。
Config Server是一个可横向扩展、集中式的配置服务器,它用于集中管理应用程序各个环境下的配置,默认使用Git存储配置文件内容,也可以使用SVN存储,或者是本地文件存储。
Config Client是Config Server的客户端,用于操作存储在Config Server中的配置内容。微服务在启动时会请求Config Server获取配置文件的内容,请求到后再启动容器。

***步骤***:

  1. 创建git私有仓库
  2. 上传配置文件到git服务器
上传文件命名规则:{application}-{profile}.yml或{application}-{profile}.properties
application为应用名称 profile指的开发环境(用于区分开发环境,测试环境、生产环境等)
例如:base-dev  基础微服务开发环境配置文件
  1. 配置文件中心微服务
1引入依赖
 <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    <version>2.0.0.M8</version>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId>
    <version>2.0.0.M9</version>
</dependency>
2创建配置文件
spring:
  application:
    name: tensquare-config
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/lvhonlgong/tensquare_config.git  #git服务器地址
server:
  port: 12000
eureka:  #注册服务中心
  client:
    service-url:
      defaultZone: http://localhost:6868/eureka
3创建启动类
@SpringBootApplication
@EnableConfigServer  //开启配置服务!!
public class ConfigApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigApplication.class);
    }
}

  1. 配置微服务项目(客户端)
1新增依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
    <version>2.0.0.M9</version>
</dependency>
2在resources目录下新增文件boostrap.yml,删除application.yml(此文件已经上传到git仓库,动态获取)
spring:
  cloud:
    config:
      name: base    #application的名称
      profile: dev  #profile的名称
      label: master #选择分支
      discovery:  #服务发现暴露地址
        enabled: true
        service-id: tensquare-config #连接配置中心
eureka:
  client:
    service-url:
      defaultZone: http://localhost:6868/eureka

Bus消息总线

使用SpringCloudBus来实现配置的自动更新

  1. 配置服务端(基于rabbitMQ)
1新增依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-bus</artifactId>
    <version>2.0.0.M7</version>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
    <version>2.0.0.RC3</version>
</dependency>
2修改配置文件新增
management:
  endpoints:
    web:
      exposure:
        include: bus-refresh  #暴露触发消息总线的地址,刷新项目配置文件 以后更新的url必须以/bus-refresh结尾,自定义
  1. 配置客户端
1增加依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-bus</artifactId>
    <version>2.0.0.M7</version>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
    <version>2.0.0.RC3</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  1. 发送POST请求更新配置文件
http://127.0.0.1:12000/actuator/bus-refresh
配置中心地址/actuator/暴露触发消息总线的地址

注意:若在后端代码需要读取配置文件的属性需要添加注解@RefreshScope用于刷新配置(动态配置属性的获取)

@RestController
@RequestMapping("/test")
@RefreshScope  //用于刷新配置
public class TestController {

    @Value("${sms.ip}")
    private String ip;

    @GetMapping("/ip")
    public String getIp(){
        return ip;
    }
}
如*问/test/ip动态获取配置属性