SpringCloud

发布于 2022-02-21  144 次阅读


Spring Cloud

Eureka、Ribbon、Feign、Hystrix、Zuul

Eureka:订单服务想要调用库存服务、仓储服务,或者积分服务,怎么调用?

aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAxOC8xMS83LzE2NmViZmZjYjdjZTMxYjg_aW1hZ2VWaWV3Mi8wL3cvMTI4MC9oLzk2MC9mb3JtYXQvd2VicC9pZ25vcmUtZXJyb3IvMQ

Feign : 难道订单服务要自己写一大堆代码,跟其他服务建立网络连接,然后构造一个复杂的请求,接着发送请求过去,最后对返回的响应结果再写一大堆代码来处理吗?

Feign的一个关键机制就是使用了动态代理

img

Ribbon: 库存服务部署在了5台机器上 Feign怎么知道该请求哪台机器呢?

负载均衡 轮询算法

img

Hystrix 积分服务不幸的挂了,每次订单服务调用积

分服务的时候,都会卡住几秒钟,然后抛出—个超时异常。 雪崩 熔断 降级

img

Zuul: 微服务网关 负责网络路由的

如果前端、移动端要调用后端系统,统一从Zuul网关进入,由Zuul网关转发请求给对应的服务

1111

image-20220217213824698

微服务架构 ---》 新架构

模块化 功能化

用户 支付 签到 娱乐 ....

人越来越多 一台服务器解决不了 在增加服务器 横向

假如 a 服务器 占用 98% 资源 b 服务器 只占用 10% 负载均衡

将原来的整体项目 分成模块化 用户就是一个单独的项目 签到也是一个单独的项目 项目和项目之间需要通信 怎么样通信

用户十分多 而签到少 给用户多一个服务 给签到一点服务

微服务架构问题:

四个核心问题:

  • 这么多服务 用户该如何去访问?
  • 这么多服务 服务之间该如何去通信?
  • 这么多服务 怎么治理?
  • 服务停了 怎么办?

Spring Cloud :是一套生态 就是用来解决分布式架构的四个问题

Spring Cloud 是基于 spring Boot 的

  1. Spring Cloud NetFlix 出来的一套解决方案 一站式解决 我们都可以拿去使用

Api 网关 zuul 组件

Feign -- 》 httpClient --- 》 http 通信方式 同步并且阻塞

服务注册于发现 Eureka

熔断机制 Hystrix

2108 年 netFlix 宣布无限期停止维护 生态不在维护 就会脱节

  1. Apache Dubbo zookeeper 第二套解决方案

Api : 没有 要么使用第三方组件 要么自己实现

Dubbo 是一个高性能的基于java实现的 RPC 通信框架

服务注册与发现 zookeeper :(动物管理者) (hadoop hive)

熔断机制: 没有 借助了 Hystrix

  1. spring Cloud Alibaba 一站式解决

  2. 服务网格 Server Mesh

万变不离其宗 一通百通!

  • Api 网关 ,服务路由
  • HTTP RPC框架 异步调用
  • 服务注册与发现 高可用
  • 熔断机制 服务降级

一切的问题 都是由于 网咯不可靠

微服务技术栈

微服务条目 技术
服务开发 SpringBoot Spring SpringMVC
服务配置与管理 Netflix 公司的 Archaius , 阿里的 Diamond
服务注册与发现 Eureka, Consul, Zookeeper
服务调用 Rest , RPC , gRPC
服务熔断器 Hystrix,Envoy
负载均衡 Ribbon, Nginx
服务接口调用(客户端调用服务的简单工具) Feign
消息队列 Kafka, RabbitMQ, ActiveMQ
服务配置中心管理 SpringCloudConfig, Chef
服务路由(API 网关) Zuul
服务监控 Zabbix , Nagios, Metrics, pecator
全链路追踪 Zipkin, Brave , apper
服务部署 Docker,OpenStack, Kubernetes
数据流操作开发包 Spring Cloud Stram(封装 Redis Rabbit kafka 等发送接收消息)
事件消息总线 SpringCloud Bus

