SpringCloud 学习笔记

SpringCloud 学习笔记

1 简介

1.1 什么是 SpringCloud

SpringCloud 是一系列框架的有序集合,它利用 SpringBoot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 SpringBoot 的开发风格做到一键启动和部署。Spring Cloud 并没有重复制造轮子,它只是将各家公司开发的比较成熟、经得起考验的服务框架组合起来,通过 SpringBoot 风格进行再封装屏蔽掉了复杂的配置和实现原理

1.2 SpringCloud 能干什么

springCloud 可以实现服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控。

  • 服务注册发现(Eureka)

    相当于 Zookeeper,好处是服务调用方不直接依赖于服务提供方,保证服务高可用

  • 服务熔断(Hystrix)

    可以保证某个服务提供方的异常不会影响整体系统的稳定

  • 数据监控(Zuul)

    Zuul 是在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架,相当于网关

  • 配置中心(Archaius)

    配置管理 API,包含一系列配置管理 API,提供动态类型化属性、线程安全配置操作、轮询框架、回调机制等功能

  • 消息总线(Bus)

    时间、消息总线,用于在集群(例如,配置变化事件)中传播状态变化

  • 日志追踪(Sleuth)

    日志收集工具包,封装了 Dapper 和 log-based 追踪以及 Zipkin 和 HTrace 操作,为 SpringCloud 应用实现了一种分布式追踪解决方案

  • 大数据处理(Data Flow)

    Data Flow 是一个用于开发和执行大范围数据处理其模式包括 ETL,批量运算和持续运算的统一编程模型和托管服务。

2. 微服务

2.1 什么是微服务

微服务(Microservice Architecture)是最近几年流行的一种架构思想,关于它的概念很难一言以蔽之。

究竟什么是微服务呢?我们在此引用 ThoughtWorks 公司的首席科学家 Martin Fowler 于2014年提出的一段话:

微服务原文:https://martinfowler.com/articles/microservices.html

  • 就目前而言,对于微服务,业界并没有一个统一的,标准的定义
  • 但通常而言,微服务架构是一种架构模式,或者说是一种架构风格,它提倡将单一的应用程序划分成一组小的服务,每个服务运行在其独立的自己的进程内,服务之间互相协调,互相配置,为用户提供最终价值。服务之间采用轻量级的通信机制互相沟通,每个服务都围绕着具体的业务进行构建,并且能够被独立的部署到生产环境中,另外,应尽量避免统一的,集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言,工具对其进行构建,可以有一个非常轻量级的集中式管理来协调这些服务,可以使用不同的语言来编写服务,也可以使用不同的数据存储。

可能有的人觉得官方的话太过生涩,我们从技术维度来理解下:

  • 微服务化的核心就是将传统的一站式应用,根据业务拆分成一个一个的服务,彻底地请耦合,每一个微服务提供单个业务功能的服务,一个服务做一件事情,从技术角度看就是一种小而独立的处理过程,类似进程的概念,能够自行单独启动或销毁,拥有自己独立的数据库。

image-20200903095543037

2.2 微服务与微服务架构

微服务

强调的是服务的大小,他关注的是某一个点,是具体解决某一问题提供落地对应服务的一个服务应用,狭义地看,可以看做 IDEA 中一个个微服务工程,或者是Module。

IDEA 工具里面使用Maven开发的一个个独立的Module,它具体是使用SpringBoot开发的一个小模块,一个模块就做一件事

强调的是一个个的个体,每个个体完成一个具体的任务或者功能!

微服务架构

微服务架构是一种新的架构形式,它提倡将单一应用程序划分成为一组小的服务,服务之间相互协调,互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相协作,每个服务都围绕着具体的业务进行构建,并且能够独立地部署到生产环境中,另外,应精良避免统一的,集中式地服务管理机制,对具体地一个服务而言,应根据业务上下文,选择合适地语言,工具进行构建

2.3 微服务架构的四个核心问题

问题

  1. 服务很多,客户端该如何访问?
  2. 这么多服务,服务之间如何通信?
  3. 这么多服务,如何治理?
  4. 服务挂了怎么办?

解决方案

  1. Spring Cloud NetFlix 一站式解决方案

    API网关: zuul 组件

    Feign:基于 HttpClient,使用 Http通信方式,比如同步,阻塞

    Eureka:服务注册发现

    熔断机制:Hystrix

  2. Apache Dubbo Zookeeper 半自动,需要整合别人的

    API网关:没有,找第三方组件,或者自己实现

    Dubbo:基于RPC高性能开源的Java框架

    Zookeeper:服务注册发现

    熔断机制:需要借助 Hystrix

    Dubbo 这个方案并不完善,但它是RPC框架中最好的

  3. Spring Cloud Alibaba 一站式解决方案

    比 Spring Cloud NetFlix 更简单

虽然主流是这三个方案,但万变不离其宗

  1. API 网关:提供路由支持,客户端通过 API 网关来访问对应的微服务
  2. HTTP,RPC:解决微服务之间的通信问题
  3. 服务注册和发现:统一管理微服务,使得服务高可用
  4. 熔断机制:解决服务挂了的问题,服务挂了的时候,服务降级,防止服务雪崩

综合上面所说的,其实就是网络不可靠引起了这些问题

2.4 微服务优缺点

优点

  • 每个服务足够内聚,足够小,代码容易理解,这样能聚集一个指定的业务功能或业务需求
  • 开发简单,开发效率高,一个服务可能只是专一地干一件事
  • 微服务能够被小团队单独开发
  • 微服务是松耦合地,是有功能意义的服务,无论是在开发阶段或部署阶段都是独立的
  • 微服务能使用不同的语言开发。
  • 易于和第三方集成,微服务允许容易且灵活的方式集成自动部署,通过持续集成工具,如 jenkins,Hudson,bamboo
  • 微服务易于被一个开发人员理解,修改和维护,这样小团队能够更关注自己的工作成果。无需通过合作才能体现价值
  • 微服务允许你利用融合新技术
  • 微服务只是业务逻辑的代码,不会和 HTML、CS或其他界面混合
  • 每个微服务都有自己的存储能力,可以有自己的数据库,也可以统一数据库

缺点

  • 开发人员要处理分布式的复杂性
  • 多服务运维难度会随着服务的增加而增加,运维的压力也在增大
  • 系统部署依赖
  • 服务间通信成本
  • 数据一致性
  • 系统集成测试
  • 性能监控

2.5 微服务技术栈有哪些?

微服务条目 落地技术
服务开发 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、Specatator等
全链路追踪 Zipkin、Brave、Dapper等
服务部署 Docker、OpenStack、Kubernetes等
数据流操作开发包 SpringCloud Stream(封装与 Redis,RabbitMQ,Kafka等发送接收消息)
事件消息总线 SpringCloud Bus

2.6 为什么选择 SpringCloud 作为微服务架构

1. 选型依据

  • 整体解决方案和框架成熟度
  • 社区热度
  • 可维护性
  • 学习区县

2. 当前各大IT公司用得微服务架构有哪些?

  • 阿里:dubbo+HFS
  • 京东:JSF
  • 新浪:Motan
  • 当当网:DubboX
  • ......

3. 各微服务框架对比

功能点/服务框架 Netflix/SpringCloud Motan gRPC Thrift Dubbo/DubboX
功能定位 完整的微服务框架 RPC框架,但整合了ZK或Consul,实现了集群环境的基本服务注册/发现 RPC框架 RPC框架 服务框架
支持 Rest 是,Ribbon支持多种可插拔的序列化选择
支持 RPC 是(Hession2)
支持多语言 是(Rest形式)?
负载均衡 是(服务端zuul+客户端Ribbon),zuul服务,动态路由,云端负载均衡 Eureka(针对中间层服务器) 是(客户端) 是(客户端)
配置服务 Netfix Archius,Spring Cloud Config Server 几种配置 是(zookeeper提供)
服务调用链监控 是(zuul),zuul提供边缘服务,API网关
高可用/容错 是(服务端Hystrix+客户端Ribbon) 是(客户端) 是(客户端)
典型应用案例 Netfix Sina Google Facebook
社区活跃程度 一般 一般 2017年后重新开始维护,之前断了5年
学习难度 中低
文档丰富度 一般 一般 一般
其他 SpringCloud Bus 为我们的应用程序带来了更多管理断点 支持降级 Netflix内部再开发集成gRPC IDL定义 实践的公司比较多

