Spring Cloud NetFlix 总结

一、微服务是什么?

  微服务是软件开发过程中最近提出来的一套解决方案,软件开发经历了All in one–>多实例负载均衡–>微服务。
  微服务方案致力于将一个系统拆分成多个简单细小的服务,分开部署。
对于每个服务可以根据需要部署不同数量的集群,这些服务之间通过远程调用的方式相互调用为消费者提供服务。
  对于微服务,最大的问题是 网络的不可靠 ,所以微服务主要解决以下四个问题:

二、SpringBoot和SpringCloud的关系

-SpringBoot专注于快速方便的开发单个个体微服务。
-SpringCloud是关注全局的微服务协调整理治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来,为各个微服务之间提供:配置管理,服务发现,断路器,路由,微代理,事件总线,全局锁,决策精选,分布式会话等等集成服务。
-SpringBoot可以离开SpringCloud独立使用,开发项目,但是SpringCloud离不开SpringBoot,属于依赖关系
-SpringBoot专注于快速、方便的开发单个个体微服务,SpringCloud关注全局的服务治理框架。

三、SpringCloud生态

首先我们来看一下现在主流的几个SpringCloud生态所使用的框架。

1、Spring Cloud NetFlix(一站式解决方案)

(1)Eureka,服务注册和发现,它提供了一个服务注册中心、服务发现的客户端,还有一个方便的查看所有注册的服务的界面。 所有的服务使用 Eureka 的服务发现客户端来将自己注册到 Eureka 的服务器上。

(2)Zuul,网关,所有的客户端请求通过这个网关访问后台的服务。他可以使用一定的路由配置来判断某一个 URL 由哪个服务来处理。并从 Eureka 获取注册的服务来转发请求。

(3)Ribbon,即负载均衡,Zuul 网关将一个请求发送给某一个服务的应用的时候,如果一个服务启动了多个实例,就会通过 Ribbon 来通过一定的负载均衡策略来发送给某一个服务实例。

(4)Feign,服务客户端,服务之间如果需要相互访问,可以使用 RestTemplate,也可以使用 Feign 客户端访问。它默认会使用 Ribbon 来实现负载均衡。

(5)Hystrix,监控和断路器。我们只需要在服务接口上添加 Hystrix 标签,就可以实现对这个接口的监控和断路器功能。

(6)Hystrix Dashboard,监控面板,他提供了一个界面,可以监控各个服务上的服务调用所消耗的时间等。

(7)Turbine,监控聚合,使用 Hystrix 监控,我们需要打开每一个服务实例的监控信息来查看。而 Turbine 可以帮助我们把所有的服务实例的监控信息聚合到一个地方统一查看。

2、Apache Dubbo Zookeeper (半自动,需要整合)

  Apache Dubbo (incubating) |ˈdʌbəʊ| 是一款高性能、轻量级的开源 Java RPC 框架。
ZooKeeper 是一种分布式协调服务,用于管理大型主机。在分布式环境中协调和管理服务是一个复杂的过程。

3、Spring Cloud Alibaba Nacos (新一站式解决方案)

  Nacos 是 Alibaba 公司推出的开源工具,用于实现分布式系统的服务发现与配置管理。英文全称 Dynamic Naming and Configuration Service,Na 为 Naming/NameServer 即注册中心,co 为 Configuration 即配置中心,Service 是指该注册/配置中心都是以服务为核心。服务(Service)是 Nacos 世界的一等公民。

四、RestFul服务

1、构建环境

(1)创建数据库表

这里我们需要新建一个名为db01的数据库

