从零开始-系统架构之SpringCloud搭建全教程

上一篇写了一个单据项目,springboot-vue的简单前后端项目,本次介绍当前主流的微服务架构springcloud项目搭建

springboot-vue项目链接

https://blog.csdn.net/qq_44899022/article/details/131638136


一、服务治理-注册中心Eureka搭建

        在没有进行服务治理前,服务之间的通信是通过服务间直接相互调用来实现的。微服务系统中服务众多,这样会导致服务间的相互调用非常不便,因为要记住提供服务的IP地址、名称、端口号等。这时就需要中间代理,通过中间代理来完成调用。

        在架构选型的时候,我们需要注意一下切记不能为了新而新,忽略了对于当前业务的支持,虽然Eureka2.0不开源了,但是谁知道以后会不会变化,而且1.0也是可以正常使用的,也有一些贡献者在维护这个项目,所以我们不必要过多的担心这个问题,要针对于业务看下该技术框架是否支持在做考虑。

Spring Cloud Eureka 是Netflix 开发的注册发现组件,本身是一个基于 REST 的服务。提供注册与发现,同时还提供了负载均衡、故障转移等能力。

注册中心有eureka,zookeeper等等,本项目选用eureka。

区别:

Zookeeper:CP架构,有选举机制,搭建集群后,当leader挂掉,会从follower选举一个作为新的leader,但选举时间过长,30s~120s之间

Eureka:AP架构,优先保证服务的可用性

注意:

  • Eureka Server:服务器端。它提供服务的注册和发现功能,即实现服务的治理。
  • Service Provider:服务提供者。它将自身服务注册到Eureka Server中,以便“服务消费者”能够通过服务器端提供的服务清单(注册服务列表)来调用它。
  • Service Consumer:服务消费者。它从 Eureka 获取“已注册的服务列表”,从而消费服务。

1.微服务聚合工程-父工程创建

1.1 maven创建项目

idea中直接new,新建,选择maven,然后直接next

 修改命名和项目存储空间,然后finish

1.2 修改字符集

左上角file->settings->file encodings  修改成utf-8

 1.3 注解生效激活

勾选上

 1.4 修改maven编译版本

我本地是8,改成8,根据自身环境修改

 1.5 统一jar包管理

将下列代码粘到pom中,注意,GroupId需要和自己项目一致

springboot在创建springcloud时候,需要对应版本,在官网中,我们选择GA版本的springcloud,然后查看对应的springboot,我们选择2021.0.8,对应的springboot是2.6.15

<?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>


    <groupId>org.Architecture</groupId>
    <artifactId>cloud</artifactId>
    <version>1.0-SNAPSHOT</version>


    <!-- 统一管理jar包版本 -->
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <spring-cloud.version>2021.0.8</spring-cloud.version>
        <spring-boot.version>2.6.15</spring-boot.version>
    </properties>


    <!-- 子模块继承之后,提供作用:锁定版本+子modlue不用写groupId和version  -->
    <dependencyManagement>
        <dependencies>
            <!--spring boot 2.6.15-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-parent</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--spring cloud 2021.0.8-->
            <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>


</project>

1.6 IDEA开启Dashboard

微服务中组件很多,我们要启动很多项服务,所以配置dashboard后,我们会直观的看到具体哪些服务启动

在idea中找到.idea配置文件,然后点开workspace.xml配置文件

 加入下列配置,然后重启idea

 <component name="RunDashboard">
 <option name="ruleStates">
  <list>
   <RuleState>
    <option name="name" value="ConfigurationTypeDashboardGroupingRule" />
   </RuleState>
   <RuleState>
    <option name="name" value="StatusDashboardGroupingRule" />
   </RuleState>
  </list>
 </option>
 <option name="configurationTypes">
 <set>
  <option value="SpringBootApplicationConfigurationType" />
 </set>
</option>
</component>

2.Eureka注册中心

1.注册服务发现_单机eureka注册中心搭建

idea中右键单击项目,创建模块,同样是maven项目

 命名cloud-eureka-server7001   7001是我们的端口号

 现在我们的目录是这样

 然后在eureka文件夹中,修改pom文件,加入下列代码,引入eureka依赖和lombok插件

    <dependencies>
        <!--  服务注册发现Eureka-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

在eureka中,创建文件夹com.eurekaServer,同时在resources下创建application.yml文件,com.eurekaserver文件夹中创建启动类EurekaServerMain7001.java

 编写启动类EurekaServerMain7001.java文件

@SpringBootApplication
@Slf4j
@EnableEurekaServer
public class EurekaServerMain7001 {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerMain7001.class,args);
        log.info("********Eureka服务 7001 启动成功**********");
    }
}

 写resources下application.yml文件

server:
  port: 7001