3. SpringCloud 入门概述

3.1 什么是 SpringCloud

SpringCloud 官方学习文档:https://spring.io/projects/spring-cloud#learn

image-20200923232004602

SpringCloud,基于 SpringBoot 提供了一套微服务解决方案,包括服务注册于发现,配置中心,全链路监控,服务网关,负载均衡,熔断器等组件,除了基于 NetFlix 的开源组件做高度抽象封装之外,还有一些选型中立的开源组件。

SpringCloud 了 SpringBoot 的开发便利,巧妙地简化了分布式系统基础设施的开发,SpringCloud为开发人员提供了快速构建分布式系统的一些工具,包括配置管理,服务发现,断路器,路由,微代理,事件总线,全局锁,决策竞选,分布式会话等等,他们都可以用 SpringBoot 的开发风格做到一键启动和部署。

SpringBoot 并没有重复造轮子,它只是将目前各家公司开发的比较成熟,经得起考研的服务框架组合起来,通过 SpringBoot 风格进行再封装,屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂,易部署和易维护的分布式系统开发工具包

SpringCloud 是分布式微服务架构下的一站式解决方案,是各个微服务架构落地技术的集合体,俗称微服务全家桶。

3.2 SpringCloud 和 SpringBoot 关系

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

3.3 Dubbo 和 SpringCloud 对比

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

最大区别:SpringCloud 抛弃了 Dubbo 的 RPC 通信,采用的是基于HTTP的REST方式

严格来说,这两种方式各有优劣,虽然从一定程度上来说,后者牺牲了服务调用的性能,但也避免了上面提到的原生 RPC 带来的问题。而且 REST 相比于 RPC 更灵活,服务提供方和调用方的依赖只依靠一纸契约,不存在代码级别的强依赖,这再强调快速研发的微服务环境下,显得更加合适。

品牌机和组装机的区别

很明显,SpringCloud 的功能比 Dubbo 更加强大,涵盖面更广,而且作为 Spring 的拳头项目,它也能够于 Spring Framework、SpringBoot、Spring Data、Spring Batch等其他 Spring 项目完美融合,这些对于微服务而言是至关重要的。使用 Dubbo 构建的微服务架构就像组装电脑,各环节我们的选择自由度很高,但是最终结果很有可能因为一条内存条质量不行就点不亮了,总是让人不怎么放心,但是如果你是一名高手,那这些都不是问题。而 SpringCloud 就像是品牌机,再 Spring Source 的整合下,做了大量的兼容性测试,保证了机器拥有更高的稳定性,但是如果要在使用非原装组件外的东西,就需要对其基础有足够了解。

社区支持度和更新力度

最为重要的是,Dubbo 停止了5年左右的更新,虽然 2017.7 重启了,对于技术发展的新需求,需要由开发者自行拓展升级(比如当当网弄出了 DubboX),这对于很多想要采用微服务架构的中小软件组织,显然是不太合适的,中小公司没有这么强大的技术能力去修改 Dubbo 源码+周边的一整套解决方案,并不是每一个公司都有阿里的大牛+真实的线上生产环境测试过。

总结

曾风靡国内的开源 RPC 服务框架 Dubbo 在重启维护后,令许多用户为之雀跃,但同时,也迎来了一些之一的声音,互联网技术发展迅速,Dubbo 是否还能跟上时代?Dubbo 与 SpringCloud 相比又有何优势何差异?是否会由相关举措保证 Dubbo 的后续更新频率?

解决的问题域不一样:Dubbo的定位是一款RPC框架,SpringCloud的目标是微服务架构下的一站式解决方案

3.4 SpringCloud 版本号

image-20200924001451507

SpringCloud 是一个由众多独立项目组成的大型综合项目,每个子项目有不同的发行节奏,都维护者自己的发布版本号,SpringCloud 通过一个资源清单BOM(Bill of Materals)来管理每个版本的项目清单。为了避免与子项目的发布号混淆,所以没有采用版本号的方式,而是通过命名的方式。

这些版本名称的命名方式采用了伦敦地铁站的名称,同时根据字母表的顺序来对应版本事件顺序。比如:最早的 Release 版本:Angel,第二个 Release 版本:Brixton,然后是 Camden、Dalston、Edgware,目前最新版本是 Hoxton

大版本说明

SpringBoot SpringCloud 关系
1.2.x Angel版本(天使) 兼容 SpringBoot 1.2.x
1.3.x Brixton版本(布里克斯顿) 兼容 SpringBoot 1.3.x,也兼容 SpringBoot 1.4.x
1.4.x Camden版本(卡姆登) 兼容 SpringBoot 1.4.x,也兼容 SpringBoot 1.5.x
1.5.x Dalston版本(多尔斯顿) 兼容 SpringBoot 1.5.x,不兼容 SpringBoot 2.0.x
1.5.x Edgware版本(埃奇韦尔) 兼容 SpringBoot 1.5.x, 不兼容 SpringBoot 2.0.x
2.0.x Finchley版本(芬奇利) 兼容 SpringBoot 2.0.x,不兼容 SpringBoot 1.5.x
2.1.x Greenwich版本(格林威治) 兼容 SpringBoot 2.1.x
2.2.x Hoxton版本(霍克斯顿) 兼容 SpringBoot 2.2.x

实际开发版本关系

spring-boot-starter-parent spring-cloud-dependencies
1.5.2.RELEASE Daiston.RC1
1.5.9.RELEASE Edgware.RELEASE
1.5.16.RELEASE Edgware.SR5
1.5.20.RELEASE Edgware.SR5
2.0.2.RELEASE Finchiey.BUILD-SNAPSHOT
2.0.6.RELEASE Finchiey.SR2
2.1.4.RELEASE Greenwich.SR1
2.1.0.RELEASE-2.1.14.RELEASE Greenwich.SR5
2.2.0.M4 Hoxton.SR4

4. 第一个 SpringCloud 案例

我们模拟一下生产者和消费者的案例

创建一个Maven父工程 SpringCloud-Study ,并规范好子工程依赖版本

父工程的 pom.xml 如下

:SpringBoot 和 SpringCloud 的版本要匹配(可以参考上面开发版本号,或者查询官网),不然可能会出现很多意想不到的错误

<properties>
    <mysql.version>5.1.46</mysql.version>
    <lombok.version>1.18.12</lombok.version>
    <log4j.version>1.2.17</log4j.version>
    <druid.version>1.1.23</druid.version>
    <mybatisplus.version>3.3.2</mybatisplus.version>
</properties>

<dependencyManagement>
    <dependencies>
        <!-- SpringBoot 版本依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.1.14.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!-- SpringCloud 版本依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Greenwich.SR3</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!-- mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
        </dependency>
        <!-- log4j -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${log4j.version}</version>
        </dependency>
        <!-- log4j 日志门面 -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.3</version>
        </dependency>
        <!-- druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>${druid.version}</version>
        </dependency>
        <!-- MyBatisPlus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatisplus.version}</version>
        </dependency>
    </dependencies>
</dependencyManagement>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

创建数据库和表

创建一个名为 “db01” 的的数据库,并创建 dept 表

dept 表的建表 SQL 语句如下

create table dept
(
    deptno      bigint auto_increment primary key,
    dname       varchar(255) null,
    db_resource varchar(255) null
);

往表中插入数据

-- 插入数据 DATABASE() 这个函数是显示具体是当前数据库的名称
insert into dept (dname, db_resource) VALUES ('开发部',DATABASE());
insert into dept (dname, db_resource) VALUES ('人事部',DATABASE());
insert into dept (dname, db_resource) VALUES ('财务部',DATABASE());
insert into dept (dname, db_resource) VALUES ('市场部',DATABASE());
insert into dept (dname, db_resource) VALUES ('运维部',DATABASE());
-- 查询插入后的所有数据
select * from dept;

创建API工程

创建一个子工程,工程名为 SpringCloud-API,并创建表对应的实体类