DROP TABLE IF EXISTS `dept`;
CREATE TABLE `dept`  (
  `deptno` int(11) NOT NULL AUTO_INCREMENT,
  `dname` varchar(60) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `db_source` varchar(60) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`deptno`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of dept
-- ----------------------------
INSERT INTO `dept` VALUES (1, '开发部门', NULL);
INSERT INTO `dept` VALUES (2, '人事部门', NULL);
INSERT INTO `dept` VALUES (3, '财务部门', NULL);
INSERT INTO `dept` VALUES (4, '市场部门', NULL);
INSERT INTO `dept` VALUES (5, '运维部门', NULL);

SET FOREIGN_KEY_CHECKS = 1;

(2)构建父项目的Maven配置

<?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.imperfect</groupId>
    <artifactId>SpringCloud</artifactId>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>springcloud-api</module>
        <!--        服务提供者-->
        <module>spring-cloud-provider-dept-8001</module>
        <!--        服务消费者-->
        <module>spring-cloud-consumer-dept-8080</module>
    </modules>

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


    <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>
        <junit.version>4.12</junit.version>
        <lombok.version>1.16.10</lombok.version>
        <log4j.version>1.2.17</log4j.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <!--                SpringCloud的依赖-->
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--            SpringBoot-->
            <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>5.1.47</version>
            </dependency>

            <!-- 数据源 -->
            <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.10</version>
            </dependency>

            <!-- SpringBoot启动器 -->
            <!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>1.3.2</version>
            </dependency>

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

            <!-- 日志相关的 -->
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-core</artifactId>
                <version>1.2.3</version>
            </dependency>

            <!--log4j-->
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>${log4j.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

(3)构建springcloud-api模块

<?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.imperfect</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springcloud-api</artifactId>


<!--    当前的module自己需要的依赖,如果当前父依赖中配置了版本,这里就不用写了-->
<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>

</dependencies>
</project>

编写pojo代码src/main/java/com/imperfect/api/pojo/Dept.java

package com.imperfect.api.pojo;

import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.io.Serializable;
@Data
@NoArgsConstructor
@Accessors(chain = true)
public class Dept implements Serializable {

    private Long deptno;
    private String dname;
    private String db_source;

    public Dept(String dname) {
        this.dname = dname;
    }
}

(4)构建服务提供者模块(spring-cloud-provider-dept-8001)

在这里插入图片描述

编写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.imperfect</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-cloud-provider-dept-8001</artifactId>


    <dependencies>
        <!--        我们需要拿到实体类,所以我们需要配置api-module-->
        <dependency>
        <groupId>com.imperfect</groupId>
        <artifactId>springcloud-api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </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>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-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>

    </dependencies>
</project>

编写application.yml配置类
src/main/resources/application.yml

server:
  port: 8001
  #myBatis配置
mybatis:
  type-aliases-package: com.imperfect.api.pojo
  mapper-locations: classpath:mybatis/mapper/*.xml
  config-location:  classpath:mybatis/mybatis-config.xml

 #spring的配置
spring:
  application:
    name: spring-cloud-provider-dept
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: org.gjt.mm.mysql.Driver
    url: jdbc:mysql://数据库地址/db01?useUnicode=true&characterEncoding=utf8&useSSL=false
    username: 用户名
    password: 密码

编写mybatis-config.xml配置文件
mybatis/mybatis-config. xml

<?xml version="1.0" encoding="UTF8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="cacheEnabled" value="true"/>
    </settings>
</configuration>

编写DeptMapper.xml
mybatis/mapper/DeptMapper.xml

<?xml version="1.0" encoding="UTF8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.imperfect.mapper.DeptMapper">
    <insert id="addDept" parameterType="Dept">
        insert into dept (dname,db_source)
        values (#{dname},DATABASE())
    </insert>

    <select id="queryById" parameterType="long" resultType="com.imperfect.api.pojo.Dept">
        select * from dept where deptno=#{deptno}
    </select>

    <select id="queryAll" resultType="Dept">
        select * from dept;
    </select>


</mapper>

编写DeptMapper接口
com/imperfect/mapper/DeptMapper.java

package com.imperfect.mapper;

import com.imperfect.api.pojo.Dept;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

@Mapper
@Repository
public interface DeptMapper {

    public boolean addDept(Dept dept);

    public Dept queryById(Long id);

    public List<Dept> queryAll();
}

编写DeptService接口类
com/imperfect/service/DeptService.java

package com.imperfect.service;

import com.imperfect.api.pojo.Dept;

import java.util.List;

public interface DeptService {
    public boolean addDept(Dept dept);

    public Dept queryById(Long id);

    public List<Dept> queryAll();
}

编写DeptServiceImpl实现类
com/imperfect/service/DeptServiceImpl.java

package com.imperfect.service;

import com.imperfect.api.pojo.Dept;
import com.imperfect.mapper.DeptMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class DeptServiceImpl implements DeptService {

    @Autowired
    private DeptMapper deptMapper;

    @Override
    public boolean addDept(Dept dept) {
        return deptMapper.addDept(dept);
    }

    @Override
    public Dept queryById(Long id) {
        return deptMapper.queryById(id);
    }

    @Override
    public List<Dept> queryAll() {
        return deptMapper.queryAll();
    }
}

编写DeptController类
com/imperfect/controller/DeptController.java

package com.imperfect.controller;

import com.imperfect.api.pojo.Dept;
import com.imperfect.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
public class DeptController {

    @Autowired
    private DeptService deptService;

    @PostMapping("/dept/add")
    public boolean addDept(Dept dept){
        return deptService.addDept(dept);
    }

    @GetMapping("/dept/get/{id}")
    public Dept queryById(@PathVariable("id")Long id){
        return deptService.queryById(id);
    }

    @PostMapping("/dept/list")
    public List<Dept> queryAll(){
        return deptService.queryAll();
    }

}

编写主启动类
com/imperfect/DeptProvider_8001.java

package com.imperfect;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


//启动类
@SpringBootApplication
public class DeptProvider_8001 {
    public static void main(String[] args) {
        SpringApplication.run(DeptProvider_8001.class,args);
    }
}

这里我们首先测试一下8001端口的这个服务提供者的服务是否能够正常运行
在这里插入图片描述
调用成功后,我们就可以开始编写服务消费者模块的代码

(5)构建服务消费者模块(spring-cloud-consumer-dept-8080)

在这里插入图片描述
由于服务消费者模块不需要进行数据库连接,只需要调用相对应的服务进行消费,所以我们进行配置的时候不需要配置对应的数据库连接地址。
首先我们编写一下对应的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.imperfect</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-cloud-consumer-dept-8080</artifactId>

    <dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
        <dependency>
            <groupId>com.imperfect</groupId>
            <artifactId>springcloud-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

编写spring的配置类
src/main/resources/application.yml

server:
  port: 8080

这里我们还需要把RestTemplate的模板注册到Spring容器当中进行托管
com/imperfect/config/RestConfig.java

package com.imperfect.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestConfig {

    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

编写调用消费者的Ctronller类
src/main/java/com/imperfect/controller/DeptConsumerController.java

package com.imperfect.controller;

import com.imperfect.api.pojo.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@RestController
public class DeptConsumerController {
    @Autowired
    private RestTemplate restTemplate;

    private static final String REST_URL_PREFIX = "http://localhost:8001";

    @RequestMapping("/consumer/dept/add")
    public boolean add(Dept dept) {
        return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept, Boolean.class);//提供多种便携访问远程http服务的方法,简单的RestFul服务模板
    }

    @GetMapping("/dept/get/{id}")
    public Dept queryById(@PathVariable("id")Long id){
        return restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/"+id, Dept.class);
    }

    @PostMapping("/dept/list")
    public List<Dept> queryAll(){
        return restTemplate.postForObject(REST_URL_PREFIX + "/dept/list",null,List.class);
    }



}

编写SpringBoot启动类
com/imperfect/DeptConsumer_8080.java

package com.imperfect;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

然后我们就来通过消费8080端口服务的接口来验证是否能够调用到服务提供者的接口
在这里插入图片描述
测试结果如下:
在这里插入图片描述
成功调用!

五、CAP原则

  只有系统达到一定的体量,就不得不面临分布式的问题,分布式系统的最大难点,就是各个节点的状态如何同步。 CAP 定理就是这方面的基本定理,由加州大学的计算机科学家 Eric Brewer 于 1998 年提出。
Eric Brewer 认为 分布式系统有三个指标
C : Consistency 一致性
A : Availability 可用性
P : Partition tolerance 分区容错性
并且 Eric Brewer 认为这三个指标不可能同时做到!这个结论就叫做 CAP 定理。
在这里插入图片描述

1、Partition tolerance

  Partition tolerance 直译 分区容错性, 我们将 Partition tolerance 拆开来理解。
Partition(分区):
分布式、分布式,系统分部在不同的地方才叫分布式, Eric Brewer 将"系统分部在不同的地方"这个概念取了个名字叫 Partition(分区) ,这就是分区的含义
tolerance(容错):
  既然分布式一定是 Partition(分区) 的,那么就会带来一个问题,不同区域之间通信会有延迟甚至是失败。比如一台服务器在 齐齐哈尔 一台服务器在 上海长距离通信可能导致超时或失败
总的来理解,就是 分布式系统的不同分区之间会产生“隔阂”
通常来讲一个分布式系统一定(有“隔阂”)满足 Partition tolerance(分区容错性)

2、Consistency

Consistency 中文叫做"一致性"
假设有如下分布式系统
在这里插入图片描述

G1、G2 是同一个服务的两个实例,分部在不同的区域

客户端发送一个请求被网关转发到 G1 上,将 G1 的 u0 改为了 u1
客户端想查看修改结果,于是又发送一个请求,这次被网关转发到 G2 上,结果发现还是 u0

上面的例子值在 G1 上是 u1, 在 G2 上是 u0,就不符合Consistency(一致性)
那么样才能满足 Consistency(一致性) 呢,可以让 G1 和 G2 同步
在这里插入图片描述

如图,G1 发生变化后同步到 G2, 这样 G1 和 G2 就一样了。
但问题并没有解决,上一章我们讲到 分布式系统的不同分区之间会产生“隔阂”,这意味着同步不是瞬间完成的而是有个过程的,甚至可能还会失败。假如在同步过程中,client 访问 G2 得到还是 u0
怎么解决这个问题呢?
在这里插入图片描述
答案是,在同步完成之前,不让 G2 对外提供服务,同步完成之后,在对外提供服务
经过上面的一系列操作,终于完成了 Consistency(一致性)

3、Availability

Availability 中文叫做"可用性" ,顾名思义指的是 分布式系统中,服务必须可以使用(访问)。

这一点实现起来比较简单,只有你的服务不挂,并且对外提供服务就可以了。

4、Consistency 和 Availability 难以共存

Consistency 那节提到,为了保证同步过程中的数据一致,我们让G2不对外提供服务。此时 G2就处于 不可用状态。 不满足Availability(可用性) 的条件。
但是我们如果不这么做,有无法保证 Consistency(一致性)
因此说 Consistency 和 Availability 难以共存

5、Consistency 和 Availability 难以共存的根本原因

  Consistency 和 Availability 难以共存的根本原因是因为 Partition tolerance(分区容错性)
回到 Consistency 那一小节,我们为什么要让 G2 停止对外提供服务,是因为同步需要一个过程。
  哪为什么同步需要一个过程呢?是因为 分布式系统的不同分区之间会产生“隔阂”,相互通信可能会有延迟或失败
等哪一天,通信技术发达了从中国到美国也能做的通信0延迟,分布式系统的不同分区之间就不会产生“隔阂”了。分区之前同步瞬间完成,我们也不需要让 G2 对外停止服务了。**Consistency 和 Availability ** 就可以共存!

特别鸣谢:https://www.ruanyifeng.com/blog/2018/07/cap.html

六、Eureka和Zookeeper比较

  Eureka和ZooKeeper都遵循CAP(Consistency, Availability, Partition-tolerance)原则。但是,任何分布式协调服务都不可能同时满足CAP中的三个要求。
  在分布式系统中,由于网络故障导致的丢包问题经常发生,所以网络分区容错是必须保证的。对于一致性和可用性,则需要根据实际需求合理取舍。

1、Eureka保证AP

Eureka中各个节点都是平等的。一个或多个节点挂掉不会影响正常节点的工作,剩余节点仍然可以提供注册和查询服务,只不过查到的信息可能不是最新的。

另外,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有检测到正常的心跳,那么Eureka就认为客户端与注册中心之间出现了网络故障,此时会出现以下几种情况:
  a. Eureka不再从注册列表中移除因为长时间没有收到心跳而应该过期的服务;
  b.Eureka仍然能够接受新的服务注册和查询请求,但不会将其同步到其它节点;
  c.当网络恢复正常后,新注册的服务信息会被同步到其它节点上作者获得授权,非商业转载请注明出处。

2、ZooKeeper保证CP

 a. ZK不能保证每次请求的可用性。任何时刻对ZK的访问请求都能得到一直的数据结果。在极端环境下,ZK可能会丢弃一些请求,应用程序需要重新请求才能获取结果。
  b.进行leader选举时ZK集群不可用。通过ZK获取服务列表时,当leader节点因为网络故障与其他节点失联时,剩余节点需要重新进行leader选举。问题在于,选举的时间太长,30-120s,这就导致选举期间注册服务瘫痪。

七、搭建Eureka

1、Eureka服务

项目结构:
在这里插入图片描述
首先引入创建eureka所需要的相关依赖

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

application.yml

server:
  port: 7001
#eureka配置

eureka:
  instance:
    hostname: localhost
  client:
    register-with-eureka: false #表示是否向eureka注册中心注册自己
    fetch-registry: false #如果为false,表示自己为注册中心
    service-url:  #监控页面
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

com/imperfect/EurekaServer_7001.java启动类

package com.imperfect;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer 
public class EurekaServer_7001 {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServer_7001.class,args);
    }
}

2、服务提供者

导入eureka作为客户端的相关依赖

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

application.yml

 #eureka的配置
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka/

启动类:com/imperfect/DeptProvider_8001.java
添加以下注解

@EnableEurekaClient

这里可以看到客户端provider-dept-8001成功注册到eureka服务当中
在这里插入图片描述

八、Eureka集群环境配置

首先我们需要新建两个新的模块
在这里插入图片描述
这两个模块可以根据eureka7001的配置进行搬迁,其中的eureka7001,eureka7002,eureka7003的application.yml配置文件都需要按照以下来进行配置

server:
  port: 7001
#eureka配置

eureka:
  instance:
    hostname: eureka7001.com
  client:
    register-with-eureka: false #表示是否向eureka注册中心注册自己
    fetch-registry: false #如果为false,表示自己为注册中心
    service-url:  #监控页面
      #集群(关联)
      defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/

并且我们windows系统下需要修改一下地址映射文件
在这里插入图片描述
添加如下配置
在这里插入图片描述
在服务提供者的application.yml文件中更改一下相关的eureka配置

 eureka的配置
eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/

集群部署效果如下:
7001
在这里插入图片描述
7002
在这里插入图片描述
7003
在这里插入图片描述

九、Ribbon

1、简介

  Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。

  Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单地说,就是在配置文件中列出Load Balancer(简称LB)后面所有机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。

1.简单地说就是将用户的请求平均分摊到多个服务上,从而达到系统的HA(高可用)。
常见的负载均衡软件有Nginx,LVS,硬件F5等。

​ 2.Ribbon本地负载均衡客户端和Nginx服务端负载均衡区别

​ Nginx是服务器负载均衡,客户端所有请求都会交给Nginx,然后由Nginx实现转发请求。即负载均衡是由服务端实现的。

​ Ribbon本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术。

2、自定义负载均衡算法

首先我们先导入一下对应的jar包

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

然后我们在项目启动类的父级目录的同级目录下新建一些我们自定义的负载均衡规则(这样做的原因是在启动Spring容器进行包扫描的时候,我们这里自定义负载均衡算法的bean不被注册到容器当中,我们在主启动类显式地去声明启动Ribbon客户端需要加载的配置文件的位置,这样就能够使代码更加清晰)
在这里插入图片描述
com/myrule/ImperfectRule.java

package com.myrule;

import com.netflix.loadbalancer.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author : Imperfect(lxm)
 * @Des:
 * @date : 2023/2/8  10:55
 */
@Configuration
public class ImperfectRule {
    @Bean
    public IRule myRule() {
        return new ImperfectRandomRule();
    }
}

这里我自定义了负载均衡的算法,其中的代码基础模板是参照randomRule的代码进行改造的。
规则:是对存活的服务限制每个服务访问五次,然后再去访问下个存活的服务

com/myrule/ImperfectRandomRule.java

package com.myrule;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;

import java.util.List;
import java.util.concurrent.ThreadLocalRandom;


public class ImperfectRandomRule extends AbstractLoadBalancerRule {

    //每个服务,访问5次,换下一个服务
    private int total = 0;
    private int currentIndex = 0;

    /**
     * Randomly choose from all living servers
     */
    // @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        }
        Server server = null;
        while (server == null) {
            if (Thread.interrupted()) {
                return null;
            }
            List<Server> upList = lb.getReachableServers(); //获得活着的服务
            List<Server> allList = lb.getAllServers();  //获得全部的服务

            int serverCount = allList.size();
            if (serverCount == 0) {
                /*
                 * No servers. End regardless of pass, because subsequent passes
                 * only get more restrictive.
                 */
                return null;
            }

//            int index = chooseRandomInt(serverCount); //生成区间随机数
//            server = upList.get(index); //从活着的服务中,随机获取一个


            if (total < 5) {
                server = upList.get(currentIndex);
                total++;
            } else {
                total = 0;
                currentIndex++;
                if (currentIndex >= upList.size()) {
                    currentIndex = 0;
                }
                server = upList.get(currentIndex);
            }


            if (server == null) {
                /*
                 * The only time this should happen is if the server list were
                 * somehow trimmed. This is a transient condition. Retry after
                 * yielding.
                 */
                Thread.yield();
                continue;
            }

            if (server.isAlive()) {
                return (server);
            }

            // Shouldn't actually happen.. but must be transient or a bug.
            server = null;
            Thread.yield();
        }

        return server;

    }

    protected int chooseRandomInt(int serverCount) {
        return ThreadLocalRandom.current().nextInt(serverCount);
    }

    @Override
    public Server choose(Object key) {
        return choose(getLoadBalancer(), key);
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
        // TODO Auto-generated method stub

    }
}

十、Feign接口方式调用服务

首先我们新建一个项目springcloud-consumer-dept-feign
导入相关的依赖

<?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.imperfect</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springcloud-consumer-dept-feign</artifactId>

    <dependencies>

        <!--        feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>
        <!--        Ribbon-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>
        <!--        eureka-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
            <version>1.4.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.imperfect</groupId>
            <artifactId>springcloud-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

编写一下对应的application.yml文件

server:
  port: 6060
  #Eureka配置
eureka:
  client:
    register-with-eureka: false #不向Eureka中注册自己
    service-url:
      defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/,http://localhost:7003/eureka/

编写imperfect/controller/DeptConsumerController.java

package imperfect.controller;

import com.imperfect.api.pojo.Dept;
import com.imperfect.api.service.DeptCilentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class DeptConsumerController {
    @Autowired
    private DeptCilentService deptCilentService;

//    @GetMapping("/dept/get/{id}")
//    public Dept queryById(@PathVariable("id")Long id){
//        return deptCilentService.queryById();
//    }

    @RequestMapping("/consumer/dept/list")
    public List<Dept> queryAll(){
        return deptCilentService.queryAll();
    }
}

编写imperfect/FeignDeptConsumer_8080.java

package imperfect;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages="com.imperfect.api")//指定feign客户端扫描的包
public class FeignDeptConsumer_8080 {
    public static void main(String[] args) {
        SpringApplication.run(FeignDeptConsumer_8080.class,args);
    }
}

