SpringCloud学习笔记

万变不离其宗:

SpringCloud不是技术而是一种生态。

为了解决大型网站的各种庞大复杂的业务,我们需要进行微服务架构。

有很多的解决方案,但是都要满足一下四点:

  • API网关
  • 服务之间的通讯
  • 服务注册与发现
  • 熔断机制

整体的机制:

image.png

总结来说,SpringCloud就是微服务思想下一套一站式的解决方案
image.png

SpringCloud的版本是以伦敦地铁站的名字命名的,以字母表顺序代表开发的顺序进度。

其中snapshot代表的十快照版本不建议使用

要使用GA版本-稳定通用版

构建Rest环境

首先我们需要构建一个Springboot的模块化项目来进行以后的测试

基本的项目结构如下:

其中需要有一个消费者和一个服务提供者:

image.png

其中:

  • api中提供相应的实体类
  • consumer为消费者
  • provider是服务提供者

构建的流程:

  • 首先新建一个Maven项目,删除src文件目录结构,然后在pom文件中配置:
<?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>com.zzm</groupId>
    <artifactId>springcloud</artifactId>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>springcloud-api</module>
        <module>springcloud-provider</module>
        <module>springcloud-consumer</module>
        <module>springcloud-eureka</module>
    </modules>

    <!--    配置打包方式-->
    <packaging>pom</packaging>


<!--    配置属性文件-->
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <junit.version>4.12</junit.version>
        <lombok.version>1.18.12</lombok.version>
    </properties>
<!--    管理依赖-->
    <dependencyManagement>
        <dependencies>
<!--            springcloud的依赖-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.1.4.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.20</version>
            </dependency>

            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.14</version>
            </dependency>

            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>3.0.5</version>
            </dependency>

            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>${junit.version}</version>
                <scope>test</scope>
            </dependency>

            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
            </dependency>

            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>1.2.17</version>
            </dependency>

            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-core</artifactId>
                <version>1.2.3</version>
            </dependency>




        </dependencies>
    </dependencyManagement>
</project>

  • 然后创建一个模块,依旧是普通的Maven项目
  • 创建后修改该项目的pom文件,需要什么依赖,就在该项目的pom文件中引入即可,下面以provider模块示例:
<?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">
    <parent>
        <artifactId>springcloud</artifactId>
        <groupId>com.zzm</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springcloud-provider</artifactId>


    <dependencies>

        <dependency>
            <groupId>com.zzm</groupId>
            <artifactId>springcloud-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>


        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
        </dependency>

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-test</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>


    </dependencies>

</project>

注意,如果父子项目构建成功的话,在idea中会有下图那样的标志

image20200812161700111.png

  • 构建成功后即可在该项目中编写业务逻辑
    • api:提供实体类
    • provider:提供相应的controller、service、dao
    • consumer:编写相应的controller即可。通过RestTemplate进行连接provider中的接口实现相应的业务逻辑
  • consumer中Controller中的业务逻辑处理:
@RestController
@RequestMapping("/dept")
public class DeptCustomerController {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    @Qualifier("url")
    private String url;

    /**
     * 查询全部
     * @return
     */
    @GetMapping
    public List<Dept> getall(){
        return restTemplate.getForObject(url+"/dept",List.class);
    }


    @GetMapping("/getOne/{deptno}")
    public Dept getOne(@PathVariable(value = "deptno") Integer deptno){
        return restTemplate.getForObject(url+"/dept/getDept/"+deptno,Dept.class);
    }

}
  • 最后一步,编写主启动类,注意主启动类的位置
  • 启动两个项目,然后访问consumer项目中的 “/dept”,即可得到相应的数据则为成功。

Eureka

简介

Eureka是一种基于REST(Representational State Transfer)的服务,主要用于AWS云,用于定位服务,以实现中间层服务器的负载平衡和故障转移。我们叫这种服务–Eureka Server。Eureka还附带了一个基于Java的客户端组件Eureka Client,它使与服务的交互变得更加容易。
客户端还有一个内置的负载均衡器,可以进行基本的循环负载均衡。在Netflix,一个更复杂的负载均衡器包装Eureka,根据流量,资源使用,错误条件等多种因素提供加权负载平衡,以提供卓越的灵活性。

image.png

Eureka整体可以分为三个角色:

image.png

Eureka Server:注册中心服务端

注册中心服务端主要对外提供了三个功能:

服务注册
服务提供者启动时,会通过 Eureka Client 向 Eureka Server 注册信息,Eureka Server 会存储该服务的信息,Eureka Server 内部有二层缓存机制来维护整个注册表

提供注册表
服务消费者在调用服务时,如果 Eureka Client 没有缓存注册表的话,会从 Eureka Server 获取最新的注册表

同步状态
Eureka Client 通过注册、心跳机制和 Eureka Server 同步当前客户端的状态。

Eureka Client:注册中心客户端
Eureka Client 是一个 Java 客户端,用于简化与 Eureka Server 的交互。Eureka Client 会拉取、更新和缓存 Eureka Server 中的信息。因此当所有的 Eureka Server 节点都宕掉,服务消费者依然可以使用缓存中的信息找到服务提供者,但是当服务有更改的时候会出现信息不一致。

Register: 服务注册
服务的提供者,将自身注册到注册中心,服务提供者也是一个 Eureka Client。当 Eureka Client 向 Eureka Server 注册时,它提供自身的元数据,比如 IP 地址、端口,运行状况指示符 URL,主页等。

Renew: 服务续约
Eureka Client 会每隔 30 秒发送一次心跳来续约。 通过续约来告知 Eureka Server 该 Eureka Client 运行正常,没有出现问题。 默认情况下,如果 Eureka Server 在 90 秒内没有收到 Eureka Client 的续约,Server 端会将实例从其注册表中删除,此时间可配置,一般情况不建议更改。

自我保护机制

默认情况下,如果 Eureka Server 在一定的 90s 内没有接收到某个微服务实例的心跳,会注销该实例。但是在微服务架构下服务之间通常都是跨进程调用,网络通信往往会面临着各种问题,比如微服务状态正常,网络分区故障,导致此实例被注销。

固定时间内大量实例被注销,可能会严重威胁整个微服务架构的可用性。为了解决这个问题,Eureka 开发了自我保护机制,那么什么是自我保护机制呢?

Eureka Server 在运行期间会去统计心跳失败比例在 15 分钟之内是否低于 85%,如果低于 85%,Eureka Server 即会进入自我保护机制。

Eureka Server 触发自我保护机制后,页面会出现提示:
image.png