eureka:
  instance:
  # eureka服务端的实例名字
   hostname: localhost
  client:
  # 表示是否将自己注册到Eureka Server
   register-with-eureka: false
  # 表示是否从Eureka Server获取注册的服务信息
   fetch-registry: false
  # 设置与 Eureka server交互的地址查询服务和注册服务都需要依赖这个地址
   service-url:
    defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

 然后启动主启动类,浏览器搜索localhost:7001,成功后如图

 

 2.注册服务发现_创建服务提供者

 新建一个模块,我们创建服务的提供者

 提供者中pom引入相关依赖

    <dependencies>
        <!--  引入Eureka 客户端依赖  -->
        <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-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
        </dependency>
    </dependencies>

 创建com.payment文件夹,随后在文件夹下创建payment8001主启动类paymentMain8001.class,

在resourse下创建application.yml文件

 主启动类paymentMain8001.class写入

@EnableEurekaClient
@SpringBootApplication
@Slf4j
public class PaymentMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain8001.class,args);
        log.info("********** 服务提供者启动成功 ***********");
    }
}

提供者的application.yml写入,我们需要指定eureka的server服务地址,在defaultZone指定

server:
  port: 8001
eureka:
  client:
    service-url:
      # Eureka server 地址
      defaultZone: http://localhost:7001/eureka/

随后我们启动服务

我们首先要启动7001的注册中心服务,然后才能启动提供者8001服务

启动成功如下,我们去浏览器验证localhost:7001

如下,我们就启动成功了

 但是我们现在没有应用名字,在提供者中yml继续写入

spring:
  application:
    # 设置应用名词
    name: cloud-payment-provider

 再次打开浏览器,localhost:7001,我们能看到刚刚修改的名字出现在这,说明提供者启动成功了!!!

 3.注册中心前端页面解读

  • Environment: 环境,默认为test,该参数在实际使用过程中,可以不用更改
  • Data center: 数据中心,使用的是默认的是 “MyOwn”
  • Current time:当前的系统时间
  • Uptime:已经运行了多少时间
  • Lease expiration enabled:是否启用租约过期 ,自我保护机制关闭时,该值默认是true, 自我保护机制开启之后为false。
  • Renews threshold: 每分钟最少续约数,Eureka Server 期望每分钟收到客户端实例续约的总数。
  • Renews (last min): 最后一分钟的续约数量(不含当前,1分钟更新一次),Eureka Server 最后 1 分钟收到客户端实例续约的总数

DS Replicas

显示eureka相邻的节点

Instances currently registered with Eureka

  • Application:服务名称。配置的spring.application.name属性
  • AMIs:n/a (1),字符串n/a+实例的数量,我不了解
  • Availability Zones:实例的数量
  • Status:实例的状态 + eureka.instance.instance‐id的值。

实例的状态分为UP、DOWN、STARTING、OUT_OF_SERVICE、UNKNOWN.

  • UP: 服务正常运行,特殊情况当进入自我保护模式,所有的服务依然是UP状态,所以需要做好熔断重试等容错机制应对灾难性网络出错情况
  • OUT_OF_SERVICE : 不再提供服务,其他的Eureka Client将调用不到该服务,一般有人为的调用接口设置的,如:强制下线。
  • UNKNOWN: 未知状态
  • STARTING : 表示服务正在启动中
  • DOWN: 表示服务已经宕机,无法继续提供服务

General Info

  • total-avail-memory : 总共可用的内存
  • environment : 环境名称,默认test
  • num-of-cpus : CPU的个数
  • current-memory-usage : 当前已经使用内存的百分比
  • server-uptime : 服务启动时间
  • registered-replicas : 相邻集群复制节点
  • unavailable-replicas :不可用的集群复制节点,如何确定不可用? 主要是server1 向 server2和server3发送接口查询自身的注册信息。
  • available-replicas :可用的相邻集群复制节点

Instance Info

  • ipAddr:eureka服务端IP
  • status:eureka服务端状态

4.注册服务发现_创建服务消费者

新建一个模块,老样子,还是maven

命名cloud-comsumer-order80 

生产者也需要注册到eureka注册中心中,pom中导入依赖,也需要client

<dependencies>
    <!--  引入Eureka client依赖  -->
    <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-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.22</version>
    </dependency>
  </dependencies>

消费中java文件夹下创建文件夹com.consumer,然后在新创建的order文件夹下创建主启动类OrderMain.class,同时,在resources下创建application.yml文件

生产者和消费者创建过程很相似!

 OrderMain80编写启动类

/**
 * 主启动类
 */
@SpringBootApplication
@EnableEurekaClient
@Slf4j
public class OrderMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain80.class,args);
        log.info("*************** 订单服务消费者启动成功 ***********");
    }
}

写yml文件

server:
  port: 80
eureka:
  client:
    service-url:
      # Eureka server 地址
      defaultZone: http://localhost:7001/eureka/

spring:
  application:
    # 设置应用名字
    name: cloud-order-consumer

消费者完成了,我们打开浏览器localhost:7001试一下

OK!消费者完成了

5.服务注册发现_actuator微服务信息完善