然后我们编写一下springcloud-api模块的代码
com/imperfect/api/service/DeptCilentService.java

package com.imperfect.api.service;

import com.imperfect.api.pojo.Dept;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import java.util.List;

/**
 * @author : Imperfect(lxm)
 * @Des:
 * @date : 2023/2/8  15:07
 */
@FeignClient(value = "SPRING-CLOUD-PROVIDER-DEPT")
public interface DeptCilentService {


    @PostMapping("/dept/list")
    public List<Dept> queryAll();
}

效果展示
在这里插入图片描述

十一、Hystrix

1、服务熔断

1、简介

  熔断简单来说就是在单个服务出现问题,不可用时,为了避免引发更严重的问题,导致整个服务链路不可用的情况下,可以采用熔断的方式来避免。熔断一般情况下意味着服务的降级,可以理解为是一种异常兜底策略,需要服务的上游调用方来实现。
  在访问量比较高的情况下,客户端访问A节点,A节点一个依赖的服务节点B出现延迟(或者不可用),这种情况下,无论是重试策略(重试3次)也好,或者超时策略(超过1S返回失败或者默认结果)也好,都会比正常请求消耗更多的资源,这时在流量高的场景有可能造成A服务资源被沾满,从而导致A服务其他接口也出现延迟或者不可用情况,再严重一些可能会出现雪崩,A服务依赖的上游也不可用,进而整个集群链路崩溃。
  因此,服务异常时可以通过熔断的方式来进行快速的失败,避免后续流量继续请求到服务B,避免雪崩。
  熔断更多的是指服务之间的熔断,熔断通常都会有恢复策略。