Eureka Server 进入自我保护机制,会出现以下几种情况:
(1 Eureka 不再从注册列表中移除因为长时间没收到心跳而应该过期的服务
(2 Eureka 仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用)
(3 当网络稳定时,当前实例新的注册信息会被同步到其它节点中

Eureka 自我保护机制是为了防止误杀服务而提供的一个机制。当个别客户端出现心跳失联时,则认为是客户端的问题,剔除掉客户端;当 Eureka 捕获到大量的心跳失败时,则认为可能是网络问题,进入自我保护机制;当客户端心跳恢复时,Eureka 会自动退出自我保护机制。

如果在保护期内刚好这个服务提供者非正常下线了,此时服务消费者就会拿到一个无效的服务实例,即会调用失败。对于这个问题需要服务消费者端要有一些容错机制,如重试,断路器等。

通过在 Eureka Server 配置如下参数,开启或者关闭保护机制,生产环境建议打开:

eureka.server.enable-self-preservation=true
1

Eureka 集群原理

再来看看 Eureka 集群的工作原理。我们假设有三台 Eureka Server 组成的集群,第一台 Eureka Server 在北京机房,另外两台 Eureka Server 在深圳和西安机房。这样三台 Eureka Server 就组建成了一个跨区域的高可用集群,只要三个地方的任意一个机房不出现问题,都不会影响整个架构的稳定性。

在这里插入图片描述

从图中可以看出 Eureka Server 集群相互之间通过 Replicate 来同步数据,相互之间不区分主节点和从节点,所有的节点都是平等的。在这种架构中,节点通过彼此互相注册来提高可用性,每个节点需要添加一个或多个有效的 serviceUrl 指向其他节点。

如果某台 Eureka Server 宕机,Eureka Client 的请求会自动切换到新的 Eureka Server 节点。当宕机的服务器重新恢复后,Eureka 会再次将其纳入到服务器集群管理之中。当节点开始接受客户端请求时,所有的操作都会进行节点间复制,将请求复制到其它 Eureka Server 当前所知的所有节点中。

另外 Eureka Server 的同步遵循着一个非常简单的原则:只要有一条边将节点连接,就可以进行信息传播与同步。所以,如果存在多个节点,只需要将节点之间两两连接起来形成通路,那么其它注册中心都可以共享信息。每个 Eureka Server 同时也是 Eureka Client,多个 Eureka Server 之间通过 P2P 的方式完成服务注册表的同步。

Eureka Server 集群之间的状态是采用异步方式同步的,所以不保证节点间的状态一定是一致的,不过基本能保证最终状态是一致的。

Eureka 分区
Eureka 提供了 Region 和 Zone 两个概念来进行分区,这两个概念均来自于亚马逊的 AWS:
region:可以理解为地理上的不同区域,比如亚洲地区,中国区或者深圳等等。没有具体大小的限制。根据项目具体的情况,可以自行合理划分 region。
zone:可以简单理解为 region 内的具体机房,比如说 region 划分为深圳,然后深圳有两个机房,就可以在此 region 之下划分出 zone1、zone2 两个 zone。

上图中的 us-east-1c、us-east-1d、us-east-1e 就代表了不同的 Zone。Zone 内的 Eureka Client 优先和 Zone 内的 Eureka Server 进行心跳同步,同样调用端优先在 Zone 内的 Eureka Server 获取服务列表,当 Zone 内的 Eureka Server 挂掉之后,才会从别的 Zone 中获取信息。

Eurka 保证 AP

Eureka Server 各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而 Eureka Client 在向某个 Eureka 注册时,如果发现连接失败,则会自动切换至其它节点。只要有一台 Eureka Server 还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。

Eurka 工作流程

了解完 Eureka 核心概念,自我保护机制,以及集群内的工作原理后,我们来整体梳理一下 Eureka 的工作流程:

1、Eureka Server 启动成功,等待服务端注册。在启动过程中如果配置了集群,集群之间定时通过 Replicate 同步注册表,每个 Eureka Server 都存在独立完整的服务注册表信息

2、Eureka Client 启动时根据配置的 Eureka Server 地址去注册中心注册服务

3、Eureka Client 会每 30s 向 Eureka Server 发送一次心跳请求,证明客户端服务正常

4、当 Eureka Server 90s 内没有收到 Eureka Client 的心跳,注册中心则认为该节点失效,会注销该实例

5、单位时间内 Eureka Server 统计到有大量的 Eureka Client 没有上送心跳,则认为可能为网络异常,进入自我保护机制,不再剔除没有上送心跳的客户端

6、当 Eureka Client 心跳请求恢复正常之后,Eureka Server 自动退出自我保护模式

7、Eureka Client 定时全量或者增量从注册中心获取服务注册表,并且将获取到的信息缓存到本地

8、服务调用时,Eureka Client 会先从本地缓存找寻调取的服务。如果获取不到,先从注册中心刷新注册表,再同步到本地缓存

9、Eureka Client 获取到目标服务器信息,发起服务调用

10、Eureka Client 程序关闭时向 Eureka Server 发送取消请求,Eureka Server 将实例从注册表中删除

这就是Eurka基本工作流程

具体的实现

首先我们在之前的项目之中新建一个模块,叫做eureka

然后需要在该项目中的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">
    <parent>
        <artifactId>springcloud</artifactId>
        <groupId>com.zzm</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springcloud-eureka</artifactId>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>


    </dependencies>

</project>

然后新建application.yml,在其中配置:

server:
  port: 8082

#Eureka配置
eureka:
  instance:
    #服务端的实例名称
    hostname: localhost
  client:
    #是否向eureka中注册自己
    register-with-eureka: false
    #false表示自己是服务器
    fetch-registry: false
    #配置eureka的监控地址
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

创建主启动类,在上面加上注解

@EnableEurekaServer

下面附上主启动类:

@SpringBootApplication
//表示是一个注册中心
@EnableEurekaServer
public class EurekaApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class,args);
    }
}

此时,服务注册中心已经配置完毕启动后可以看到:

image.png

此时还是没有服务注册进来。

开始向服务注册中心注册服务

我们在之前的provider模块中的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>

    <groupId>com.zzm</groupId>
    <artifactId>springcloud</artifactId>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>springcloud-api</module>
        <module>springcloud-provider</module>
        <module>springcloud-consumer</module>
        <module>springcloud-eureka</module>
    </modules>

    <!--    配置打包方式-->
    <packaging>pom</packaging>


<!--    配置属性文件-->
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <junit.version>4.12</junit.version>
        <lombok.version>1.18.12</lombok.version>
    </properties>