SpringCloud 和 SpringBoot 关系

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

    QQ截图20211111203812

SpringCloud 基本环境搭建

以下都是创建的 maven 项目

QQ截图20211112141043

SpringCloud

pom.xml

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

<properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>

    <junit.version>4.13.2</junit.version>
    <lombok.version>1.18.22</lombok.version>
    <log4j.service>1.2.17</log4j.service>
</properties>

<dependencyManagement>
    <dependencies>
        <!-- spring-cloud-dependencies -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Hoxton.SR12</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!--springboot-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.3.12.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!--数据库-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.27</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.6</version>
        </dependency>
        <!--SpringBoot 启动器-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.6</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${log4j.service}</version>
        </dependency>
    </dependencies>
</dependencyManagement>

SpringCloud-api

pom.xml

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

数据库: db01

#数据库存在则删除
drop database if exists db01;
#创建数据库
create database db01;
#设置编码
alter database db01 character set utf8;

use db01;

DROP TABLE IF EXISTS dept;
CREATE TABLE dept
(
    deptno int primary key auto_increment,
    dname VARCHAR(50) not null,
    db_source VARCHAR(50) not NULL
);

INSERT into dept (dname,db_source) VALUES('开发部',DATABASE());
INSERT into dept (dname,db_source) VALUES('人事部',DATABASE());
INSERT into dept (dname,db_source) VALUES('财务部',DATABASE());
INSERT into dept (dname,db_source) VALUES('市场部',DATABASE());
INSERT into dept (dname,db_source) VALUES('运维部',DATABASE());

SELECT * from dept;

Dept

package com.zhang.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 int deptno;
    private String dname;
    private String db_source;  //指定当前属于哪个数据库

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

    /**
     * 链式写法
     * Dept dept = new Dept();
     * dept.setDeptno(1).setDname("xiaohuihui").setDb_source("001");
     */

}

SpringCloud-provider-dept-8001

QQ截图20211112141658

pom.xml

<dependencies>
    <!--我们要拿到实体类 所以需要配置 api module-->
    <dependency>
        <groupId>org.example</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>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>

    <!--jetty 同 Tomact-->
    <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>
</dependencies>

application.yml

server:
  port: 8001

# mybatis 配置
mybatis:
  type-aliases-package: com.zhang.pojo
  config-location: classpath:mybatis/mybatis-config.xml
  mapper-locations: classpath:mybatis/mapper/*.xml

# Spring 配置
spring:
  application:
    name: SpringCloud-provider-dept
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
    username: root
    password:

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!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

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.zhang.mapper.DeptMapper">

    <insert id="addDept" parameterType="Dept">
        insert into dept (dname,db_source)
        values (#{dname},DATABASE());
    </insert>

    <select id="queryDeptById" resultType="Dept" parameterType="int">
        select *
        from dept where deptno = #{deptno};
    </select>

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

</mapper>

DeptMapper

@Mapper
@Repository
public interface DeptMapper {

    public boolean addDept(Dept dept);

    public Dept queryDeptById(int id);

    public List<Dept> queryAll();
}

DeptController

// 提供 restFul 服务
@RestController
public class DeptController {
    @Autowired
    private DeptService deptService;

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

    @GetMapping("/dept/selById/{id}")
    public Dept queryDeptById(@PathVariable("id") int id){
        return deptService.queryDeptById(id);
    }

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

DeptProvider_8001

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

SpringCloud-consumer-dept-80

QQ截图20211112142102

pom.xml

<dependencies>
    <!--实体类 + web-->
    <dependency>
        <groupId>org.example</groupId>
        <artifactId>SpringCloud-api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </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>
</dependencies>

application.yml

server:
  port: 80

ConfigBean

@Configuration
public class ConfigBean {
    /**
     * 将 RestTemplate 注册到 bean 中
     * @return
     */
    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