SpringCloud体系里的,服务实体向eureka注册时,注册名默认是IP名:应用名:应用端口名,我们修改一下eureka的实例名

生产者和消费者的pom中添加依赖

<!-- actuator监控信息完善 -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

同时,在生产者和消费者的property.yml中加入如下配置

  instance:
    #根据需要自己起名字
    instance-id: cloud-payment-provider-8001

生产者是8001,消费者是80,我们从下面的name中名字粘过来,然后后面加端口号

 然后我们重启两个服务,在eureka中能看到如下信息

 6.服务注册发现_服务发现Discovery

服务发现,就是新注册的这个服务模块能够及时的被其他调用者发现。不管是服务新增和服务删减都能实现自动发现。

在微服务时代,我们所有的服务都被劲量拆分成最小的粒度,原先所有的服务都在混在1个server里,现在就被按照功能或者对象拆分成N个服务模块,这样做的好处是深度解耦,1个模块只负责自己的事情就好,能够实现快速的迭代更新。坏处就是服务的管理和控制变得异常的复杂和繁琐,人工维护难度变大。还有排查问题和性能变差(服务调用时的网络开销)

各个微服务相互独立,每个微服务,由多台机器或者单机器不同的实例组成,各个微服务之间错综复杂的相互关联调用

首先我们在消费者中建立一个controller文件夹,然后,新建一个OrderController

 ordercontroller中代码如下

@RestController
@Slf4j
@RequestMapping("order")
public class OrderController {

    @Autowired
    //服务发现
    private DiscoveryClient discoveryClient;

    /**
     * 服务发现获取列表
     * @return
     */
    @GetMapping("discovery")
    public Object discovery(){
        // 获取所有微服务信息
        List<String> services = discoveryClient.getServices();
        for (String service : services) {
            log.info("server:={}",service);
        }
        return this.discoveryClient;
    }
}

然后我们重启服务,打开浏览器搜索localhost/order/discovery

浏览器展示如下

 7.引入RestTemplate

RestTemplate 是从 Spring3.0 开始支持的一个 HTTP 请求工具,它提供了常见的REST请求方案的模版,例如 GET 请求、POST 请求、PUT 请求、DELETE 请求以及一些通用的请求执行方法 exchange 以及 execute。

我们在消费者下创建包config,然后创建类RestTemplateConfig

作用是将RestTemplate放入到spring的Ioc容器中

RestTemplate代码如下,@LoadBalanced是加入负载均衡的功能

@Configuration
public class RestTemplateConfig {

    @LoadBalanced
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

在payment8001工程下创建PaymentController

编写order80工程Controller

@RestController
@RequestMapping("/order")
public class OrderController {


  //  HTTP 请求工具
  @Autowired
  private RestTemplate restTemplate;


  /**
   * 测试服务发现接口
   * @return
   */
  @GetMapping("/index")
  public String index(){
    //1.远程调用方法的主机
    //Stringhost="http://localhost:1000";
    //将远程微服务调用地址从"IP地址+端口号改成"微服务名称""
    String host = "http://cloud-payment-provider";
    // 2. 远程调用方法具体URL地址
    String url = "/payment/index";
    // 3. 发起远程调用
    //getForObject:返回响应体中数据转化成的对象,可以理解为json
    //getForEntity:返回的是ResponseEntity的对象包含了一些重要的信息
    String forObject = restTemplate.getForObject(host + url, String.class);
    return forObject;
   }
}

浏览器打开http://localhost/order/index

OK! 

8.Eureka高可用注册中心搭建

在微服务架构这样的分布式环境中,我们需要充分考虑发生故障的情况,所以在生产环境中必须对各个组件进行高可用部署,对于微服务如此,对于服务注册中心也一样。

Spring-Cloud为基础的微服务架构,所有的微服务都需要注册到注册中心,如果这个注册中心阻塞或者崩了,那么整个系统都无法继续正常提供服务,所以,这里就需要对注册中心搭建,高可用(HA)集群。

Eureka Server的设计一开始就考虑了高可用问题,在Eureka的服务治理设计中,所有节点即是服务提供方,也是服务消费方,服务注册中心也不例外。是否还记得在单节点的配置中,我们设置过下面这两个参数,让服务注册中心不注册自己:

eureka.client.register-with-eureka-false
eureka.client.fetch-registry-false

好,我们开始搭建eureka集群

首先在父项目下建一个模块,同理于创建7001端口eureka

 然后我们在7002下得pom文件 加入依赖

    <dependencies>
        <!--  服务注册发现Eureka-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

我们在7002下创建一个文件夹,com.eurekaServer,我们直接将7001的启动类copy过来,然后改个名,如图

 将主启动类里面的7001改成7002