2、搭建环境

首先我们先导入一下对应的jar包

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

编写处理熔断的代码
com.imperfect.controller.DeptController

package com.imperfect.controller;

import com.imperfect.api.pojo.Dept;
import com.imperfect.service.DeptService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;


@RestController
public class DeptController {

    @Autowired
    private DeptService deptService;



    @GetMapping("/dept/get/{id}")
    @HystrixCommand(fallbackMethod = "hystrixGet")
    public Dept queryById(@PathVariable("id")Long id){
        Dept dept = deptService.queryById(id);
        if(dept==null){
            throw new RuntimeException("不存在该用户");
        }
        return dept;
    }


    //备选方法
    public Dept hystrixGet(@PathVariable("id")Long id){
        return new Dept()
                .setDeptno(id)
                .setDname("没有对应的信息为null")
                .setDb_source("no this datasource");

    }
}

在主启动类中添加对熔断的支持
com/imperfect/DeptProviderHystrix_8001.java

package com.imperfect;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;


//启动类
@SpringBootApplication
@EnableEurekaClient
//添加对熔断的支持
@EnableCircuitBreaker
public class DeptProviderHystrix_8001 {
    public static void main(String[] args) {
        SpringApplication.run(DeptProviderHystrix_8001.class,args);
    }
}