DeptConsumerController

@RestController
public class DeptConsumerController {
    // 消费者 不应该有 service 层
    // 可以通过 RestFul 调用远程服务  (通过 url)
    //  RestTemplate 模板供我们直接调用 注册到spring 中

    @Autowired
    private RestTemplate restTemplate;    // 提供多种便捷访问远程 http 服务的方法 简单的restFul 服务模板
    // 远程请求的地址 前缀
    private static final String REST_URL_PREFIX = "http://localhost:8001/";

    /**
     * 通过 ID 查询部门
     * @param id
     * @return
     */
    @RequestMapping("/consumer/dept/get/{id}")
    public Dept get(@PathVariable("id") int id){
        return restTemplate.getForObject(REST_URL_PREFIX+"/dept/selById"+id,Dept.class);
    }

    /**
     * 添加一个部门
     * @param dept
     * @return
     */
    @RequestMapping("/consumer/dept/add")
    public Boolean add(Dept dept){
        return restTemplate.postForObject(REST_URL_PREFIX+"/dept/add",dept,Boolean.class);
    }

    /**
     * 查询所有部门
     * @return
     */
    @RequestMapping("/consumer/dept/list")
    public List<Dept> list(){
        return restTemplate.getForObject(REST_URL_PREFIX+"/dept/query",List.class);
    }
}

DeptConsumer_80

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

Eureka 服务注册与发现

Eureka 是什么?

  • NetFlix 在设计 Eureka 时,遵循的就是AP 原则
  • Eureka 是 NetFlix 的一个子模块 也是核心模块之一, Eureka 是一个基于 REST 的服务 用于定义服务 以实现云端中间层服务发现和故障转移 服务发现与注册对微服务是非常重要的 有了服务发现与注册 只需要使用服务的标志符 就可以访问到服务 而不需要修改服务调用的配置文件 功能类似于 Dubbo 的注册中心 比如 Zookeeper

QQ截图20211112161451

原理讲解

  • SpringCloud 封装的 NetFlix 公司开发的 Eureka 模块来实现服务注册与发现 (对比 Zookeeper)
  • Eureka 采用 C-S 的设计架构 EurekaServer 作为服务注册功能的服务器 他是服务注册中心
  • 系统中的其他微服务 使用 Eureka 的客户端连接到 EurekaServer 并维持心跳链接 这样系统维护人员就可以通过EurekaServer 来监控系统中的个个微服务是否正常运行 SpringCloud 的一些其他模块(比如zuul)就可以通过 EurekaServer 来发现系统中的其他微服务 并执行相关逻辑
  • 和 Dubbo 架构对比

SpringCloud-eureka-7001

<dependencies>
    <!-- spring-cloud-starter-eureka-server -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka-server</artifactId>
        <version>1.4.7.RELEASE</version>
    </dependency>
    <!--热部署工具-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
    </dependency>
</dependencies>

application.yml

server:
  port: 7001

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

Eureka_7001

// 主启动类
@SpringBootApplication
@EnableEurekaServer  // EnableEurekaServer 服务端的启动类 可以接收别人注册进来
public class Eureka_7001 {
    public static void main(String[] args) {
        SpringApplication.run(Eureka_7001.class,args);
    }
}

启动 访问 :http://localhost:7001/

QQ截图20211112171133

将 8001 注册到 eureka 注册中心

SpringCloud-provider-dept-8001 添加

pom 依赖

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

application.yml

# eureka 配置
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka/
  instance:
    instance-id: SpringCloud-provider-dept-8001   # 修改 eureka 上默认描述信息

# info
info:
  app.name: xiaotao-springcloud
  company.name: www.xiaotao.cloud

在主启动类 添加

@EnableEurekaClient  // 在服务启动后自动注册到 Eureka 中

先启动 7001 在启动 8001

QQ截图20211112232409

点击 SpringCloud-provider-dept-8001 可以看到当前的一些配置信息