 在7002下创建yml文件

我们需要修改7001和7002的俩个yml文件

#修改7001主机yml文件
server:
  port: 7001
eureka:
  instance:
  # eureka服务端的实例名字
   hostname: eureka7001.com
  client:
  #表 示是否将自己注册到Eureka Server
   register-with-eureka: false
  # 表示是否从Eureka Server获取注册的服务信息
   fetch-registry: false
  # 设置与 Eureka server交互的地址查询服务和注册服务都需要依赖这个地址
   service-url:
    defaultZone: http://eureka7002.com:7002/eureka/
#修改7002主机yml文件
server:
  port: 7002
eureka:
  instance:
  # eureka服务端的实例名字
   hostname: eureka7002.com
  client:
  #表 示是否将自己注册到Eureka Server
   register-with-eureka: false
  # 表示是否从Eureka Server获取注册的服务信息
   fetch-registry: false
  # 设置与 Eureka server交互的地址查询服务和注册服务都需要依赖这个地址
   service-url:
    defaultZone: http://eureka7001.com:7001/eureka/

 每个配置文件最后一行,7001获取7002的配置,7002获取7001的配置,这样可以实现相互获取配置,达到高可用

我们在修改生产者8001下的配置文件,因为我们现在搭建了eureka的集群

8001下我们需要把两个注册中心都加入进去,用英文逗号进行拼接 

80端口同理,需要都发布到集群中

然后我们需要修改映射配置

找到C:\Windows\System32\drivers\etc\hosts

#添加如下配置
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com

修改后重启四个服务,浏览器用localhost:7001和 localhost:7002都行,如下图,成功

3.负载均衡

俗话说在生产队薅羊毛不能逮着一只羊薅,在微服务领域也是这个道理。面对一个庞大的微服务集群,如果你每次发起服务调用都只盯着那一两台服务器,在大用户访问量的情况下,这几台被薅羊毛的服务器一定会不堪重负

负载均衡要干什么事情

负载均衡有两类,服务端负载均衡客户端负载均衡

1.负载均衡分类

服务端负载均衡

在服务集群内设置一个中心化负载均衡器,比如Nginx。发起服务间调用的时候,服务请求并不直接发向目标服务器,而是发给这个全局负载均衡器,它再根据配置的负载均衡策略将请求转发到目标服务。

客户端负载均衡

Spring Cloud Loadbalancer 采用了客户端负载均衡技术,每个发起服务调用的客户端都存有完整的目标服务地址列表,根据配置的负载均衡策略,由客户端自己决定向哪台服务器发起调用。

由于Ribbon已经进入维护模式,并且Ribbon 2并不与Ribbon 1相互兼容,所以Spring Cloud全家桶在Spring Cloud Commons项目中,添加了Spring cloud Loadbalancer作为新的负载均衡器,并且做了向前兼容,就算你的项目中继续用 Spring Cloud Netflix 套装(包括Ribbon,Eureka,Zuul,Hystrix等等)让你的项目中有这些依赖,你也可以通过简单的配置,把Ribbon替换成Spring Cloud LoadBalancer。

2.负载均衡策略

springcloud loadbalancer只提供两种

  • RandomLoadBalancer 随机
  • RoundRobinLoadBalancer 轮询

二、服务调用

1.OpenFeign

Spring Cloud OpenFeign用于Spring Boot应用程序的声明式REST客户端。

我们之前用的是resttemplate,在实际开发过程中,我们很少使用restTemplate,于是,我们为了效率和可读性,引用openfeign

简而言之,OpenFeign是帮我们完成服务调用,也就是接口+注解的方式完成调用

Feign旨在使编写Java Http客户端变得更容易。前面在使用RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。

2.替代restTemplate

在父项目下创建maven工程,下面的流程跟之前的80端口流程一样,目录结构也一样,文件夹命名为cloud-consumer-openfeign-order80

 在openFeign80下pom文件引入依赖

    <dependencies>
        <!--  引入Eureka client依赖  -->
        <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-web</artifactId>
        </dependency>
        <!-- 引入OpenFeign依赖  -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
        </dependency>
        <!-- actuator监控信息完善 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

    </dependencies>

写openFeign80的主启动类

/**
 * 主启动类
 */
@Slf4j
@SpringBootApplication
@EnableFeignClients
public class OrderFeignMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderFeignMain80.class,args);
        log.info("************** OrderFeignMain80 服务启动成功  **********");
    }
}

写yml文件

eureka:
  client:
  # 表示是否将自己注册到Eureka Server
   register-with-eureka: true
  # 示是否从Eureka Server获取注册的服务信息
   fetch-registry: true
  # Eureka Server地址
   service-url:
    defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
  instance:
   instance-id: cloud-openfeign-order-consumer
   prefer-ip-address: true
spring:
  application:
  # 设置应用名词
   name: cloud-openfeign-order-consumer
server:
  port: 80

然后创建控制层和服务层,文件目录格式如下

OrderController代码
/**
 * 订单控制层
 */
@RestController
@RequestMapping("/order")
public class OrderController {


    @Autowired
    private PaymentFeignService paymentFeignService;