<!--    管理依赖-->
    <dependencyManagement>
        <dependencies>
<!--            springcloud的依赖-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.1.4.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.20</version>
            </dependency>

            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.14</version>
            </dependency>

            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>3.0.5</version>
            </dependency>

            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>${junit.version}</version>
                <scope>test</scope>
            </dependency>

            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
            </dependency>

            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>1.2.17</version>
            </dependency>

            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-core</artifactId>
                <version>1.2.3</version>
            </dependency>




        </dependencies>
    </dependencyManagement>
</project>

然后编写application.yml:

#Eureka的配置

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8082/eureka/
  instance:
    #修改eureka上面的默认描述
    instance-id: springcloud-provider

最后在主启动类中加入

@EnableEurekaClient

附上主启动类:

@MapperScan("com.zzm.mapper")
@SpringBootApplication
@EnableEurekaClient //向Eureka中注册服务
public class DeptApplication {
    public static void main(String[] args) {
        SpringApplication.run(DeptApplication.class,args);
    }
}

最后启动provider这个模块就可以在服务注册中心中注册:

image.png

此时就注册完成了。

需要理解一下Eureka的自我保护机制

总结

讲了 Eureka 核心概念、Eureka 自我保护机制和 Eureka 集群原理。通过分析 Eureka 工作原理,我可以明显地感觉到 Eureka 的设计之巧妙,通过一些列的机制,完美地解决了注册中心的稳定性和高可用性。

Eureka 为了保障注册中心的高可用性,容忍了数据的非强一致性,服务节点间的数据可能不一致, Client-Server 间的数据可能不一致。比较适合跨越多机房、对注册中心服务可用性要求较高的使用场景。

Ribbon

简介

Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。Spring Cloud Ribbon虽然只是一个工具类框架,它不像服务注册中心、配置中心、API网关那样需要独立部署,但是它几乎存在于每一个Spring Cloud构建的微服务和基础设施中。因为微服务间的调用,API网关的请求转发等内容,实际上都是通过Ribbon来实现的,包括后续我们将要介绍的Feign,它也是基于Ribbon实现的工具。所以,对Spring Cloud Ribbon的理解和使用,对于我们使用Spring Cloud来构建微服务非常重要。

作者:Chandler_珏瑜
链接:https://www.jianshu.com/p/1bd66db5dc46
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

通俗一点,就是当用户发出一个请求的时候,通过ribbon的算法使用户随机的访问不同的已经在服务中心中注册的服务。

所以说,ribbon离不开eureka。

实现的步骤

首先要知道,Ribbon面向的是项目中的消费者,也就是真正的客户端,在我们搭建的项目中也就是consumer这个模块。

所以:

  • 在consumer模块中的pom文件中增加依赖:
<!--        ribbon-->
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-ribbon -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-ribbon</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>
<!--eureka-->
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>
  • 然后再application.yml中进行对于eureka的配置
#eureka
eureka:
  client:
    register-with-eureka: false  #不向Eureka中配置自己
    service-url:
      defaultZone: 
  • 然后在我们consumer的项目中,之前配置了RestTemplate的相关内容,再该配置类上面加上注解@LoadBalanced注解,下面附上配置类代码:

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

至此,我们Ribbon就已经融合进消费者的项目中了。

然后在之前我们写的请求的地址那里需要修改一下:

将之前的url比如:localhost:8080

修改成你对应的服务提供者的applicationname,这样就会通过Eureka去相应的服务。

比如本项目中的服务提供者配置的名字为springcloud-provider,那么现在的地址就应该为:http://SPRINGCLOUD_PROVIDER

然后实现这个负载均衡的方式,必须要有Eureka的集群环境,就是要有多个服务注册中心

  • 多个服务注册中心的注册方法:

    • 新建模块
    • 将之前的Eureka的pom文件复制,导入相关的依赖
    • 修改该模块的application.yml
    server:
      port: 8001
    
    #Eureka配置
    eureka:
      instance:
        #服务端的实例名称
        hostname: eureka8001
      client:
        #是否向eureka中注册自己
        register-with-eureka: false
        #false表示自己是服务器
        fetch-registry: false
        #配置eureka的监控地址
        service-url:
          defaultZone: http://192.168.50.169:8080/eureka/
    

    重点是在修改service-url下面的defaultZone,需要在这里配置集群的服务注册中心的地址,这里是配置在linux

    • 然后创建相应的主启动类
    @SpringBootApplication
    //表示是一个注册中心
    @EnableEurekaServer
    public class EurekaApplication {
        public static void main(String[] args) {
            SpringApplication.run(EurekaApplication.class,args);
        }
    }
    
    • 部署在Linux即可,注意,如果打的jar包运行的时候提示没有主加载清单的话,那么是pom文件中没有导入打包插件,需要引入:

      <!--打包插件-->
          <build>
              <plugins>
                  <plugin>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-maven-plugin</artifactId>
                      <executions>
                          <execution>
                              <goals>
                                  <goal>repackage</goal>
                              </goals>
                          </execution>
                      </executions>
                  </plugin>
              </plugins>
          </build>
      