该子工程的 pom.xml 中依赖配置如下

<dependencies>
    <!-- lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <!-- MybatisPlus -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus</artifactId>
        <version>3.3.2</version>
    </dependency>
</dependencies>

实体类 Dept.java

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class Dept implements Serializable {

    /**
     * 逐渐
     */
    @TableId(value = "deptno" ,type = IdType.AUTO)
    private Long deptNo;
    /**
     * 部门名
     */
    @TableField("dname")
    private String dname;
    /**
     * 这个数据存在哪个数据库,一个服务对应一个数据库,同一个信息可能存在不同的数据库
     */
    @TableField("db_resource")
    private String db_resource;

}

创建生产者

创建一个子工程,工程名为 SpringCloud-Provider-8001

该子工程的 pom.xml 如下

<dependencies>
    <!-- 我们需要拿到实体类,所以需要配置 api module -->
    <dependency>
        <groupId>com.xp</groupId>
        <artifactId>SpringCloud-API</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-core</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
    </dependency>
    <!-- MybatisPlus -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.3.2</version>
    </dependency>
    <!-- test -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-test</artifactId>
    </dependency>
    <!-- web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <!-- 排除tomcat依赖,使用jetty服务器 -->
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!-- jetty -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jetty</artifactId>
    </dependency>
</dependencies>

然后配置 SpringBoot 的配置文件 applicaton.yml

# 配置端口号
server:
  port: 8001
# 配置 mybatis
mybatis-plus:
  type-aliases-package: com.xp.model.entity
  configuration:
    map-underscore-to-camel-case: false
    cache-enabled: true
  mapper-locations: classpath:mapper/*Mapper.xml
# 配置数据源
spring:
  application:
    name: springcloud-study-provider
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: org.gjt.mm.mysql.Driver
    url: jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: root

搭建 mvc 三层

创建 mapper 包并创建 DeptMapper 接口

@Mapper
@Repository
public interface DeptMapper extends BaseMapper<Dept> {

    boolean addDept(Dept dept);

}

在 resource 目录下创建mapper包并在mapper包内创建 DeptMapper.xml 实现 DeptMapper 接口

<?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.xp.mapper.DeptMapper">
    <insert id="addDept">
        insert into dept (dname, db_resource)
        values (#{dname},DATABASE());
    </insert>
</mapper>

在 SpringbBoot 程序主启动类上加上 mapper 扫描的注解

@SpringBootApplication
@MapperScan("com.xp.mapper") // 扫描mapper接口所在的包
public class Application {

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

}

创建 service 包并创建 DeptService 接口及其实现类

DeptService 接口

public interface DeptService extends IService<Dept> {

    boolean addDept(Dept dept);

}

DeptService 接口实现类

@Service
public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements DeptService {

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

创建 controller 包,并创建 DeptController 对外提供服务

@RestController
public class DeptController {

    @Autowired
    DeptService deptService;

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

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

    @GetMapping("/dept/get")
    public List<Dept> getList(){
        return deptService.list();
    }

}

到这里,一个简单的服务生产(提供)者就完成了

我们可以测试一下是否能够成功使用这个服务

启动项目,然后访问 8001 端口的具体接口,比如 http://localhost:8001/dept/get

image-20201001215832717

创建消费者

创建一个子工程,工程名为 SpringCloud-Consumer-80

该工程的 pom.xml 配置文件如下

<dependencies>
    <!-- 我们需要使用到实体类,所以需要引入 api module -->
    <dependency>
        <groupId>com.xp</groupId>
        <artifactId>SpringCloud-API</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <!-- SpringBoot -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!-- jetty -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jetty</artifactId>
    </dependency>
</dependencies>

application.yml 配置文件如下:

只修改了端口号为 80,因为我们网站默认是80端口访问,所以如果我们设置服务器启动的端口号为80端口,我们可以直接使用 http://localhost/XXXX 来访问具体的项目接口,就跟我们平时上网直接访问网站一样(比如百度:http://www.baidu.com,不是http://www.baidu.com:8080),不需要在网址后面加上端口号。

server:
  port: 80

我们学过 SpringBoot 就会知道,SpringBoot 中会有很多 XXXTemplate(比如 JdbcTemplate)。我们也知道 SpringCloud 搭建的微服务是通过 HTTP 的 REST 来进行通信的,所以消费者要调用生产者的服务,就得通过 HTTP 得 REST 来进行调用,SpringCloud 中有提供 RestTemplate 这个类来简化我们的调用过程。

那么如何使用 RestTemplate 这个类来进行服务的调用呢

我们只需要将 RestTemplate 这个类注入到 Spring 容器中,然后在需要调用远程服务的 Controller 中自动注入使用即可

创建一个配置类ConfigBean,并将 RestTemplate 注入到 Spring 容器中

@Configuration
public class ConfigBean {

    // 将 RestTemplate 注入到 Spring 容器中
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

}

创建 DeptConsumerController 类,并使用 RestTemplate 调用远程的服务

@RestController
public class DeptConsumerController {

    /**
     * 消费者不应该有 Service 层
     * 所以使用 RestTemplate 来通过 Restful风格请求来调用具体的 service
     * 首先需要将 RestTemplate 注入到 Spring 容器中
     * RestTemplate 提供多种编写访问远程 http 服务的方法,简单的 RESTFUL 服务模板
     * RestTemplate 中 getForObject 是使用get方式来获取服务提供者的服务
     * RestTemplate 中 postForObject 是使用post方式来获取服务提供者的服务
     */
    @Autowired
    private RestTemplate restTemplate;

    // 由于远程调用服务者的地址前缀是一样的,所以直接使用常量来防止写错
    private static final String REST_URL_PREFIX = "http://localhost:8001";

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

    @RequestMapping("/consumer/dept/add")
    public boolean add(Dept dept){
        return restTemplate.postForObject(REST_URL_PREFIX+"/dept/add",dept,Boolean.class);
    }

    @RequestMapping("/consumer/dept/get")
    public List<Dept> getList(){
        return restTemplate.getForObject(REST_URL_PREFIX+"/dept/get",List.class);
    }

}

可能会有小伙伴会问,这个项目怎么没有 Service 层,这是因为消费者不应该有 Servcie 层,而是应该通过远程调用 Service 层来完成业务需求。微服务不正是将以前单体应用(all in one)中的服务拆分出来,然后根据具体的项目(消费者)需求调用不同的服务。

接下来,我们测试下消费者是否可以调用生产者提供的服务

先启动生产者,然后启动消费者访问消费者的具体接口

image-20201001220147704

到这里,第一个简单的 SpringCloud 生产者消费者案例就完成了

5. Eureka 服务注册与发现

5.1 什么是 Eureka

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

5.2 原理讲解

  • Eureka 的基本架构

    • SpringCloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务注册和发现(对比 Zookeeper)

    • Eureka 采用了 C-S 架构设计,Eureka Server 作为服务注册功能的服务器,他是服务注册中心

    • 而系统中的其他微服务,使用 Eureka 的客户端连接到 Eureka Server 并维持心跳链接。这样系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行,SpringCloud 的一些其他模块(比如 Zuul)就可以通过 Eureka Server 来发现系统中的其他微服务,并执行相关的逻辑

    • 和 Dubbo 架构的对比

      image-20201003154414586

      image-20201002203454364

    • Eureka 包含两个组件: Eureka Server 何 Eureka Client

    • Eureka Server 提供服务注册服务,各个节点启动后,会在 Eureka Server 中进行注册,这样 Eureka Server 中的服务注册表中存储所有服务器节点的信息,服务器节点的信息可以在界面中直观的看到

    • Eureka Client 是一个 Java 客户端,用于简化 Eureka Server 的交互,客户端同时也具备一个内置的,使用轮询负载算法的负载均衡器。在启动后,将会向 Eureka Server 发送心跳(默认周期为30秒)。如果 Eureka Server 在多个心跳周期内没有接收到某个节点的心跳,Eureka Server 将会从服务注册表中把这个服务节点移除掉(默认周期为90秒)

  • 三大角色

    • Eureka Server:提供服务的注册与发现

    • Service Provider:将自身服务注册到 Eureka 中,从而使消费者方能够找到

    • Service Consumer:服务消费方从 Eureka 中获取注册服务列表,从而找到消费服务