    /**
     * 测试OpenFeign接口调用
     * @return
     */
    @GetMapping("/index")
    public String get(){
        return paymentFeignService.index();
    }
}

 PaymentFeignService代码,注意 PaymentFeignService是接口

/**
 * 支付远程调用Feign接口
 */
@Component
@FeignClient(value = "cloud-payment-provider")
public interface PaymentFeignService {


    @GetMapping("/payment/index")
    String index();


}

这里我们是填写调用的生产者的名字,下面是生产者接口的路由,对应的如下图

 

 然后我们重启服务验证一下,把之前的80端口关掉,换成新的openfeign的80,浏览器搜索http://localhost/order/index,如下图OK

3.日志增强

OpenFeign虽然提供了日志增强功能,但是默认是不显示任何日志的,不过开发者在调试阶段可以自己配置日志的级别。

OpenFeign的日志级别如下:

  • NONE:默认的,不显示任何日志;
  • BASIC:仅记录请求方法、URL、响应状态码及执行时间;
  • HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息;
  • FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。

创建config文件夹,然后创建实体类

 然后设置目录级别,在OpenFeignConfig类下写, log包是feign下的log

@Configuration
public class OpenFeignConfig{

    /**
     * 日志级别定义
     */
    @Bean
    Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL;
    }
}

在feign80下yml文件加入配置,这里面的路径要对应自己建的文件夹路径

logging:
  level:
    com.consumer.service: debug

然后重启feign80,浏览器打开http://localhost/order/index

idea控制台出现如下,日志增强启用成功了 

4.超时机制

超时机制是保护服务的一种手段

问题:

  • 服务消费者在调用服务提供者的时候发生了阻塞、等待的情形,这个时候,服务消费者会一直等待下去。
  • 在某个峰值时刻,大呈的请求都在同时请求服务消费者,会造成线程的大呈堆积,势必会造成雪崩。
  • 利用超时机制来解决这个问题,设置一个超时时间,在这个时间段内,无法完成服务访问,则自动断开连接。

为了解决这些问题,超时机制来了!

在openfeign80的yml文件加入如下配置

# 默认超时时间
feign:
  client:
    config:
      default:
      # 连接超时时间
        connectTimeout: 2000
        # 读取超时时间
          readTimeout: 2000

然后我们在生产者的控制层写一个超时的接口

    /**
     * 测试超时机制
     *
     * @return
     */
    @GetMapping("timeout")
    public String paymentFeignTimeOut() {
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "payment success";
    }

服务者80添加超时方法PaymentFeignService


    @GetMapping("/payment/timeout")
    String timeout();

服务者80添加超时方法OrderController,来测试超时机制

    /**
     * 测试超时机制   
     * @return
     */
    @GetMapping("timeout")
    public String timeout() {
        return paymentFeignService.timeout();
    }

然后我们把修改的后台服务重启,浏览器搜索http://localhost/order/timeout

浏览器会报错,我们的idea控制台如下,抛出异常

三、服务断路器(熔断器)

1.服务熔断器_雪崩效应解决方案

服务熔断

熔断就跟保险丝一样,当一个服务请求并发特别大,服务器已经招架不住了,调用错误率飙升,当错误率达到一定阈值后,就将这个服务熔断了。熔断之后,后续的请求就不会再请求服务器了,以减缓服务器的压力。

当失败率(如因网络故障/超时造成的失败率高)达到阀值自动触发降级,熔断器触发的快速失败会进行快速恢复。

服务降级

当失败率(如因网络故障/超时造成的失败率高)达到阀值自动触发降级,熔断器触发的快速失败会进行快速恢复。

两种场景:

  • 当下游的服务因为某种原因响应过慢,下游服务主动停掉一些不太重要的业务,释放出服务器资源,增加响应速度!
  • 当下游的服务因为某种原因不可用,上游主动调用本地的一些降级逻辑,避免卡顿,迅速返回给用户!

概念:服务器繁忙,请稍后重试,不让客户端等待并立即返回一个友好的提示。

出现服务降级的情况:

  • 程序运行异常
  • 超时
  • 服务熔断触发服务降级
  • 线程池/信号量打满也会导致服务降级

服务隔离

 服务限流

服务熔断和服务隔离都属于出错后的容错处理机制,而限流模式则可以称为预防模式。

限流模式主要是提前对各个类型的请求设置最高的QPS阈值,若高于设置的阈值则对该请求直接返回,不再调用后续资源。

流量控制

  • 网关限流:防止大量请求进入系统,Mq实现流量消峰
  • 用户交流限流:提交按钮限制点击频率限制等

2.服务熔断器_Resilience4j

我们耳熟能详的就是Netflix Hystrix,这个断路器是SpringCloud中最早支持的一种容错方案,现在这个断路器已经处于维护状态,已经不再更新了,你仍然可以使用这个断路器,但是呢,我不建议你去使用,因为这个已经不再更新,所以Spring官方已经出现了Netflix Hystrix的替换方案。