此时我们有了多个注册中心,那么为了实现负载均衡,我们还需要创建多个provider模块来提供不同的服务(不同的服务提供不同的数据库),步骤如下:

  • 创建新的模块
  • 修改pom文件
    <dependencies>

        <dependency>
            <groupId>com.zzm</groupId>
            <artifactId>springcloud-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>


        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
        </dependency>

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-test</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>


    </dependencies>
  • 更改配置文件:
server:
  port: 8082

mybatis-plus:
  mapper-locations: classpath*:**/xml/*Mapper.xml,classpath*:**/xml/*/*Mapper.xml
  type-aliases-package: com.zzm.**.entity

spring:
  application:
    name: springcloud-provider
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db02?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai&useSSL=false&autoReconnect=true&autoReconnectForPools=true&allowMultiQueries=true
    username: root
    password: root

#Eureka的配置

eureka:
  client:
    service-url:
      defaultZone: http://192.168.50.169:8081/eureka/,http://192.168.50.169:8080/eureka/
  instance:
    #修改eureka上面的默认描述
    instance-id: springcloud-provider-2

这里需要注意的是修改数据库的连接,一定要使用不同的连接,要不然看不到效果

然后需要注意的就是,service-url这里,这里需要将两个注册中心都填进去,我们将这个服务注册到两个不同的服务注册中心

最重要的一点,就是application的name,这里必须和之前的provider保持一致才能实现效果

  • 然后再模块中添加相应的方法,和之前模块中的保持一直即可
  • 然后挨个启动我们的服务者,最后我们启动我们的消费者就可以实现负载均衡。

负载均衡的策略

  1. 随机 (Random)
  2. 轮询 (RoundRobin)
  3. 一致性哈希 (ConsistentHash)
  4. 哈希 (Hash)
  5. 加权(Weighted)

如果想要更改策略的话,那么就在配置类中注册Bean即可

 @Bean
    public IRule ribbonRule() {
        return new RandomRule();
    }

此外,Ribbon还支持自定义策略的配置,但是要注意的是

如果使用自定义的配置,那么这个配置类不能和SpringBoot的主启动类再同一个包下,因为这样的话,会被@ComponentScan注解扫描到,会和Ribbon自带的配置起冲突

然后需要在主启动类上面加上一下注解来实现自定义的策略:

@RibbonClient(configuration = ,name=)
//name 代表这个所需要进行负载均衡的服务的名字,configuration 代表的是配置文件的位置

Fegin

简介

其实说白了,Fegin和Ribbon其实实现的目的都一样,只不过Fegin在实现的方式上面采取的是接口的实现方式,使代码变得更加整洁了。

更易于Java开发人员的变成习惯。

实现的步骤

  • 首先我们新建一个模块,区别于之前的Ribbon客户端。
  • 然后在该模块中修改pom文件:
<dependencies>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
        <!--        fegin-->
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-ribbon -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>
        <!--eureka-->
        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>

    </dependencies>
  • 修改一下application.yml
server:
  port: 8088
custom:
  url: http://springcloud-provider

#eureka
eureka:
  client:
    register-with-eureka: false  #不向Eureka中配置自己
    service-url:
      defaultZone: http://192.168.50.169:8081/eureka/,http://192.168.50.169:8080/eureka/

主要是修改端口号

  • 修改Controller

    @Autowired
    private DeptService deptService;
    /**
     * 查询全部
     * @return
     */
    @GetMapping
    public List<Dept> getall(){

        return deptService.getAll();

    }
  • 在主启动类上面加上注解
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableFeignClients(basePackages = {"com.zzm.service"})
public class ConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class,args);
    }
}

最重要的一步来了,需要在api模块上面进行对于service的改造