5.3 构建步骤

1. eureka-server:

  1. 创建一个SpringBoot项目 SpringCloud-Eureka-7001

  2. 导入依赖

    pom.xml的依赖如下:

    <dependencies>
        <!-- Eureka -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
            <version>1.4.7.RELEASE</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- jetty -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>
    </dependencies>
    
  3. 配置 eureka-server

    在 resources 目录下创建 application.yml,然后配置 eureka-server

    server:
      port: 7001 # 端口号
    
    # eureka 配置
    eureka:
      instance:
        hostname: localhost # 服务端的实例名称
      client:
        register-with-eureka: false # 表示是否向 eureka 注册中心注册自己
        fetch-registry: false # fetch-registry 如果为 false,则表示自己为注册中心,,不去检索服务
        service-url: # 监控页面
          defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
    
  4. 开启 eureka-server

    在主启动类上加上 @EnableEurekaServer 注解

    // 启动之后访问 http://localhost:7001
    @SpringBootApplication
    @EnableEurekaServer // 表示是服务端的启动类,可以接受别人注册进来
    public class EurekaApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(EurekaApplication.class,args);
        }
    
    }
    
  5. 启动项目,测试访问 Eureka 监控中心

    打开浏览器,输入 http://localhost:7001 ,测试访问 Eureka 监控中心

    image-20201006155834985

    如果可以访问,代表 eureka-server 启动成功

2. Service Provider

将我们之前写的8001服务提供者注册到7001的eureka中

  1. 修改8001服务的pom文件,增加eureka支持

    <!-- Eureka -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka</artifactId>
        <version>1.4.7.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-config</artifactId>
        <version>2.1.4.RELEASE</version>
    </dependency>
    
  2. 修改 yml

    增加eureka的配置

    # eureka 配置
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:7001/eureka/
      instance:
        instance-id: springcloud-provider-8001 # 修改 Eureka 中默认描述信息
    
  3. 开启注解支持,让服务自动注册到 Eureka 中

    @SpringBootApplication
    @EnableEurekaClient // 在服务启动后自动注册到 Eureka 中
    @MapperScan("com.xp.mapper")
    public class Application {
    
        public static void main(String[] args) {
            SpringApplication.run(Application.class,args);
        }
    
    }
    
  4. 启动,测试是否成功注册

    访问 http://localhost:7001 Eureka 监控中心

    image-20201006161442882

    鼠标悬停在如图所在位置,可以看到该服务的IP信息

3. actuator与注册微服务信息完善

在yml配置文件中加多一个配置

# eureka 配置
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka/
  instance:
    instance-id: springcloud-provider-8001 # 修改 Eureka 中默认描述信息
    prefer-ip-address: true # 访问路径可以显示ip地址

然后重启刷新

image-20201006185602051

会发现原本的项目访问地址显示出了ip地址

如果我们点击这个链接,会显示空白错误页,在pom文件中增加如下依赖

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

重启刷新这时会显示空内容

image-20201006200608440

增加yml配置

# info 配置
info:
  app.name: xp-springcloud
  company.name: xp

然后重启刷新

就会显示我们自定义的内容了

image-20201006201341719

4. Eureka 的自我保护机制

在之前的重启中,应该会出现下面这段红字,这段红字的意思是我们Eureka注册中心注册的某个服务可能挂了

image-20201006202933727

在 Dubbo+Zookeeper 中,如果注册的服务检测到未能连接,则会整个注册中的服务都不能访问。刷新页面,我们仍能在Eureka监控中心中看到这个服务,并且点进取还能查看到这个服务的信息,这是为什么呢?

这是因为Eureka中有自我保护机制

自我保护机制:好似不如赖活着

一句话总结:某时刻某一个微服务不可以用了,eureka不会立刻清理,依旧会对该微服务的信息进行保存!

  • 默认情况下:如果EurekaServer 在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当时网络分区故障发生时,微服务与Eureka之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本部应该注销这个服务,Eureka通过自我保护机制来解决这个问题——当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。一旦进入该模式,EurekaServer就会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复猴,该EurekaServer节点会自动推出自我保护模式。
  • 在自我保护模式中,EurekaServer会保护服务注册表中的信息,不再注销任何服务实例。当它收到的心跳数恢复到阈值以上时,该EurekaServer节点就会自动推出自我保护模式。它的设计哲学就是宁可保留错误的服务注册信息,也部不盲目注销任何可能健康的服务实例。一句话:好死不如赖活着
  • 综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留),也不盲目注销任何健康的微服务,使用自我保护模式,可以让Eureka集群更加的健壮和稳定
  • 在SpringCloud中,可以使用eureka.server.enable-self-preservation = false 禁用自我保护模式【不推荐关闭自我保护机制】

5. 8001服务发现Discovery

  • 对于注册进 Eureka 里面的微服务,可以通过服务发现来获得该服务的信息。【对外暴露服务】

  • 修改 DeptController

    先自动注入 DiscoveryClient

    @Autowired
    private DiscoveryClient client;
    

    编写一个接口,用于我们获得该服务的信息

    // 注册进来的微服务,获取一些消息
    @GetMapping("/dept/discovery")
    public Object discovery(){
        // 获得微服务列表的清单
        List<String> services = client.getServices();
        List<ServiceInstance> instances = client.getInstances("SPRINGCLOUD-PROVIDER");
        for (ServiceInstance instance : instances) {
            System.out.println(instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri()+"\t"+instance.getServiceId());
        }
        return this.client;
    }
    

    点进 ServiceInstance 这个类,发现这个类提供了获取注册服务信息的方法

    public interface ServiceInstance {
        default String getInstanceId() {
            return null;
        }
    
        String getServiceId();
    
        String getHost();
    
        int getPort();
    
        boolean isSecure();
    
        URI getUri();
    
        Map<String, String> getMetadata();
    
        default String getScheme() {
            return null;
        }
    }
    

    我们使用的这些方法在EurekaServiceInstance中实现

    image-20201008160319371

    然后在消费者的 DeptConsumerController 中远程调用这个服务

    @RequestMapping("/consumer/dept/discovery")
    public Object discovery(){
        return restTemplate.getForObject(REST_URL_PREFIX+"/dept/discovery",Object.class);
    }
    

    依次启动注册中心,服务提供者,服务消费者,使用浏览器访问测试

    image-20201008161218977

    控制台输出如下:

    image-20201008161354706

5.4 集群配置

  • 新建 SpringCloud-Eureka-7002 和 SpringCloud-Eureka-7003 两个工程项目

  • 将 7001 的内容复制到 7002 和 7003 中

  • 修改映射配置

    windows域名映射

    使用资源管理器进入 C:\Windows\System32\drivers\etc 目录,修改 host 文件

    image-20201008162403538

    或者使用火绒安全软件自带的功能修改

    image-20201008162516332

    向 host 文件中增加域名映射

    127.0.0.1 eureka7001.com
    127.0.0.1 eureka7002.com
    127.0.0.1 eureka7003.com

    image-20201008163147248

    若无法修改,则可能是因为host文件被设置成只读文件

    右键-->属性-->将只读勾选去掉

    image-20201008162928173

  • 修改 yml 配置文件

    修改注册中心启动的端口号以及实例的主机名,并关联集群

    register-with-eureka设置为true或者不设置

    关联集群时,将其他注册中心的访问地址填写在 eureka.client.service-url.defaultZone 中,使用逗号隔开

    server:
      port: 7003
    
      # Eureka
    eureka:
      instance:
        hostname: eureka7003.com
      client:
        register-with-eureka: true # 向Eureka注册中心注册自己
        fetch-registry: false # 表示自己是注册中心,不去检索服务
        service-url:
          # 单机:http://${eureka.instance.hostname}:${server.port}/eureka/
          # 集群(关联):使用逗号隔开,关联其他的注册中心
          defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
    
  • 启动 Eureka 集群

    启动 7001,7002和7003注册中心,访问任意一个服务注册中心监控页面

    image-20201009005818986

    DS Replicas 中有其他注册中心且unavailable-replicas为空,available-replicas中有其他注册中心,则表示Eureka集群搭建成功

  • 将服务注册到集群中

    修改8001服务提供者的 yml 配置文件,defaultZone中增加Eureka注册中心,并使用逗号隔开

    # 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-8001 # 修改 Eureka 中默认描述信息
        prefer-ip-address: true # 访问路径可以显示ip地址
    

    开启8001服务,然后分别访问每个注册中心的监控页面,会发现每个注册中心中都注册了这个服务

    image-20201009010640446