什么是Resilience4j?

Resilience4j是一个轻量级的容错组件,其灵感来自于Hystrix,但主要为Java 8和函数式编程所设计,也就是我们的lambda表达式。轻量级体现在其只用 Vavr 库(前身是 Javaslang),没有任何外部依赖。而Hystrix依赖了Archaius ,Archaius本身又依赖很多第三方包,例如 Guava、Apache Commons Configuration 等。

Resilience4J 提供了一系列增强微服务的可用性功能:

  • resilience4j-circuitbreaker:熔断
  • resilience4j-ratelimiter:限流
  • resilience4j-bulkhead:隔离
  • resilience4j-retry:自动重试
  • resilience4j-cache:结果缓存
  • resilience4j-timelimiter:超时处理

3.服务熔断器_Resilience4j的断路器之超时降级

在消费者80(后续所有消费者都指的是openfeign的80模块)下增加pom依赖

        <dependency>
            <groupId>io.github.resilience4j</groupId>
            <artifactId>resilience4j-spring-cloud2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
        </dependency>

80下yml文件增加超时机制,我们测试一下熔断器的超时机制,timeoutDuration设置两秒钟返回数据

 # 超时机制
resilience4j:
  timelimiter:
   instances:
    delay:
    # 设置超时时间 5秒
     timeoutDuration: 2

我们将80下之前调用timeout接口的方法注掉

 然后加入如下代码,需要引入日志注解

    @GetMapping("/timeout")
    @TimeLimiter(name = "delay",fallbackMethod = "timeoutfallback")
    public CompletableFuture<String> timeout() {
        log.info("********* 进入方法 ******");
        //异步操作
        CompletableFuture<String> completableFuture = CompletableFuture
                .supplyAsync((Supplier<String>) () -> (paymentFeignService.timeout()));
        log.info("********* 离开方法 ******");
        return completableFuture;
    }

 在这段代码的注解@timelimiter中,有一个reply,这个reply(命名)需要跟yml文件中一致

,这个名字是可以更改的,自定义的 

然后再80端口的controller再加入如下代码,用于超时返回

    /**
     * 超时服务降级方法
     * @param e
     * @return
     */
    public CompletableFuture<ResponseEntity> timeoutfallback(Exception e){
        e.printStackTrace();
        return CompletableFuture.completedFuture(ResponseEntity.ok("超时啦"));
    }

如下图,成功了

4.服务熔断器_Resilience4j的断路器之重试机制

重试机制比较简单,当服务端处理客户端请求异常时,服务端将会开启重试机制,重试期间内,服务端将每隔一段时间重试业务逻辑处理。 如果最大重试次数内成功处理业务,则停止重试,视为处理成功。如果在最大重试次数内处理业务逻辑依然异常,则此时系统将拒绝该请求。

在消费者80的yml增加配置,原超时降级机制不用注掉

resilience4j:
 retry:
   instances:
    backendA:
    # 最大重试次数
     maxRetryAttempts: 3
    # 固定的重试间隔
     waitDuration: 10s
     enableExponentialBackoff: true
     exponentialBackoffMultiplier: 2

80的controller加入如下代码,@retry注解后的机制名和yml文件相同,自定义即可

 /**
 * 重试机制
 * @return
 */
@GetMapping("/retry")
@Retry(name = "backendA")
public CompletableFuture<String> retry() {
    log.info("********* 进入方法 ******");
    //异步操作
    CompletableFuture<String> completableFuture = CompletableFuture
         .supplyAsync((Supplier<String>) () -> (paymentFeignService.index()));
    log.info("********* 离开方法 ******");
    return completableFuture;
   }

然后我们重启服务,进行压力测试,我们自行下载Apache JMeter,使用教程网上都有,然后修改为中文(我英语比较菜- -!)

我们先创建线程组-HTTP请求&查看结果树,修改线程数为10,同时十个请求

 修改后点击上方的三角号,我们现在都请求成功了

 我们关掉8001的生产者,在重新请求,如图

 (半道换了个主题,md,无法保存,不让测,垃圾工具有bug!!!!)

 我们后台会看到一直在请求,往下拉,一直在重试,这个重试机制就成功了 

 

5.服务熔断器_Resilience4j的断路器之异常熔断

80控制层写入

    /**
     * 异常比例熔断降级
     * @return
     */
    @GetMapping("/citcuitBackend")
    @CircuitBreaker(name = "backendA")
    public String citcuitBackend(){


        log.info("************ 进入方法 ***********");
        String index = paymentFeignService.index();
        log.info("************ 离开方法 ***********");


        return index;
    }

80yml文件写入,具体细节都在yml文件中写了,然后我们重启,此时我们8001是关闭的

  # 熔断降级