QQ截图20211112232437

eureka 自我保护机制

当 8001 因为某种原因突然停止 eureka 会通过保护机制 监控 当30秒还没有监控到 8001 的心跳 就会报如下 红

但是 8001 服务依然存在 等8001 启动成功 eureka 恢复正常

可以避免一些信息的丢失

QQ截图20211112232654

获取注册进来的微服务的一些信息 服务发现

多人开发时 有很多服务 方便开发 与管理

在 8001 获取

DeptController 添加

// 获取一些配置信息 得到具体的 微服务
@Autowired
private DiscoveryClient client;

/**
     * 获取注册进来的微服务的一些信息
     * @return
     */
@GetMapping("/dept/discovery")
public Object discovery(){
    // 获取 微服务 清单
    List<String> services = client.getServices();
    System.out.println("discovery=>services:"+services);

    // 得到具体的微服务信息 通过具体的微服务 id:applicationName
    List<ServiceInstance> instances = client.getInstances("SPRINGCLOUD-PROVIDER-DEPT");
    for (ServiceInstance instance : instances) {
        System.out.println(
            instance.getHost()+"\t"+
            instance.getServiceId()+"\t"+
            instance.getPort()+"\t"+
            instance.getUri()
        );
    }
    return this.client;
}

在主启动类添加

@EnableDiscoveryClient  // 服务发现

启动访问:

http://localhost:8001/dept/discovery

QQ截图20211112235456

在控制台 可以看到输出的配置信息

QQ截图20211112235552

eureka 集群配置

QQ截图20211113001401

在搭建两个注册中心 形成 集群

7002 7003 注册中心 跟 7001 一样 只需要修改 对应端口号即可

启动访问

QQ截图20211113001639

说明搭建成功

现在三个注册中心 是单独的 我们需要使他们成为一个整体

QQ截图20211113001339

QQ截图20211113002245

域名映射 这样 我们访问 eureka7001.com eureka7002.com eureka7003.com 都代表 127.0.0.1

这里 只是为了让我们能够方便识别 假装在 三个服务器上 方便理解

修改上面三个配置文件 yml

server:
  port: 7001

# eureka 配置
eureka:
  instance:
    hostname: eureka7001.com  # eureka 服务端的实例名称
  client:
    register-with-eureka: false # 表示是否向 eureka 注册中心注册自己
    fetch-registry: false  # false : 表示 自己是注册中心
    service-url:   # 监控中心的注册地址
      # 单机
      # defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
      # 集群 (关联)
      defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
server:
  port: 7002

# eureka 配置
eureka:
  instance:
    hostname: eureka7002.com  # eureka 服务端的实例名称
  client:
    register-with-eureka: false # 表示是否向 eureka 注册中心注册自己
    fetch-registry: false  # false : 表示 自己是注册中心
    service-url:   # 监控中心的注册地址
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7003.com:7003/eureka/

其他 同理 只要修改 defaultZone 即可

将 8001 发布在集群上 之前是在 单机上

修改 8001 application.yml

# eureka 配置
eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
  instance:
    instance-id: SpringCloud-provider-dept-8001   # 修改 eureka 上默认描述信息

启动

7001 7002 7003 8001

访问 7003

QQ截图20211113004609

QQ截图20211113005118

QQ截图20211113005651

现在假如 7002 挂了

但是 7001 7003 仍然在运行 不会对 数据产生影响

CPA 原则 及 对比 Zookeeper

RDBMS (Mysql Oracle sqlServer) ===》 ACID

NoSQL (redis mongDB) ===》 CAP

ACID:

  • A : 原子性
  • C: 一致性
  • I : 隔离性
  • D: 持久性

CAP:

  • C: 强一致性
  • A:可用性
  • P:分区容错性

CAP 的三进二: CA AP CP