测试结果
在这里插入图片描述
这里我们数据库中并没有id为7的数据,所以会进行熔断。

2、服务降级

  降级是从系统功能优先级的角度考虑如何应对系统故障。

  服务降级指的是当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。

服务降级是针对在客户端的:
首先我们在api模块下新建一个FallBack工厂
com/imperfect/api/service/DeptCilentServiceFallBackFactory.java

package com.imperfect.api.service;

import com.imperfect.api.pojo.Dept;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * @author : Imperfect(lxm)
 * @Des:
 * @date : 2023/2/10  14:31
 */
//降级
@Component
public class DeptCilentServiceFallBackFactory implements FallbackFactory {

    @Override
    public DeptCilentService create(Throwable throwable) {
        return new DeptCilentService() {
            @Override
            public List<Dept> queryAll() {
                return null;
            }

            @Override
            public Dept queryById(Long id) {
                return new Dept()
                        .setDeptno(id)
                        .setDname("没有对应的信息为null,该服务已被降级")
                        .setDb_source("no this datasource");
            }
        };
    }
}

并且在com/imperfect/api/service/DeptCilentService.java中添加绑定fallback工厂

@FeignClient(value = "SPRING-CLOUD-PROVIDER-DEPT",fallbackFactory = DeptCilentServiceFallBackFactory.class)