resilience4j.circuitbreaker:
  configs:
    default:
      # 熔断器打开的失败阈值
      failureRateThreshold: 30
      # 默认滑动窗口大小,circuitbreaker使用基于计数和时间范围欢动窗口聚合统计失败率
      slidingWindowSize: 10
      # 计算比率的最小值,和滑动窗口大小去最小值,即当请求发生5次才会计算失败率
      minimumNumberOfCalls: 5
      # 滑动窗口类型,默认为基于计数的滑动窗口
      slidingWindowType: TIME_BASED
      # 半开状态允许的请求数
      permittedNumberOfCallsInHalfOpenState: 3
      # 是否自动从打开到半开
      automaticTransitionFromOpenToHalfOpenEnabled: true
      # 熔断器从打开到半开需要的时间
      waitDurationInOpenState: 2s
      recordExceptions:
        - java.lang.Exception
  instances:
    backendA:
      baseConfig: default

然后我们进行压力测试,我们控制台是这样,请求进来了,但是没有返回结果

然后我们将8001打开,修改请求地址,再重新测试

我们只进来了三个请求,然后会切换到半开启状态

6.服务熔断器_Resilience4j的断路器之慢调用熔断降级

在消费者80的yml中,resilience4j.circuitbreaker最后面加入配置

backendB:
      # 熔断器打开的失败阈值
      failureRateThreshold: 50
      # 慢调用时间阈值 高于这个阈值的
      slowCallDurationThreshold: 2s
      # 慢调用百分比阈值,断路器吧调用事件大于slow
      slowCallRateThreshold: 30
      slidingWindowSize: 10
      slidingWindowType: TIME_BASED
      minimumNumberOfCalls: 2
      permittedNumberOfCallsInHalfOpenState: 2
      waitDurationInOpenState: 2s
      eventConsumerBufferSize: 10

我们跟之前的配置大部分都一样,只有这两个地方不同

意思是2s后打开熔断降级,超过30%就打开断路降级

我们写控制层来验证一下

80的控制层写入

    /**
     * 慢调用熔断降级
     * @return
     */
    public String  slowcircuitbreakerfallback(Exception e){
        e.printStackTrace();
        return "太慢了。。。。。。。。。。。";
    }

    /**
     * 慢调用比例熔断降级
     * @return
     */
    @GetMapping("/slowcircuitbackend")
    @CircuitBreaker(name = "backendB",fallbackMethod = "slowcircuitbreakerfallback")
    public String slowcircuitbackend(){


        log.info("************ 进入方法 ***********");
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String index = paymentFeignService.index();
        log.info("************ 离开方法 ***********");


        return index;
    }

 然后我们修改一下8001的index方法,不干点坏事,测不了!!!让他睡十秒钟!

    @GetMapping("/index")
    public String index() {
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "payment + successs";
    }

jmeter我们测试一下,修改一下接口路径

 我们返回结果如下


 
7.服务断路器_Resilience4j信号量隔离实现 

信号量隔离与其他不同,需要引入隔离依赖,我们在80的pom文件中加入依赖

 <dependency>
   <groupId>io.github.resilience4j</groupId>
   <artifactId>resilience4j-bulkhead</artifactId>
   <version>1.7.0</version>
</dependency>

我们打开maven,之前引入的断路器版本是1.7.0,所以我们信号量隔离最好也要引入这个版本

 我们在80yml下加入配置,要加入到断路器中,不要放到熔断里

  # 信号量隔离
  bulkhead:
    instances:
      backendA:
        # 隔离允许并发线程执行的最大数量
        maxConcurrentCalls: 5
        # 当达到并发调用数量时,新的线程的阻塞时间
        maxWaitDuration: 20ms

我们接着在80的控制层测,加入代码,重启项目,jmeter修改路径

    /**
     * 测试信号量隔离
     * @return
     */
    @Bulkhead(name = "backendA",type = Bulkhead.Type.SEMAPHORE)
    @GetMapping("bulkhead")
    public String bulkhead() throws InterruptedException {
        log.info("************** 进入方法 *******");
        TimeUnit.SECONDS.sleep(10);
        String index = paymentFeignService.index();
        log.info("************** 离开方法 *******");
        return index;
    }

我们后台控制台能看到,只进来五个请求

 jmeter如下,我们的后台控制台有报错,意思是已经达到最大线程数,所以就不允许访问了。

 8.服务断路器_Resilience4j的线程池服务隔离

80yml文件熔断器下加入配置

 # 线程池服务隔离
  thread-pool-bulkhead:
    instances:
      backendA:
        # 最大线程池大小
        maxThreadPoolSize: 4
        # 核心线程池大小
        coreThreadPoolSize: 2
        #  队列容量
        queueCapacity: 2

 80控制层写入,然后重启,修改jmeter路径

  /**
   * 测试线程池服务隔离
   * @return
   */
  @Bulkhead(name = "backendA",type = Bulkhead.Type.THREADPOOL)
  @GetMapping("/futrue")
  public CompletableFuture future(){
    log.info("********** 进入方法 *******");
    try {
      TimeUnit.SECONDS.sleep(5);
     } catch (InterruptedException e) {
      e.printStackTrace();
     }
    log.info("********** 离开方法 *******");
    return CompletableFuture.supplyAsync(() -> "线程池隔离信息......");
   }