CPA 理论核心

  • 一个分布式系统不可能同时很好的满足一致性 可用性,分区容错性三个需求
  • 根据CAP 原则 将 NoSQL 数据库分成了 满足 CA 原则 满足CP 原则 满足AP原则 三大类
    • CA: 单点集群 满足一致性 可用性的系统 通常可扩展性较差
    • CP:满足一致性 分区容错性的系统 通常性能不是特别高
    • AP:满足可用性 分区容错性的系统 通常可以对一致性要求低一点
作为服务注册中心,eureka 比 zookeeper 好在那里?

著名的 ACP 理论指出,一个分布式系统不能同时满足C(一致性) A(可用性) P(分区容错性)

由于分区容错性 p 在分布式系统中是必须要保证的 因此只能在A和C之间进行权衡

  • zookeeper:保证的是CP;
  • eureka:保证的是AP;

zookeeper 保证的是 CP;

当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟前的注册信息 但不能接受服务直接down掉不可用。也就是说 服务注册功能对可用性的要求高于一致性 但是zookeeper 会出现这样一种情况 当master节点因为网络故障与其他节点失去联系时 剩余节点会重新进行 leader 选举 问题在于 leader 选举 的时间过长 30~120s 且选举期间整个zookeeper 集群都是不可用的 这就导致在选举期间注册服务瘫痪 在云部署的环境下 因为网络问题使得zookeeper 集群失去maoter 节点是较大概率会发生的事件 虽然服务最终会恢复 但在漫长的选举时间导致注册长期不可用是不能容忍的

eureka 保证的是AP;

eureka 在设计时优先保证可用性 eureka 的每个节点都是平等的 几个节点挂掉都不会影响正常节点的工作 剩余的节点依旧可以提供注册和查询服务 eureka 只要有一个节点还在 就能保证注册服务的可用性 只是查询到的数据可能不是最新的

除此之外 eureka 还有一种自我保护机制 如果在 15分钟内超过85%的节点都没有正常工作 那么 Eureka 就认为客户端与注册中心出现了网络故障 可能会出现:

  • eureka 不在从注册列表移除因长时间没有收到心跳而应该过期的服务
  • eureka 任然能够接受新服务的注册的查询的请求但是不会同步到其它节点(即保证当前几点依然可以使用)
  • 当网络稳定时 当前实例新的注册信息会被同步到其它节点

因此 eureka 可以很好的应对网络故障导致部分节点失去联系的情况 而不会像 zookeeper 那样使整个注册服务瘫痪

ribbon 负载均衡

什么是 ribbon?

  • spring coloud ribbon 是基于 NetFlix Ribbon 实现的一套客户端负载均衡的工具
  • Ribbon 是 NetFlix 发布的开源项目 主要功能是提供客户端的软件负载均衡算法 将NetFlix 的中间层服务连接到一起 Ribbon 的客户端提供一系列完整的配置项:连接超时 重试 等等 在配置文件列出LoadBalancer (简称 LB:负载均衡) 后面所有的机器 Ribbon 会自动帮助你基于某种规则(如简单轮询 随机连接..) 去连接这些机器 我们也很容易使用Ribbon 实现自定义的负载均衡算法

ribbon 能干嘛?

  • LB: 及负载均衡 在微服务或分布式集群中经常用的一种应用
  • 负载均衡简单的说就是将用户的请求平摊的分配到多个服务器上 从而达到系统的HA(高可用)
  • 常见的负载均衡软件有 Nginx ,Lvs ...
  • dubbo,SpringCloud 中均给我们提供了负载均衡 SpringCloud的负载均衡算法可以自定义
  • 负载均衡简单分类:
    • 集中式LB
    • 即在服务的提供方和消费方之间使用独立的LB设施 如Nginx 由该设施负责把访问请求通过某种策略转发至服务的提供方
    • 进程式LB
    • 将LB逻辑集成到消费方 消费方从服务注册中心获知有哪些地址可用 然后在从这些地址中先出一个合适的服务器;
    • Ribbon 就属于进程式LB 它只是一个类库 集成于消费方进程 消费方通过它来获取到服务提供方的地址