@FeignClient(value = "springcloud-provider")
public interface DeptService {

    @GetMapping("/dept")
    List<Dept> getAll();
}

启动各种服务,以及服务注册中心,即可实现Fegin方式的负载均衡

Hystrix

简介

在分布式环境中,许多服务依赖项中的一些必然会失败。Hystrix是一个库,通过添加延迟容忍和容错逻辑,帮助你控制这些分布式服务之间的交互。Hystrix通过隔离服务之间的访问点、停止级联失败和提供回退选项来实现这一点,所有这些都可以提高系统的整体弹性。

当一切正常时,请求看起来是这样的:
image.png

当其中有一个系统有延迟时,它可能阻塞整个用户请求:

image.png

在高流量的情况下,一个后端依赖项的延迟可能导致所有服务器上的所有资源在数秒内饱和(PS:意味着后续再有请求将无法立即提供服务)
image.png

Hystrix就是这样的一个机制,当服务熔断的时候,那么通过Hystrix去寻找事先设置的备选方法,通过这个备选的方法,立即返回当前的错误信息,不再继续下去,这避免了服务之间的相互等待,以及更大的错误的发生。

熔断器—实现的步骤

  • 新建一个模块,单独实现Hystrix模块的业务。
  • 添加依赖
    <dependencies>

        <dependency>
            <groupId>com.zzm</groupId>
            <artifactId>springcloud-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>


        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
        </dependency>

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-test</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>


    </dependencies>
  • 修改我们的Controller,场景就是当输入一个不存在的部门编号的时候,那么爆出一个异常
@GetMapping("/getDept/{deptno}")
public Dept getDeptByDeptNo(@PathVariable(name = "deptno") Integer deptno){
    Dept dept = deptService.getById(deptno);
    
    if(dept==null){
        throw new RuntimeException("该部门不存在,或者是id有误");
    }
    return dept;
}
  • 在还没有采取熔断措施的时候,我们采取这样的的形式测试一下:
    image.png

后台:

image.png

所以现在应该采取熔断措施,当服务出现问题的时候,我们需要给用户返回一个相应的信息,不能直接错误崩掉。

定义方法:

   public Dept hystrixDept(){
            return new Dept()
                    .setDeptno(500)
                    .setDbname("部门不存在,或者是id有误")
                    .setDname("null");
        }

就是定义一个对象,设置属性然后返回过去。

然后需要在原方法上面加上@HystrixCommand()的注解,其中,设置fallbackMethod,代表的是失败回调的方法的名称

    @GetMapping("/getDept/{deptno}")
        @HystrixCommand(fallbackMethod = "hystrixDept")
        public Dept getDeptByDeptNo(@PathVariable(name = "deptno") Integer deptno){
            Dept dept = deptService.getById(deptno);

            if(dept==null){
                throw new RuntimeException("该部门不存在,或者是id有误");
            }
            return dept;
        }

然后需要在主启动类上面开启熔断器,就是加上@EnableCircuitBreaker注解

@MapperScan("com.zzm.mapper")
@SpringBootApplication
@EnableEurekaClient //向Eureka中注册服务
@EnableCircuitBreaker//开启熔断器
public class DeptApplicationHystrix {
    public static void main(String[] args) {
        SpringApplication.run(DeptApplicationHystrix.class,args);
    }
}

需要注意的一点就是 目标方法和回退方法参数和返回类型要一致

测试一下:

image.png

至此,最基本的熔断器就配置进来了。

服务降级—实现的步骤

Hystrix不仅有熔断机制,经过上面的分析,得出结论,Hystrix的熔断机制是作用在服务提供者上面的,服务降级的机制是作用在消费者上面的。

当大型项目中有很多的服务器,比如A服务器中的微服务非常多,一时间高并发严重,然而B服务其却很空闲,那么就可以把B服务器关了,然后去解决A服务其上面的高并发问题,这个时候就需要服务降级,具体的实现步骤如下:

  • 增加springcloud-api-fegin项目中的降级的实现类,注意继承FallbackFactory
@Component
public class DeptFallbackFactory implements FallbackFactory {
    public DeptService create(Throwable throwable) {
        return new DeptService() {
            public List<Dept> getAll() {
                return null;
            }

            public Dept getById(Integer deptno) {
                return new Dept().setDname("XXXXXXXXXXXXXXX").setDeptno(deptno).setDbname("XXXXXXXXXXXXXXXXXXXXXXXXX");
            }
        };
    }
}
  • 在DeptService上面使用注解@FeignClient(value = "springcloud-provider",fallbackFactory = DeptFallbackFactory.class)