5.5 对比 Zookeeper

回顾CAP原则

RDBMS(MySQL、Oracle、sqlServer) ---> ACID

NoSQL(redis、mongodb) ---> CAP11

ACID是什么?

  • A(Atomicity)原子性
  • C(Consistency)一致性
  • I(Isolation)隔离性
  • D(Durability)持久性

CAP是什么?

  • C(Consistency)强一致性
  • A(Availability)可用性
  • P(Partition tolerance)分区容错性

CAP的三进二:CA、AP、CP

作为服务注册中心,Eureka比Zookeeper好在哪里?

著名的CAP理论指出,一个分布式系统不可能同时C(一致性)、A(可用性)、P(容错性),由于分区容错性P在分布式系统中是必须要保证的,因此我们只能在A和C之间进行权衡

  • Zookeeper 保证的是CP
  • Eureka 保证的是AP

Zookeeper保证的是CP

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

Eureka保证的是AP

Eureka看明白了这一点,因此在设计时就优先保证可用性,Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册时,如果发现连接失败,则会自动切换至其他节点,只要有一台Eureka还在,就能保证注册服务的可用性,只不过查到的信息可能不是最新的,除此之外,Eureka还有一种自我保护机制,日过在15分钟内超过85%的节点都没有正常的心跳,那么Eureka默认为客户端与注册中心出现了网络故障,此时会出现一下几种情况:

  1. Eureka不再从注册列表中移除因为长时间没收到心跳而应该过期的服务
  2. Eureka任然能够接受新服务的注册和查询请求,但是不会被同步到其他节点(即保证当前节点依然可用)
  3. 当网络稳定时,当前实例新的注册信息会被同步到其他节点中

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

6. Spring Cloud Ribbon

6.1 Ribbon 是什么?

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

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

6.2 Ribbon 能干嘛?

  • LB,即负载均衡(Load Balance),在微服务或分布式集群中经常用地一种应用。

  • 负载均衡简单地说就是将用户地请求平摊地分配到多个服务上,从而达到系统的HA(高可用)。

  • 常见的负载均衡软件有 Nginx,Lvs 等等

  • dubbo、SpringCloud 中均给我们提供了负载均衡,SpringCloud的负载均衡算法可以自定义

  • 负载均衡简单分类:

    • 集中式LB

      • 即在服务的消费方和提供方之间使用独立的LB设施,如Nginx,由该实施负责把访问请求通过某种策略转发至服务的提供方!
    • 进程式LB

      • 将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选出一个合适的服务器。
      • Ribbon就是属于进程式LB,它只是一个类库,集成于消费方进程,消费方通过它来获取提供方的地址!

6.3 使用Ribbon实现负载均衡

创建2个数据库,分别为db02和db03,出了db_resourece 字段的内容不相同,其他的数据内容完全相同

# 创建数据库
create database db03;
# 使用刚刚创建的数据库
use db03;
# 创建 dept 表
create table dept
(
    deptno      bigint auto_increment
        primary key,
    dname       varchar(255) null,
    db_resource varchar(255) null
);
# 插入数据
insert into dept (dname, db_resource) VALUES ('开发部',DATABASE());
insert into dept (dname, db_resource) VALUES ('人事部',DATABASE());
insert into dept (dname, db_resource) VALUES ('财务部',DATABASE());
insert into dept (dname, db_resource) VALUES ('市场部',DATABASE());
insert into dept (dname, db_resource) VALUES ('运维部',DATABASE());

新建2个工程项目SpringCloud-Provider-8002、SpringCloud-Provider-8003,复制8001中的代码以及配置文件到这两个项目中(相当于将8001项目复制两份)

修改8002和8003中yml配置文件中的服务端口号,连接的数据库和eureka实例描述

注:yml配置中 spring.application.name 不能修改,因为负载均衡的前提是服务名一致

# 修改端口号为800x
server:
  port: 8002
# 修改数据库为800x
spring:
  application:
  	name: springcloud-provider # 这个不能改,服务名称一致是负载均衡的前提
  datasource:
      type: com.alibaba.druid.pool.DruidDataSource
      driver-class-name: org.gjt.mm.mysql.Driver
      url: jdbc:mysql://localhost:3306/db02?useUnicode=true&characterEncoding=utf-8&useSSL=false
      username: root
      password: root
# eureka 配置,修改实例描述为800x
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-8002 # 修改 Eureka 中默认描述信息
    prefer-ip-address: true # 访问路径可以显示ip地址

修改消费者80的pom文件,添加eureka和ribbon依赖

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

修改消费者80的yml,添加eureka配置

server:
  port: 80

# eureka
eureka:
  client:
    register-with-eureka: false # 不向Eureka注册中心注册自己
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/

开启负载均衡

在 ConfigBean 类中将 RestTemplate 添加 @LoadBalanced 注解开启负载均衡

@Configuration
public class ConfigBean {

    // 配置负载均衡实现 RestTemplate
    @Bean
    @LoadBalanced // ribbon
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

}

修改消费者80的Controller,因为我们使用负载均衡,所以我们应该访问服务名

// 没有负债均衡前 private static final String REST_URL_PREFIX = "http://localhost:8001";
// 使用负载均衡,这里应该是一个变量,我们使用服务名来访问
private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER";

消费者80的启动类,添加 @RibbonClient 注解

@SpringBootApplication
@EnableEurekaClient
@RibbonClient
public class ConsumerApplication {

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

}

分别依次启动7001、7002、7003 eureka注册中心,8001、8002、8003 服务提供者,80 服务消费者

先访问 eureka 监控页面,查看服务是否注册成功

image-20201010090429592

然后通过消费者访问接口远程调用服务: http://localhost/consumer/dept/get/1

查看db_resource的变化,我们会发现它会循环出现db01,db02,db03,至此我们成功使用Ribbon实现负载均衡

6.4 Ribbon 负载均衡算法

6.4.1 IRule

Ribbon 的负载均衡算法都是实现了 IRule 接口

点进 IRule 接口,查看其实现类以及继承关系

实现类

image-20201010095946556

继承关系

image-20201010100940655

算法说明:

RoundRobinRule:默认轮询方式

RandomRule:随机方式

AvailabilityFilteringRule:会先过滤掉跳闸,访问故障的服务,对剩下的服务进行轮询方式

RetryRule:会先按照轮询获取服务,如果服务获取失败,会在指定的时间内进行重试,若再获取不到则会放弃

BestAvaliableRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务

ZoneAvoidanceRule:根据性能和可用性来选择

6.4.2 分析RoundRobinRule算法源码

RoundRobinRule 类中核心的算法就在 choose(ILoadBalancer lb, Object key) 这个方法中

public Server choose(ILoadBalancer lb, Object key) {
    // 判断是否进行负载均衡
    if (lb == null) {
        log.warn("no load balancer");
        return null;
    }

    Server server = null;
    // 尝试获取下一个服务的次数,用于计数
    int count = 0;
    while (server == null && count++ < 10) {
        // 获取存活的服务
        List<Server> reachableServers = lb.getReachableServers();
        // 获取所有服务
        List<Server> allServers = lb.getAllServers();
        // 存活服务的数量
        int upCount = reachableServers.size();
        // 所有服务的数量
        int serverCount = allServers.size();

        // 如果存活服务的数量为0或者总服务数量为0,则不会进行负载均衡
        if ((upCount == 0) || (serverCount == 0)) {
            log.warn("No up servers available from load balancer: " + lb);
            return null;
        }

        // 获取下一个服务的索引值
        int nextServerIndex = incrementAndGetModulo(serverCount);
        // 通过索引值获取下一服务
        server = allServers.get(nextServerIndex);

        // 如果下一个服务为 null(空),线程礼让,稍等片刻后进行下一次尝试获取下一个服务
        if (server == null) {
            Thread.yield();
            continue;
        }

        // 如果下一个服务存在,则返回下一个服务
        if (server.isAlive() && (server.isReadyToServe())) {
            return (server);
        }

        // 上面的条件都不成立,则认为服务不存在,为 null,进行下一次负载均衡尝试
        server = null;
    }

    // 如果尝试获取下一个服务的次数达到10次或以上,则提示尝试10次后,没有可用的服务
    if (count >= 10) {
        log.warn("No available alive servers after 10 tries from load balancer: "
                + lb);
    }
    // 尝试10次或以上后没有获取到服务,返回null
    return server;
}