横向添加两个数据库

db02 db03

QQ截图20211118094409

SpringCloud-provider-dept-8002

复制 SpringCloud-provider-dept-8001

application.yml

server:
  port: 8002

# mybatis 配置
mybatis:
  type-aliases-package: com.zhang.pojo
  config-location: classpath:mybatis/mybatis-config.xml
  mapper-locations: classpath:mybatis/mapper/*.xml

# Spring 配置
spring:
  application:
    name: SpringCloud-provider-dept
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db02?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
    username: root
    password:

# eureka 配置
eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
  instance:
    instance-id: SpringCloud-provider-dept-8002   # 修改 eureka 上默认描述信息

# info
info:
  app.name: xiaotao-springcloud
  company.name: www.xiaotao.cloud
@SpringBootApplication
@EnableEurekaClient  // 在服务启动后自动注册到 Eureka 中
@EnableDiscoveryClient  // 服务发现
public class DeptProvider_8002 {
    public static void main(String[] args) {
        SpringApplication.run(DeptProvider_8002.class,args);
    }
}

SpringCloud-provider-dept-8003

同理

SpringCloud-consumer-dept-80

导入依赖

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

报错 : 原因 service 写错

No instances available for SPRINGCLOUD-PROVIDER-DEPT

QQ截图20211118093850

每次查询都会从不同的服务器查询 采用轮循的方式

QQ截图20211118100525

QQ截图20211118100535

负载均衡修改默认查询方式

查询方式:

IRule : 负载均衡的接口
    AvailabilityFilteringRule:会先过滤掉 跳闸 访问故障的服务  对剩下的进行轮循
    RoundRobinRule:轮循 默认
    RandomRule: 随机
    RetryRule:会先按照轮循获取服务 如果服务获取失败 则会在指定的时间内进行重试

在 80 ConfigBean 添加

/**
     * 使用随机算法进行获取
     * @return
     */
@Bean
public IRule myIRule(){
    return new RandomRule();
}

负载均衡 自定义算法

不要和主启动类在同一级下

QQ截图20211118142629

MyRule

public class MyRule extends AbstractLoadBalancerRule {
    // 每个服务 访问5次 换下一个服务 总共3个服务
    private int total = 0;  //被调用的次数
    private int currentIndex = 0; // 当前是谁在提供服务

    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) {
                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) {
                Thread.yield();
                continue;
            }

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

            server = null;
            Thread.yield();
        }

        return server;

    }

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

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

    }
}

TaoRule

@Configuration
public class TaoRule {

    @Bean
    public IRule myIRule(){
        return new MyRule();   //使用自定义的策略
    }
}

主启动类

// 在微服务启动时就能加载我们自定义的ribbon类
@RibbonClient(name = "SPRINGCLOUD-PROVIDER-DEPT",configuration = MyRule.class)

Feign 负载均衡

feign 是声明的wer service 客户端 它让微服务之间的调用变的更简单 类似于controller 调用 service SpringCloud 集成了Ribbon 和Eureka 可在使用 Feign 时提供负载均衡的http 客户端

只要创建一个接口添加注解即可

Feign 能做什么?
  • Feign 旨在使编写java http 客户端更加简单
  • 前面在使用 Ribbon + RestTemplate时 利用RestTemplate 对http 请求进行封装处理 形成一套模板化的调用方法。 但在实际开发中 由于对服务依赖的调用不止一处 往往一个接口会被多出调用 所以通常会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用 Feign 在此基础上进行进一步封装 在Feign 的实现下, 我们只需要创建一个接口并使用注解的方式配置 (类似于以前的Dao 接口 上标注 Mapper 注解 现在是一个微服务接口上面标注一个Feign 注解即可)
Feign 集成了 Ribbon