在springCloud-consumer-dept-feign模块下开启熔断降级功能
application.yml

#开启熔断降级
feign:
  hystrix:
    enabled: true

测试结果如下
搜索具有返回值的数据
在这里插入图片描述
搜索为null的数据
在这里插入图片描述

3、区别

服务熔断应用于服务端,服务降级应用于客户端

十二、Zuul

  zuul 是netflix开源的一个API Gateway 服务器, 本质上是一个web servlet应用。
  Zuul 在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架,相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门。
  zuul的核心是一系列的filters, 其作用可以类比Servlet框架的Filter,或者AOP。
首先我们新建一个网关模块
在这里插入图片描述
其中导入的核心jar包就只有

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

com/imperfect/ZuulApplication.java

package com.imperfect;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

/**
 * @author : Imperfect(lxm)
 * @Des:
 * @date : 2023/2/10  17:05
 */
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication.class,args);
    }
}

编写application.yml配置文件

server:
  port: 9527

spring:
  application:
    name: springcloud-zuul
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/,http://localhost:7003/eureka/
  instance:
    instance-id: springcloud-zuul #修改eureka上的描述信息
    prefer-ip-address: true
zuul:
  routes:
    mydept.serviceId: spring-cloud-provider-dept
    mydept.path: /mydept/**
  ignored-services: "*" #不能再使用这个路径访问了

测试结果
在这里插入图片描述

十三、Config配置中心

首先我们新建一个模块作为配置中心服务端
在这里插入图片描述
导入相关的依赖

<?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.com.imperfect</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springcloud-config-server-3344</artifactId>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-server</artifactId>
        <version>2.1.1.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
</dependencies>
</project>

编写配置文件application.yml

server:
  port: 3344
spring:
  application:
    name: springcloud-config-server
  cloud:
    config:
      server:
        git:
          uri: 仓库地址,即配置文件地址 #用https,不是ssh
          username: 用户名
          password: 密码

编写主启动类

package com.imperfect;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;

/**
 * @author : Imperfect(lxm)
 * @Des:
 * @date : 2023/2/13  10:30
 */