//下一次获取的位置
private AtomicInteger nextServerCyclicCounter;
private int incrementAndGetModulo(int modulo) {
    for (;;) {
        int current = nextServerCyclicCounter.get();
        int next = (current + 1) % modulo;
        // 由于可能是多线程同时访问,所有使用 CAS 来确保线程安全
        if (nextServerCyclicCounter.compareAndSet(current, next))
            return next;
    }
}

6.4.3 自定义负载均衡算法

在消费者80启动类的上一级目录创建一个包rule,然后创建两个类 MyRule 和 RuleBeanConfig,具体结构如下:

image-20201010113410103

为什么不能放在启动类的上下文中呢?
在 Ribbon 的官方文档中6.2 Customizing the Ribbon Client(定制 Ribbon 客户端)有明确的提示 (官方文档:https://cloud.spring.io/spring-cloud-static/spring-cloud-netflix/2.1.0.RC2/single/spring-cloud-netflix.html#spring-cloud-ribbon)

自定义配置类必须是有@Configuration注解的类s,不过需要注意的是它不能在主应用程序上下文的@ComponentScan中。否则,它会被所有的@RibbonClients共享。如果你使用@ComponentScan(或者@SpringBootApplication),你应该采取措施避免包含它(例如:可以将其放在不重叠的包中或在@ComponentScan指定显式扫描包)

The CustomConfiguration clas must be a @Configuration class, but take care that it is not in a @ComponentScan for the main application context. Otherwise, it is shared by all the @RibbonClients. If you use @ComponentScan (or @SpringBootApplication), you need to take steps to avoid it being included (for instance, you can put it in a separate, non-overlapping package or specify the packages to scan explicitly in the @ComponentScan).

编写 MyRule ,自定义负载均衡算法

先随便将 IRule 的一个算法子类复制粘贴到 MyRule 中(这里是将 RandomRule 内的代码拷贝修改的)作为算法框架,然后根据自己的算法修改其中的内容

这里的算法是每个服务循环5次后切换到其他服务

public class MyRule extends AbstractLoadBalancerRule {

	// 用于记录循环次数
    private int total = 0;
    // 当前服务索引值
    private int currentIndex = 0;

    /**
     * 自定义负载均衡算法
     * 每个服务循环5次后切换到其他服务
     */
    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;
            }

            // --------------自定义的 ribbon 负载均衡算法
            // 当访问了5次时
            if(total>4){
                // 重置计数
                total = 0;
                // 当前服务索引值+1 = 下一个服务的索引值
                currentIndex ++;
                // 如果下一个服务索引值 >= 存活的服务数
                if (currentIndex >= upList.size()){
                    // 重置服务索引值,重新开始轮询
                    currentIndex = 0;
                }
            }
            // 获取下一个服务
            server = upList.get(currentIndex);
            // 计数+1
            total ++;
            // --------------

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

将 MyRule 注册到 Spring 容器中

@Configuration
public class RuleBeanConfig {

    @Bean
    public MyRule myRule(){
        return new MyRule();
    }

}

在主启动类中关联 Rule 配置

@SpringBootApplication
@EnableEurekaClient
// name 为服务名,configuration为自定义算法规则的 Spring 配置类
@RibbonClient(name = "SPRINGCLOUD-PROVIDER",configuration = RuleBeanConfig.class)
public class ConsumerApplication {

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

}

依次启动注册中心,服务提供者,服务消费者,访问 http://localhost/consumer/dept/get/1 ,查看算法是否成功生效

7. Feign 负载均衡

7.1 简介

feign 是声明式的 web service 客户端,它让微服务之间的调用变得更简单了,类似 Controller 调用 Service,Spring Cloud 集成了 Ribbon 和 Eureka,可在使用 Feign 时提供负载均衡的 http 客户端。

只需要创建一个接口,然后天界注解即可!

Feign,主要是社区,大家都习惯面向接口编程,这个是很多开发人员的规范,调用微服务访问的两种方法:

  1. 微服务名字【Ribbon】
  2. 接口和注解【Feign】

Feign 能干什么

  • Feign 旨在使编写 Java http 客户端变得更容易
  • 前面在使用 Ribbon + RestTemplate 时,利用 RestTemplate 对 Http 请求的封装处理,形成了一套模板化的调用方法,但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用,所以,Feign 在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义,在Feign的实现下,我们只需要创建一个接口并使用注解的方式来配置它(类似于以前Dao接口上标注 Mapper 注解,现在是一个微服务接口上面标注一个 Feign 注解即可。)即可完成对服务提供方的接口绑定,简化了使用 Spring Cloud Ribbon时,自动封装服务调用客户端的开发量。

Feign集成了Ribbon

  • 利用 Ribbon 维护了微服务的服务列表信息,并且通过轮询实现了客户端的负载均衡,而与 Ribbon 不同的是,通过 Feign 只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。

7.2 Feign 使用步骤

创建一个新的工程 SpringCloud-Consumer-Feign-80,将原来的消费者80代码以及配置复制到这个工程项目中

修改pom文件,添加 Feign 依赖

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

编写接口 DeptService

:@FeignClient 注解中 value/name 的值为要绑定的微服务的名称

这里的 @GetMapping

@FeignClient(value = "SPRINGCLOUD-PROVIDER") // value的值为绑定的微服务名称
public interface DeptClientService {

    // 不使用 Feign 之前,远程调用服务是使用 RestTemplate 的 getForObject 和 @RequestMapping 获取的
    // @Autowired
    // private RestTemplate restTemplate;
    // @RequestMapping("/consumer/dept/get/{id}")
    // public Dept get(@PathVariable("id") Long id){
    //     return restTemplate.getForObject(REST_URL_PREFIX+"/dept/get/"+id,Dept.class);
    // }
    
    @GetMapping("/dept/get/{id}")
    Dept queryById(@PathVariable("id") Long id);

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

    @PostMapping("/dept/add")
    boolean addDept(Dept dept);

}

修改Controlle

因为 Feign 使用的是接口和注解,所以不需要使用 REST 的 RestTemplate

@RestController
public class DeptConsumerController {

    @Autowired
    private DeptClientService service;

    @RequestMapping("/consumer/dept/get/{id}")
    public Dept get(@PathVariable("id") Long id){
        return this.service.queryById(id);
    }

    @RequestMapping("/consumer/dept/add")
    public boolean add(Dept dept){
        return this.service.addDept(dept);
    }

    @RequestMapping("/consumer/dept/get")
    public List<Dept> getList(){
        return this.service.queryAll();
    }

}

依次启动注册中心,服务提供者,SpringCloud-Consumer-Feign-80 ,测试是否能够成功获取服务

8. Hystrix

8.1 简介

分布式系统面临的问题

复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些 时候将不再避免的失败!

服务雪崩

多个微服务之间调用的时候,假设服务A调用微服务B 和微服务C,微服务B 和微服务C 又调用其他的微服务,这就是所谓的“扇出”,如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A 的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。

对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒中饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障,这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。

我们需要弃车保帅

什么是Hystrix

Hystrix 是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免地都会调用失败,比如超时、异常等,Hystrix 能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以便提高分布式系统的弹性。
“断路器”本省是一种开关装置,当某个服务但愿发生故障后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个服务预期的,可处理的备选响应(FailBack),而不是长时间地等待或者抛出调用方式无法处理的异常,这样就可以保证服务调用方的线程不会被长时间,不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩

能干嘛

  • 服务降级
  • 服务熔断
  • 服务限流
  • 接近实时的监控
  • ......

8.2 服务熔断

是什么

熔断机制是对应雪崩效应的一种微服务链路保护机制。
当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后恢复调用链路。在 SpringCloud框架里熔断机制通过 Hystrix 实现。Hystrix 会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20此调用失败就会启动熔断机制,熔断机制的注解是 @HystrixCommand

如何使用

创建一个新的工程项目 SpringCloud-Hystrix-8001,将原来的 8001 服务提供者的代码和配置复制一份过来

在pom文件中添加 Hystrix 依赖

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

修改 DeptController

@RestController
public class DeptController {

    @Autowired
    DeptService deptService;

    // 熔断后调用 hystrixGet 备选方法
    @HystrixCommand(fallbackMethod = "hystrixGet")
    @GetMapping("/dept/get/{id}")
    public Dept get(@PathVariable("id")Long id){
        Dept dept = deptService.getById(id);
        // id 传的是空或者是无法查询到id对应的部门信息时
        if (dept==null){
            throw new RuntimeException("id-->"+id+", is not exist or can not find it");
        }
        return dept;
    }

    // /dept/get/{id} 熔断时调用的方法
    public Dept hystrixGet(@PathVariable("id") Long id){
        return new Dept()
                .setDeptNo(id)
                .setDname("id-->"+id+"没有对应的信息,null-->Hystrix")
                .setDb_resource("no this database in MySQL");
    }

}

当发生熔断时,我们需要快速返回错误信息,上面的代码中,如果 get 方法无法使用或太长无法响应,则会因为使用了熔断的注解 @HystrixCommand(fallbackMethod = "hystrixGet") 在异常时不会进行抛出,而是使用 hystrixGet 方法代替 get 方法来返回错误信息

添加熔断支持 @EnableCircuitBreaker

@SpringBootApplication
@EnableEurekaClient // 在服务启动后自动注册到 Eureka 中
@EnableDiscoveryClient // 服务发现
@MapperScan("com.xp.mapper")
@EnableCircuitBreaker // 添加对熔断的支持  CircuitBreaker:断路器
public class ApplicationHystrix8001 {

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

}

依次启动注册中心,SpringCloud-Hystrix-8001,服务消费者,访问 http://localhost/consumer/dept/get/0 测试是否能够触发熔断

image-20201012090050663

然后我们停止 SpringCloud-Hystrix-8001

8.3 服务降级

是什么

当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略地降级,以此释放资源以保证核心任务的正常运行。也就是说,当整个微服务整体的负载超出了预设的上限阈值或即将到来的流量预计将会超过预设的阈值时,为了保证重要或基本的服务能正常运行,我们可以将一些不重要不紧急的服务或任务进行服务的延迟使用暂停使用

举个例子,在银行中,有很多个服务窗口可以办理业务,其中也有特殊窗口(比如军人优先,残障人士优先的窗口),当银行来了很多人进行办理业务(服务器压力剧增,服务器负载超过了上限阈值),普通窗口已经排了很多人了。这时候,银行会暂时地关闭或延迟特殊窗口的服务,将特殊窗口变成普通窗口来办理所有人的业务,待到银行中办理业务的人不那么多时(下降到上限阈值以下)(将一些不重要或不紧急的服务或任务进行延迟使用或暂停使用),这就是服务降级。

image-20201012093944344

如何使用

使用 Feign 实现服务降级

服务降级是客户端来实现的,所以我们需要修改 SpringCloud-Consumer-Feign-80 工程项目

创建一个工厂类 DeptClientServiceFallbackFactory 用来统一对 DeptClentService 类里的服务进行降级

@Component // 将这个类注册到 Spring 容器中
public class DeptClientServiceFallbackFactory implements FallbackFactory {
    @Override
    public Object create(Throwable throwable) {
        // 服务降级,当客户端服务关闭或延迟使用时,显示对应的信息告知用户
        return new DeptClientService() {
            @Override
            public Dept queryById(Long id) {
                return new Dept().setDeptNo(id).setDname("没有对应的信息,客户端提供了降级的信息,这个服务现在已经被关闭").setDb_resource("没有数据");
            }

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

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

修改 DeptClientService

// fallbackFactory 的值为服务降级的工厂类
@FeignClient(value = "SPRINGCLOUD-PROVIDER",fallbackFactory = DeptClientServiceFallbackFactory.class)
public interface DeptClientService {

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

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

    @PostMapping("/dept/add")
    boolean addDept(Dept dept);

}

测试

依次打开注册中心,服务提供者,SpringCloud-Consumer-Feign-80,先测试服务是否能成功使用,然后再关闭服务提供者,刷新查看显示结果

image-20201012101144378

8.4 监控中心

创建一个新的工程项目 SpringCloud-Hystrix-Dashboard-9001

添加依赖

<dependencies>
    <!-- 我们需要使用到实体类,所以需要引入 api module -->
    <dependency>
        <groupId>com.xp</groupId>
        <artifactId>SpringCloud-Study-API</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <!-- SpringBoot -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!-- jetty -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jetty</artifactId>
    </dependency>
    <!-- ribbon -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-ribbon</artifactId>
        <version>1.4.7.RELEASE</version>
    </dependency>
    <!-- Feign -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-feign</artifactId>
        <version>1.4.7.RELEASE</version>
    </dependency>
    <!-- eureka -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka</artifactId>
        <version>1.4.7.RELEASE</version>
    </dependency>
    <!-- hystrix -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-hystrix</artifactId>
        <version>1.4.7.RELEASE</version>
    </dependency>
    <!-- hystrixDashboard -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
        <version>1.4.7.RELEASE</version>
    </dependency>
</dependencies>

配置 aplication.yml 文件

配置服务启动的端口号

server:
  port: 9001

开启监控页面

在 SpringCloud-Hystrix-Dashboard-9001 主启动类上加上 @EnableHystrixDashboard 注解

@SpringBootApplication
@EnableHystrixDashboard // 开启 hystrix 监控页面
public class ApplicationDashBoard9001 {

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

}

启动 SpringCloud-Hystrix-Dashboard-9001 ,访问 http://localhost:9001/hystrix 查看是否能成功启动

image-20201012111729936

这个监控页面首页可以看出,如果某个服务提供者需要被监控,则需要注册到 /actuator/hystrix.stream

监控服务提供者 SpringCloud-Hystrix-8001

在 SpringCloud-Hystrix-8001 主启动类中注册监控

@SpringBootApplication
@EnableEurekaClient // 在服务启动后自动注册到 Eureka 中
@EnableDiscoveryClient // 服务发现
@MapperScan("com.xp.mapper")
@EnableCircuitBreaker // 添加对熔断的支持  CircuitBreaker:断路器
public class ApplicationHystrix8001 {

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

    @Bean
    public ServletRegistrationBean hystrixMetricsStreamServlet(){
        ServletRegistrationBean registrationBean = new ServletRegistrationBean(new HystrixMetricsStreamServlet());
        registrationBean.addUrlMappings("/actuator/hystrix.stream");
        return registrationBean;
    }

}

http://localhost:9001/hystrix 页面输入监控地址,点击Monitor Stream 按钮进行监控

image-20201012112555494

监控分析:

  • 七色:进入监控界面后会有其中颜色的数字,其含义可以对用右上角相同颜色的单词表示的状态,其值代表该状态下触发的次数
  • 一圈:圈的大小代表该服务器的流量,图越大流量越大
  • 一线:代表监控间隔中,服务被访问的频率的折线图
  • 通过观察这些就可以在大量的实例中找出故障实例和高压实例进行修复和维护

Dashboard监控说明图:

image-20201012113411644

(图引自 https://blog.csdn.net/qq_33404395/article/details/80917484)

9. Zuul 路由网关

什么是Zuul

在 SpringCloud 官网中有这么一个图

image-20201012125232753

其中的 API Gateway,就是路由网关,Zuul 就是用来充当这个架构图中的 API Gateway

Zuul 包含了对请求的路由和过滤两个主要的功能

其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础,而过滤器功能则是负责对请求的处理过程进行干预,是实现请求校验,服务聚合等功能的基础。Zuul和Eureka进行整合,将 Zuul 自身注册为 Eureka 服务治理下的应用,同时从 Eureka 中获得其他微服务的信息,也即以后的访问微服务都是通过 Zuul 跳转后获得

注意:Zuul 服务最终还是会注册 Eureka

提供:代理+路由+过滤器 三大功能!

如何使用

创建一个新的工程项目 SpringCloud-Zuul-9527

添加依赖

<dependencies>
    <!-- SpringBoot -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!-- jetty -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jetty</artifactId>
    </dependency>
    <!-- eureka -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka</artifactId>
        <version>1.4.7.RELEASE</version>
    </dependency>
    <!-- zuul -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-zuul</artifactId>
        <version>1.4.7.RELEASE</version>
    </dependency>
</dependencies>

编写 application.yml 配置文件

配置 zuul 路由

server:
  port: 9527

spring:
  application:
    name: springcloud-zuul

# euerka
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

# zuul
zuul:
  routes:
    dept.serviceId: springcloud-provider # 服务名
    dept.path: /mydept/** # 访问该服务的路径
  ignored-services: "*" # 禁止直接通过服务名访问服务,需要加上双引号
  prefix: /xp # 前缀,如果不加上前缀则无法访问

info:
  app.name: xp-springcloud-zuul
  company.name: xp

开启注解

在主启动类中增加 @EnableZuulProxy 注解

@SpringBootApplication
@EnableZuulProxy // 开启 zuul 代理
public class ApplicationZuul9527 {

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

}

域名映射

为了模仿我们真实开发中使用网址进行访问,所以进行域名映射

127.0.0.1 www.xp.com

image-20201012162603189

测试

依次启动注册中心,服务提供者,SpringCloud-Zuul-9527

访问 http://www.xp.com:9527/xp/mydept/dept/get/1 测试

image-20201012162755234

如果端口号使用 80 端口,则与真实的网站访问无异

10. SpringCloud Config 分布式配置

10.1 概述

分布式系统面临的配置文件问题

微服务意味着要将单体应用中的业务拆分成一个个子服务,每个自服务的粒度相对较小,因此系统中会出现大量的服务,由于每个服务都需要必要的配置信息才能运行,所以一套集中式的,动态的配置管理设施是必不可少的。SpringCloud 提供了 ConfigServer 来解决这个问题,我们每一个微服务自己带着一个 application.yml,那上百个的配置文件要修改起来,岂不是要发疯。

什么是 SpringCloud Config 分布式配置中心

image-20201014100820736

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

Spring Cloud Config 分为服务端客户端两部分

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

客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息。配置服务器默认采用git来存储配置信息,这样有助于对环境配置进行版本管理。并且可以通过git客户端工具来方便的管理和访问配置内容。

Spring Cloud Config 分布式配置中心能干嘛

  • 集中管理配置文件
  • 不同环境,不同配置,动态化的配置更新,分环境部署,比如 /dev,/test,/prod,/beta,/release 等等
  • 运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉去配置自己的信息
  • 当配置发生变动时,服务不需要重启,即可感知配置的变化,并应用新的配置
  • 将配置信息以REST接口的形式暴露

10.2 Spring Cloud Config 分布式配置中心与 git 整合

由于 Spring Cloud Config 默认使用 git 来存储配置文件(也有其他方式,比如支持 SVN 和本地文件 ),但是最推荐的还是 git,而且使用的是 http/https 访问的形式

由于国内码云gitee比github速度要快,所以这里使用gitee来当作git的远程仓库

服务端

在码云上创建一个新的远程仓库,然后使用ssh克隆到本地

image-20201014163932914

在本地克隆远程仓库

image-20201014163852613

新建一个文件 application.yml ,使用记事本打开,然后编写配置提交到远程仓库

这里所说的提交到远程仓库,都是使用如下的git命令操作

git add .
git commit -m 提交的信息
git push 

application.yml 的配置内容如下

spring:
  profiles:
  active: dev

---
spring:
  profiles: dev
  application:
    name: springcloud-config-dev

---
spring:
  profiles: test
  application:
    name: springcloud-config-test

创建一个新的工程项目 SpringCloud-Config-3344,作为 Spring Cloud Config 的服务端

导入依赖

<dependencies>
    <!-- SpringCloudConfig-server 服务端 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-server</artifactId>
    </dependency>
    <!-- springboot -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!-- jetty -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jetty</artifactId>
    </dependency>
</dependencies>

编写 application.yml 配置文件

server:
  port: 3344
spring:
  application:
    name: SpringCloud-Config-server
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/windows_xp_xp/spring-cloud-config-test.git # https 链接,不是 ssh 链接

在启动类上加上 @EnableConfigServer 注解

@SpringBootApplication
@EnableConfigServer // 开启 SpringCloudConfig 服务端
public class ApplicationConfig3344 {

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

}

启动该项目并访问 http://localhost:3344/application-dev.yml 查看是否配置成功

观察 http://localhost:3344/application-dev.ymlhttp://localhost:3344/application-test.yml 的不同

image-20201014165139828

在官网中有写到具体怎么访问 Config 服务端中的配置文件

官网地址:https://docs.spring.io/spring-cloud-config/docs/2.2.5.RELEASE/reference/html/#_spring_cloud_config_client

/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties

其中 application 表示配置文件的名字,profile 表示配置的环境(比如:dev,test),label 表示远程仓库的分支

:有可能会出现访问失败的可能,报错信息为 Authentication is required but no CredentialsProvider has been registered,此时,需要将仓库权限设置为公开或在服务端配置文件中添加码云的账号和密码

server:
  port: 3344
spring:
  application:
    name: SpringCloud-Config-server
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/windows_xp_xp/spring-cloud-config-test.git # https 链接,不是 ssh 链接
          # 配置账号和密码,一般来说,为了安全起见,建议使用账号密码的方式来获取远程仓库的文件配置
          username: xxxx # 码云账号
          password: xxxx # 码云密码

客户端

在我们本地的git仓库中创建配置文件 application-client.yml

spring:
  profiles:
  active: dev

---
server:
  port: 8021
spring:
  application:
    name: springcloud-config-dev
  profiles: dev
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: org.gjt.mm.mysql.Driver
    url: jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: root
# 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-config # 修改 Eureka 中默认描述信息
    prefer-ip-address: true # 访问路径可以显示ip地址

---
server:
  port: 8022
spring:
  application:
    name: springcloud-config-test
  profiles: test
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: org.gjt.mm.mysql.Driver
    url: jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: root
# 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-config # 修改 Eureka 中默认描述信息
    prefer-ip-address: true # 访问路径可以显示ip地址

将 application-client.yml 提交到码云远程仓库中

创建一个新的工程项目 SpringCloud-Config-Client ,模拟获取 Config 配置的客户端

编写配置文件 application.yml,bootstrap.yml

application.yml 用户级别的配置

# 用户级别的配置
spring:
  application:
    name: SpringCloud-Config-Client-3355

bootstrap.yml 系统级别的配置

# 系统级别的配置
spring:
  cloud:
    config:
      uri: http://localhost:3344 # 提供远程使用配置文件的微服务uri
      profile: test # 环境选择
      label: master # 分支
      name: application-client # 需要从git读取的资源名称,不需要后缀,这里获取的就是 applicaiont-client.yml

编写 Controller 访问从远程配置文件配置的变量参数

@RestController
public class ConfigClientController {

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

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

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

    @RequestMapping("/config")
    public HashMap<String, String>  config(){
        HashMap<String, String> configMap = new HashMap<>();
        configMap.put("applicationName:",applicationName);
        configMap.put("defaultZone:",defaultZone);
        configMap.put("serverPort:",serverPort);
        return configMap;
    }

}

启动 SpringCloud-Config-Client ,访问 http://localhost:8021/config

image-20201014171025516

我们需要修改配置的时候,在 bootstarp.yml 中修改 spring.cloud.config.profile 即可

posted @ 2020-10-14 19:10  Windows_XP  阅读(503)  评论(0编辑  收藏  举报