利用Ribbon维护了 MicroServiceCloud-Dept 的服务列表信息 并且通过轮循实现了客户端的负载均衡 而与 Ribbon 不同的是 通过 Feign 只需要定义定义服务绑定接口且以声明式的方式 实现了服务调用

SpringCloud-consumer-dept-feign

QQ截图20211219095837

导入pom.xml

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

DeptClientService

@Component
@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT")
public interface DeptClientService {
    @GetMapping("/dept/get/{id}")
    public Dept queryById(@PathVariable("id") int id);
    @GetMapping("/dept/list")
    public List<Dept> queryAll();
    @PostMapping("/dept/add")
    public Boolean addDept(Dept dept);
}

QQ截图20211219095945

复制:

SpringCloud-consumer-dept-80

DeptConsumerController

@RestController
public class DeptConsumerController {
    @Autowired
    public DeptClientService deptClientService = null;

    /**
     * 通过 ID 查询部门
     * @param id
     * @return
     */
    @RequestMapping("/consumer/dept/get/{id}")
    public Dept get(@PathVariable("id") int id){
        return deptClientService.queryById(id);
    }

    /**
     * 添加一个部门
     * @param dept
     * @return
     */
    @RequestMapping("/consumer/dept/add")
    public Boolean add(Dept dept){
        return deptClientService.addDept(dept);
    }

    /**
     * 查询所有部门
     * @return
     */
    @RequestMapping("/consumer/dept/list")
    public List<Dept> list(){
        return deptClientService.queryAll();
    }
}

FeginDeptConsumer_80

// ribbon 和 Eureka 整合后 客户端可以直接调用 不用关心IP地址及端口号
// 主启动类
@SpringBootApplication
@EnableEurekaClient  // 在服务启动后自动注册到 Eureka 中
@EnableFeignClients(basePackages = "com.zhang")
public class FeginDeptConsumer_80 {
    public static void main(String[] args) {
        SpringApplication.run(FeginDeptConsumer_80.class,args);
    }
}

Hystrix

Hystrix : 服务熔断 服务器端

QQ截图20211219101524

image-20220217084048393

hystrix-logo-tagline-640

soa-1-640

soa-2-640

soa-4-isolation-640

Hystrix 使用

导入依赖

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

controller

@GetMapping("/dept/get/{id}")
@HystrixCommand(fallbackMethod = "hystrixGet")  // 出现异常或错误就会执行 hystrixGet 方法
public Dept get(@PathVariable("id") Integer id){
    Dept dept = deptService.queryDeptById(id);

    if(dept == null){
        throw new RuntimeException("id=>"+id+",不存在该用户,或者信息无法找到~");
    }

    return dept;
}

// hystrix : 备选方法
public Dept hystrixGet(@PathVariable("id") Integer id){
    return new Dept()
        .setDeptno(id)
        .setDname("id=>"+id+",不存在该用户,或者信息无法找到~")
        .setDb_source("no this database in MySQL");
}

添加熔断支持(在主启动类)

@EnableCircuitBreaker  // 添加对熔断的支持

显示服务的IP地址

eureka.instance.prefer-ip-address: true   # true: 可以显示服务的ip

服务降级 客户端

SpringCloud-api

// 服务降级
@Component
public class DeptClientServiceFallbackFactory implements FallbackFactory {
    @Override
    public DeptClientService create(Throwable throwable) {
        return new DeptClientService() {
            @Override
            public Dept queryById(int id) {
                return new Dept()
                        .setDeptno(id)
                        .setDname("id=>"+id+"没有对应的信息,客户端提供了降级信息,这个服务现在已经被关闭了")
                        .setDb_source("没有数据~");
            }

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

            @Override
            public Boolean addDept(Dept dept) {
                return null;
            }
        };
    }
}

DeptClientService

@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT",fallbackFactory = DeptClientServiceFallbackFactory.class)

SpringCloud-consumer-dept-feign

# 开启降级 feign.hystrix
feign:
  hystrix:
    enabled: true  

image-20220217095955822