@FeignClient(value = "springcloud-provider",fallbackFactory = DeptFallbackFactory.class)
public interface DeptService {

    @GetMapping("/dept")
    List<Dept> getAll();

    @GetMapping("/dept/getDept/{deptno}")
    Dept getById(@PathVariable("deptno") Integer deptno);
}
  • 然后再springcloud-consumer-fegin上面开启服务降级
feign:
  hystrix:
    enabled: true

我们测试一下:

再全部正常的情况下,是可以查到数据的,当我把服务提供者断开的时候,如果是以前没有采取这种机制的时候会直接报错,但是现在返回的是自定义的一个对象的信息

image.png

Zuul

简介

Zuul是Spring Cloud全家桶中的微服务API网关。

所有从设备或网站来的请求都会经过Zuul到达后端的Netflix应用程序。作为一个边界性质的应用程序,Zuul提供了动态路由、监控、弹性负载和安全功能。Zuul底层利用各种filter实现如下功能:

  • 认证和安全 识别每个需要认证的资源,拒绝不符合要求的请求。
  • 性能监测 在服务边界追踪并统计数据,提供精确的生产视图。
  • 动态路由 根据需要将请求动态路由到后端集群。
  • 压力测试 逐渐增加对集群的流量以了解其性能。
  • 负载卸载 预先为每种类型的请求分配容量,当请求超过容量时自动丢弃。
  • 静态资源处理 直接在边界返回某些响应。

实现的步骤

  • 首先新建一个模块,作为我们的Zuul模块

引入依赖

<dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zuul</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>


    </dependencies>
  • 配置application.yml
server:
  port: 80

spring:
  application:
    name: zuul

eureka:
  client:
    service-url:
      defaultZone: http://192.168.50.169:8080/eureka/,http://192.168.50.169:8081/eureka/
  instance:
    instance-id: zuul

zuul:
  #配置路由的规则
  routes:
    dept.serviceId: springcloud-provider
    dept.path: /dept/**
    #忽略全部的真实的服务去访问
  ignored-services: "*"

  • 编写主启动类,记得加上@EnableZuulProxy
@SpringBootApplication
@EnableZuulProxy
public class ZuulAppication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulAppication.class,args);
    }
}

image.png

至此,Zuul的配置到此结束。

Config

简介

SpringCloud Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置。

SpringCloud Config分为服务端和客户端两部分。

服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口。

客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息。

配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容。

服务端—实现的步骤

  • 首先需要一个github或者码云账号,然后新建一个仓库,之后随便编写一个application配置文件,将文件上传至仓库。
spring:
  profiles:
    active: dev

---
spring:
  profiles: dev
  application:
    name: zuul

  • 然后需要新建一个模块,导入依赖
  <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
    </dependencies>
  • 然后配置application.yml
server:
  port: 8888

spring:
  application:
    name: springcloud-config
#配置连接的相关信息
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/i8023tp/springcloud-study.git
          username: I8023tp
          password: 1998413qwert
  • 然后我们创建主启动类,注意记得加上@EnableConfigServer注解
@SpringBootApplication
@EnableConfigServer
public class ConfigApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigApplication.class,args);
    }
}

然后启动项目,输入http://localhost:8888/application-dev.yml,即可读取在服务器上面的配置。

image.png

客户端—实现的步骤

  • 新建一个模块,springcloud-consumer-config
  • 引入依赖
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>

        <dependency>
            <groupId> org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

    </dependencies>
  • 修改配置文件
spring:
  application:
    name: consumerconfig
  cloud:
    config:
      #地址(这里面我们把服务端的端口号定义为8888所以为8888)
      uri: http://localhost:8888
      #对应的名字
      name: application
      #环境
      profile: dev
      #分支的名字
      label: master

  • 编写主启动类
@SpringBootApplication
public class ConsumerConfigApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerConfigApplication.class,args);
    }
}
  • 编写一个简单的测试controller
@RestController
public class TController {

    @Value("${eureka.client.service-url.defaultZone}")
    private String uri;

    @GetMapping("/aaa")
    public String getUri(){
        return uri;
    }
}
  • 启动服务端和客户端,然后测试结果
    image.png

Q.E.D.


Hello welcome to my blog