@SpringBootApplication
@EnableConfigServer //开启注解,配置服务
public class Config_Server_3344 {
    public static void main(String[] args) {
        SpringApplication.run(Config_Server_3344.class,args);

    }}

凡是分布式模块当中的配置文件都可以通过访问配置中心的服务进行访问,这里我就举例其中一个eureka服务来实现获取配置中心的配置文件。
其中我们首先需要了解一下
bootstrap.yml(bootstrap.properties)用来程序引导时执行,应用于更加早期配置信息读取,如可以使用来配置application.yml中使用到参数等

application.yml(application.properties) 应用程序特有配置信息,可以用来配置后续各个模块中需使用的公共参数等。

这里我们首先新建一个模块springcloud-config-eureka-7001
在这里插入图片描述
导入依赖

<?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.com.imperfect</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springcloud-config-eureka-7001</artifactId>

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

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

    </dependencies>
</project>

application.yml

spring:
  application:
    name: springcloud-config-eureka-7001

bootstrap.yml

spring:
  cloud:
    config:
      name: config-eureka
      label: master
      uri: http://localhost:3344
      profile: dev

主启动类EurekaServer_7001

package com.imperfect;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaServer_7001 {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServer_7001.class,args);
    }
}

测试结果
在这里插入图片描述
成功启动!!!
到此为止,关于SpringCloud NetFlix的知识点回顾就结束了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值