dashboard 监控界面

SpringCloud-consumer-hystrix-dashboard

导入依赖

<!-- hystrix -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
    <version>1.4.7.RELEASE</version>
</dependency>
<!--hystrix 监控页面-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
    <version>1.4.7.RELEASE</version>
</dependency>
server:
  port: 9001
// 主启动类
@SpringBootApplication
@EnableHystrixDashboard  // 开启监控页面
public class DeptConsumerDashboard_9001 {
    public static void main(String[] args) {
        SpringApplication.run(DeptConsumerDashboard_9001.class,args);
    }
}

在要监控的服务添加:

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

主启动类

@EnableCircuitBreaker    // 添加对熔断的支持

// 添加一个 servlet
@Bean
public ServletRegistrationBean hystrixMetricsStreamServlet(){
    ServletRegistrationBean<HystrixMetricsStreamServlet> registrationBean = new ServletRegistrationBean<>(new HystrixMetricsStreamServlet());
    registrationBean.addUrlMappings("/actuator/hystrix.stream");
    return registrationBean;
}

QQ截图20220217101357

20210130141509922

20210130141716916

20210130141909460

20210130141939656

Zuul 路由网关

image-20220217110844001

image-20220217115442773

SpringCloud-zuul-9527

<!--zuul -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zuul</artifactId>
    <version>1.4.7.RELEASE</version>
</dependency>
server:
  port: 9527
spring:
  application:
    name: springbootcloud-zuul
eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
  instance:
    instance-id: zuul9527.com   # 修改 eureka 上默认描述信息
    prefer-ip-address: true   # true: 可以显示服务的ip
info:
  app.name: xiaotao-springboot
  company.name: blog.xiaotao.com

zuul:
  routes:
    mydept.serviceId: springcloud-provider-dept
    mydept.path: /mydept/**
  ignored-services: "*"  # 不能在使用这个路径访问  ignored : 忽略  * 忽略全部
  prefix: /xiaotao  # 设置公告前缀
// 主启动类
@SpringBootApplication
@EnableZuulProxy   // 启动 zuul
public class ZuulApplication_9527 {
    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication_9527.class,args);
    }
}

7001 8001 9527

访问: www.xiaotao.com:9527/xiaotao/mydept/dept/get/1

image-20220217115413820

config

服务器端

springcloud-config-server-3344

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

application.yml

连接远程仓库

image-20220217222727465

server:
  port: 3344
spring:
  application:
    name: springcloud-config-server
    # 连接远程仓库
  cloud:
    config:
      name: springcloud-config-server
      server:
        git:
          uri: https://gitee.com/xiaotao2177393158/springcloud-config.git
          username: 2177393158@qq.com
          password: 远程仓库密码
          force-pull: true
// 主启动类
@SpringBootApplication
@EnableConfigServer    // 开启配置服务类
public class Config_server_3344 {
    public static void main(String[] args) {
        SpringApplication.run(Config_server_3344.class,args);
    }
}

客户端

springcloud-config-client-3355

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

连接服务器端

bootstrap.yml

# 系统级别的配置
spring:
  cloud:
    config:
      uri: http://127.0.0.1:3344
      name: config-client  # 需要从 git 上读取的资源名称 不要后缀
      profile: dev
      label: master

application.yml

# 用户级别的配置
spring:
  application:
    name: springcloud-config-client-3355

获取远程配置文件信息

@RestController
public class ConfigClientController {
    @Value("${spring.application.name}")
    private String applicationName;

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

    @Value("${server.port}")
    private String port;

    @RequestMapping("/config")
    public String getConfig(){
        return "applicationName:"+applicationName +
                "eurekaServer:"+eurekaServer +
                "port"+ port;
    }
}
// 主启动类
@SpringBootApplication
public class ConfigClient_3355 {
    public static void main(String[] args) {
        SpringApplication.run(ConfigClient_3355.class,args);
    }
}

本当の声を響かせてよ