控制台

 具体哪种隔离我们可以自行选择,都可以,只需要通过注解配置即可 

9.服务断路器_Resilience4j限流

80yml文件熔断器下加入配置

  # 限流
  ratelimiter:
    instances:
      backendA:
        # 限流周期时长。    默认:500纳秒
        limitRefreshPeriod: 5s
        # 周期内允许通过的请求数量。    默认:50
        limitForPeriod: 2

80控制层写方法,都采用异步调用

  /**
   * 限流
   * @return
   */
  @GetMapping("/limiter")
  @RateLimiter(name = "backendA")
  public CompletableFuture<String> RateLimiter() {
    log.info("********* 进入方法 ******");
    //异步操作
    CompletableFuture<String> completableFuture = CompletableFuture
         .supplyAsync((Supplier<String>) () -> (paymentFeignService.index()));
    log.info("********* 离开方法 ******");
    return completableFuture;
   }

然后重启,我们观察jmeter,每5s通过两个,就OK

四、服务网关Gateway

1.服务网关Gateway_三大核心概念

路由

这是网关的基本构建块。它由一个ID,一个目标URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配。

断言

输入类型是一个ServerWebExchange。我们可以使用它来匹配来自HTTP请求的任何内容,例如headers或参数。

过滤

可以在请求被路由前或者之后对请求进行修改。


总结

首先任何请求进来,网关都会把它们拦住。根据请求的URL把它们分配到不同的路由上,路由上面会有断言,来判断请求能不能进来。进来之后会有一系列的过滤器对请求被转发前或转发后进行改动。 具体怎么个改动法,那就根据业务不同而自定义了。一般就是监控,限流,日志输出等等

2.服务网关Gateway_入门

我们在父项目下创建个maven项目,cloud-gateway-gateway9527

 在9527的pom文件中引入依赖

    <dependencies>
        <!--  引入网关Gateway依赖   -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>

在9527下创建主启动类GatewayMain9527,在resources下创建application.yml,文件层级如下

在9527主启动类写入

@Slf4j
@EnableEurekaClient
@SpringBootApplication
public class GatewayMain9527 {
    public static void main(String[] args) {
        SpringApplication.run(GatewayMain9527.class,args);
        log.info("********** GatewayMain 服务启动成功 *********");
    }
}

9527下yml文件加入配置,注意yml文件配置中格式层级

server:
  port: 9527
spring:
  cloud:
    gateway:
      routes:
        # 路由ID,没有固定规则但要求唯一,建议配合服务名
        - id: payment_provider
        # 匹配后提供服务的路由地址
          uri: http://localhost:80
        # 断言
          predicates:
          # 路径相匹配的进行路由
          - Path=/order/*

predicates中是我们本地文件的路径,如果请求这个路径中有这个服务地址,断言返回的是布尔类型值,返回true

--链路追踪后续单独会写,ok

  • 26
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring Cloud是一套用于构建分布式系统的工具集,它提供了一系列的组件和库,可以帮助开发者快速搭建分布式架构。下面是基于Spring Cloud搭建分布式架构的步骤: 1. 注册中心:Spring Cloud提供了服务注册与发现的功能,可以使用Eureka、Consul、Zookeeper等作为注册中心。通过将所有微服务都注册到注册中心,其他微服务就可以通过注册中心来发现和调用。 2. 服务提供者与消费者:通过Spring Cloud的负载均衡和服务调用功能,我们可以轻松实现服务提供者和消费者的通信。服务提供者将自己的服务注册到注册中心后,消费者可以通过调用注册中心的接口来获取服务列表,并通过负载均衡策略选择一个提供者进行调用。 3. 断路器和降级:为了保证系统的稳定性,Spring Cloud提供了断路器和降级的支持。当某个服务不可用或超时时,断路器可以自动切断对该服务的调用,并返回预先设定的默认值或执行降级逻辑,避免级联故障。 4. 配置中心:Spring Cloud提供了分布式配置中心,可以将配置文件集中管理,并在所有微服务中进行统一的快速更新和下发。通过配置中心,可以实现对不同环境的配置分离和动态更新。 5. 网关和路由:Spring Cloud Gateway和Zuul都是Spring Cloud提供的网关组件,可以用于统一管理和转发微服务的请求。通过配置路由规则,可以实现请求的转发、限流、鉴权等功能,提供更加灵活和安的访问控制。 通过以上步骤,我们可以基于Spring Cloud搭建一个完整的分布式架构。它帮助我们简化了分布式系统的开发和部署,提供了许多功能模块,包括服务注册与发现、服务调用、断路器和降级、配置中心、网关和路由等。这使得我们可以更加方便地构建和管理分布式系统,提高开发效率和系统的可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值