spring cloud

一、前言

1.应用分类

1.1单体应用框架

  • 复杂性高:整个项目包含的模块非常多、模块的边界模糊,依赖关系不清晰,代码混乱地堆砌到一起,修改 Bug 或新增功能都可能带来隐含的缺陷;
  • 技术债务:随着时间的推移、人员的变更,会逐渐形成应用程序的技术债务,而且越积越多,Not broken,dont fix(不坏不修),这在软件开发中非常常见,已使用的系统设计或代码难以被修改,因为应用程序中的其它模块可能会以意料之外的方式在使用它;
  • 部署效率低:随着代码的增多,构建和部署的时间也会增多,每次修复 Bug 或新增功能都要重新部署整个应用,全部署的方式耗时长、影响范围大、风险高,这儿使得单体应用上线部署效率很低;
  • 可靠性差:某个应用 Bug,可能会导致整个应用崩溃;
  • 扩展能力受限:单体应用只能作为一个整体扩展,无法根据业务模块的需要进行伸缩,例如:应用中有的模块是计算密集型的,需要强劲的 Cpu,有的模块是 IO 密集型的,需要更大的内存,由于这些模块都部署在一起,不得不在硬件的选择上做妥协;
  • 阻碍技术创新:单体应用往往使用统一的技术平台或方案解决所有问题,团队中的每个人都必须使用相同的语言和技术框架,要想引入新的东西比较困难,例如:一个 Struts2 构建,有 100w 行代码的单体应用,如果想换成 Spring Boot,毫无疑问,切换的成本是非常高的;

1.2分布式服务架构

  • 将一个单一应用程序开发为一组小型服务的方法,每个服务运行在自己的进程中,服务间通信采用轻量级通信机制(通常为 HTTP 资源 Api),这些服务通过全自动部署机制独立部署,共用一个集中式的管理,服务可以使用不同语言开发,使用不同的数据存储技术;
  • 搜索引擎、电商网站、微博、微信、O2O 平台……凡是涉及到大规模用户、高并发访问的,无一不是分布式,“大系统小做”,对于一个大的复杂系统,首先想到的就是对其分拆,拆成多个子系统,每个子系统自己的存储、Service、接口层,各个子系统独立开发、测试、部署、运维;
  • 优点:易于扩展、部署简单、技术异构性、从团队管理角度讲,也可以不同团队用自己熟悉的语言体系,团队之间基于接口进行协作,职责清晰,各司其职;

2、基础理论

2.1分布式和集群

  • 集群是多台计算机完成一样的工作,提高效率和安全性,需要考虑一致性的问题;
  • 分布式是多台计算机协同完成工作,提高效率和扩展性,需要考虑事务的问题;

2.2 一致性

        强一致性、弱一致性、最终一致性;

2.3 ACID:

  • 事务的四大特性,原子性、一致性、隔离性、持久性,MySQL 通过 InnoDB 日志和锁来保证事务的强一致性;

2.4 CAP 理论;

  • 1998年,加州大学的计算机科学家 Eric Brewer 提出,分布式系统有三个指标:Consistency(一致性)、Availability(有效性)、Partition tolerance(分区容错性:单个组件不可用,依然能完成操作,分区容错性是分布式系统的根本);
    • 在 G1 和 G2 通讯中,有可能无法成功(断电、出错、网络延迟等),有可能此时 Client 已经发起查询操作;
    • 在同步服务器时候,需要停掉 G2 的读写操作,无法保证有效性;
    • 允许 G2 查询得到未同步之前的数据,无法保证一致性;
  • 两个分支;
  • CP 架构:放弃可用性,追求一致性和分区容错性;
  • AP 架构:放弃强一致,追求分区容错性和可用性,这是很多分布式系统设计时的选择;
  • BASE 理论:BA(Basic Available 基本可用)、S(Soft State 柔性状态,同一数据的不同副本的状态,不需要实时一致)、E(Eventual Consisstency 最终一致性);
  • 酸碱平衡:ACID && BASE 理论平衡,比如交易系统需要强一致性,遵循 ACID,注册成功后发送邮件只需遵循 BASE 理论即可;

3.系统架构

3.1物理相关

  • 负载均衡器;
  • Linux HA:又称“双机热备”,是两台机器装上同样的系统,配好后通过“心跳线”互相监测,数据存放在共享阵列库中,当主服务器出现问题时,从服务器会接管主服务器上的所有服务;
  • 多机房:同城灾备,异地灾备;

3.2 存储

  • 存储分类
    • Nosql:内存数据库(Redis)、文件型数据库(MongoDB);
    • Sql:Mysql、Sql Server、Oracal,会设计到分库分表的几个关键性的问题:切分维度,Join 的处理,分布式事务;
    • 读写分离:对传统的单机 Mysql 数据库,读和写是完全同步的,写进去的内容,立马就可以读到,但在高并发情况下,或者读和写并不需要完全同步的情况,就可以分开存储,采用读写分离;
      • 缓存:缓存大家都不陌生,遇到性能问题,大家首先想到的就是缓存;
      • 重写轻读 VS 重读轻写;
      • 重写轻读:本质就是“空间换时间“,你不是计算起来耗时,延迟高吗,那我可以提前计算,然后存储起来,取的时候,直接去取;
      • 重读轻写:我们通常对 Mysql 的用法,写的时候,简单,查的时候,做复杂的 Join 计算,返回结果,这样做的好处是容易做到数据的强一致性,不会因为字段冗余,造成数据的不一致,但是性能可能就是问题;
  • 冷热分离:比如定期把 Mysql 中的历史数据,同步到 Hive;

3.3 Service

  • 负载均衡;
  • 分布式事务;
  • 并发:通过设计保证系统能够同时并行处理很多请求,垂直扩展(提升单机处理能力)、水平扩展(增加服务器数量,线性扩充系统性能);
  • 同步 VS 异步:因为非实时,我们就可以做异步,比如使用消息队列,比如使用后台的 Job,周期性处理某类任务;
  • Push vs Pull
  • 在所有分布式系统中,都涉及到一个基本问题:节点之间(或者 2 个子系统之间)的状态通知,比如一个节点状态变更了,要通知另外一个节点;
  • Pull: 节点 B 周期性的去询问节点 A 的状态;
  • Push: 节点 A 状态变了, Push 给节点 B;
  • 批量:批量其实也是在线/离线的一种思想,把实时问题,转化为一个批量处理的问题,从而降低对系统吞吐量的压力,比如 Kafka 中的批量发消息,比如广告扣费系统中,把多次点击累积在一起扣费;
  • 限流:现在很多电商都会有秒杀活动,秒杀的一个特点就是商品很少,但短时间内流量暴增,服务器完全处理不了这么多请求,那索性不要放那么多人进去;
  • 服务熔断与降级:服务降级是系统的最后一道保险,就是当某个服务不可用时,干脆就别让其提供服务了,直接返回一个缺省的结果,虽然这个服务不可用,但它不至于让整个主流程瘫痪,这就可以最大限度的保证核心系统可用;
  • 前端
  • 动静分离:动态的页面放在 Web服务器上,静态的资源如 Css/Js/Img,直接放到 CDN(内容分发网络)上,这样既提高性能,也极大的降低服务器压力;
  • 其他
  • 故障监控:系统监控、链路监控、日志监控;
  • 自动预警;

4.数据库分库分表

4.1 数据分片

  • 离散式分片:按照数据的某一字段哈希取模后进行分片存储;
  • 跨库操作需要由数据分库分表中间件来完成,影响数据的查询效率;
    • 当数据存储能力出现瓶颈需要扩容时,离散分片规则需要将所有数据重新进行哈希取模运算,产生数据迁移问题;
  • 连续分片:数据按照时间或连续自增主键连续存储;
  • 大量的读写往往都集中在最新存储的那一部分数据,会导致热点问题;

4.2 数据库扩展

  • 垂直分库:将一个完整的数据库根据业务功能拆分成多个独立的数据库,这些数据库可以运行在不同的服务器上,从而提升数据库整体的数据读写性能;
  • 垂直分表:如果一张表的字段非常多,将一张表中不常用的字段拆分到另一张表中,从而保证第一张表中的字段较少,提升查询效率,而另一张表中的数据通过外键与第一张表进行关联;
  • 水平分表:如果一张表中的记录数过多(超过 1000 万条记录),那么会对数据库的读写性能产生较大的影响,将一张含有很多记录数的表水平切分,拆分成几张结构相同的表,根据某字段进行哈希取模后均匀地存储在切分表中;
  • 水平分库分表:水平数据分片首先将数据表进行水平拆分,然后按照某一分片规则存储在多台数据库服务器上;
  • 跨库操作
    • 跨库 Join 变得非常麻烦,而且基于架构规范,性能,安全性等方面考虑,一般是禁止跨库 Join 的;
    • 在业务系统层面,通过多次 SQL 查询,完成数据的组装和拼接;
    • 中间件:Cobar(分库)、MyCat;

5.分布式事务

  • 目前数据库不支持跨库事务,我们就需要在数据库之上通过某种手段,实现支持跨数据库的事务支持,比如:商品系统、订单系统、支付系统、积分系统、库存系统等,用户下单,2、3、4、5需要在一个事务控制;

5.1 分布式事务协议

  • 2PC:两阶段提交协议
  • 有一个事务管理器的概念,负责协调多个数据库的事务,事务管理器先问各个数据库你准备好了吗?如果每个数据库都回复 OK,那么就正式提交事务,在各个数据库上执行操作,如果任何其中一个数据库回答 NO,那么就回滚事务;
    • PreCommit 阶段
    • 节点:协调者 Coordinator、参与者 Cohorts;
  • 协调者向所有参与者询问是否可以执行提交操作 Vote;
    • 参与者执行询问,Undo 信息和 Redo 信息写入日志;
    • 参与者向协调者发起询问,返回“成功”(事务操作成功)或“中止”(事务操作失败);
  • DoCommit 阶段
    • 协调者获得参与者“同意”时,发出“Commit”请求,参与者完成请求,释放资源,向协调者返回“完成”消息,协调者收到消息后完成事务;
      • 协调者获得参与者“中止”消息时,发出“Rollback”请求,参与者完成请求,释放资源;
      • 缺点
      • 参与者节点事务都是阻塞型的;
        • 协调者发生故障,参与者会一直阻塞;
        • 第二阶段,在协调者发出“Commit”后,协调者和其中的参与者都宕机,没人知道事务是否提交;
  • 3PC:三阶段提交协议
  • 在 2PC 的基础上,引入了协调者和参与者的超时机制,并将 Precommit 阶段分成了两个准备阶段;
  • CanCommit 阶段、PreCommit 阶段、DoCommit 阶段;

5.2 分布式事务模式

  • AT:两阶段型,是一种无侵入的分布式事务解决方案,阿里的 Seata 实现了该模式;
  • TCC:两阶段型、补偿型;
  • Try 阶段:对各个服务的资源做检测以及对资源进行锁定或者预留;
    • 这种方案使用较少,因为事务回滚实际上是严重依赖于你自己写代码来回滚;
    • Cancel 阶段:如果任何一个服务的业务方法执行出错,那么这里就需要进行补偿,就是执行已经执行成功的业务逻辑的回滚操作;
    • Confirm 阶段:在各个服务中执行实际的操作;
  • Saga:两阶段、补偿型;
  • 每个 Saga 由一系列 sub-transaction Ti 组成
    • 和 TCC 相比,Saga 没有“预留”动作,它的 Ti 就是直接提交到库;
    • 每个 Ti 都有对应的补偿动作 Ci,补偿动作用于撤销 Ti 造成的结果;
  • XA:分布式强一致性的解决方案,性能低而使用较少;

二、分布式框架

1.Dubbo + Zookeeper

以bubbo来调用的

dubbo:阿里程序员,自创,一个技术,

zookeeper:注册中心功能

类似于我们的controller调用service,通过RPC去调用远程接口

1.1RPC

  • Remote Procedure Call Protocol,远程过程调用协议,是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议;
  • 该协议允许运行于一台计算机的程序调用另一台计算机的程序,通过一个双向的通信连接(Socket 套接字),实现数据的交换;
  • 关键是创建“客户端存根”,存根就像是代理模式中的代理,生成代理后,代理跟远程服务端通信;

2.spring cloud

Netflix

注册中心,负载均衡器,网关,远程调用。

3.spring cloud Alibaba

使用了spring cloud的组件,另外添加了三大组件:

1.Nacos

2.Sentinel

3.Seata

三、分布式架构

  • Provider:生产者,接口提供方;
  • Consumer:消费者,调用接口方;
  • Registry:注册中心,服务注册与发现;
  • Monitor:监控中心,统计服务的调用次数和时间;
  • Container:服务运行容器;
  • ----------------
  • Container 负责启动,加载,运行服务;
  • Provider 在启动时,向 Registry 注册自己提供的服务;
  • Consumer 在启动时,向 Registry 订阅自己所需的服务;
  • Registry 返回 Provider 地址列表给 Consumer,如果有变更,Registry 将基于长连接推送变更数据给 Consumer;
  • Consumer 从 Provider 地址列表中,基于软负载均衡算法,选一台 Provider 进行调用,如果调用失败,再选另一台;
  • Provider 和 Consumer,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到 Monitor;

四、简介

1.特点

  • 官方文档:https://spring.io/projects/spring-cloud;
  • 在 Spring Boot 基础之上构建,用于快速构建分布式系统的通用模式的工具集;
  • 集大成者,Spring Cloud 包含了微服务架构的方方面面;
  • 约定优于配置,基于注解,配置文件少;
  • 轻量级组件、开发简便、灵活;
  • 项目结构复杂,每一个组件或者每一个服务都需要创建一个项目;
  • 部署门槛高,项目部署需要配合 Docker 等容器技术进行集群部署;

2.版本

Maven 中央库搜索 Spring Cloud Dependencies,查看 Spring Cloud 版本;

  • Spring Boot 版本以伦敦地铁站命名,对非英语母语国家开发者并不友好,2020 版本变更了命名规则;

Spring Boot 和 Spring Cloud 版本对应关系;

2020 版本变更

  • Spring Cloud 一直以来把 Netflix OSS 套件作为其官方默认的一站式解决方案,可是 Netflix 公司在 2018 年前后宣布其核心组件 Hystrix、Ribbon、Zuul、Archaius 等均进入维护状态,迫于无奈,Spring Cloud 做了阻断式升级,发布了 2020 版本;

  • Bootstrap 父上下文配置默认不再启动,参见 BootstrapApplicationListener.java,对 BootStrap 做了判断;
  • 全新的配置方式,可以使用 spring.config.import 导入其它组件的配置,如:spring.config.import=configserver:xxx,这么做更具模块化,更符合云原生环境的要求;

3.组件

  • 注册中心:Netflix Eureka;
  • 负载均衡:Netflix Ribbon(2020 版本前)、Spring Cloud Loadbalancer(2020 版本后);
  • 熔断器:Netflix Hystrix(2020 版本前)、Resilience4j(2020 版本后);
  • 声明式服务调用组件:Feign(最初属 Netflix 公司,后来移交给 OpenFeign 组织);
  • 网关:Netflix Zuul(2020 版本前)、Spring Cloud Gateway(2020 版本后);
  • 配置中心:Spring Cloud Config;
  • 事件、消息总线:Spring Cloud Bus;
  • 安全组件:Spring Cloud Security;

五、公共项目搭建

搭建一个spring cloud项目

目标:将之前写的spring boot项目拆解到spring cloud 项目中

一下市搭建分布式项目的步骤:

entity项目问题?

问题1:为什么要创建一个entity项目?

微服务之间涉及到跨模块的beanentity的调用,所以我首先将所有的分布式系统里的bean抽取出来,形成单独的模块,其他微服务以jar包的方式引入

问题2:微服务会不会跨服务调用 bean? 不会

举个栗子

其他模块会不会调用 账户模块 的user 对象? 会

对啊,既然会用到,那么我微服务 B 怎么去调用 微服务 A 里面的 bean?

问题3: 每个微服务就是一个项目? 是的

问题4: 是不是将每个微服务的 bean 抽取出来,形成一个单独的项目,其他微服务只要添加该项目依赖就可以引用到了

问题5: 那以后是不是还要把每一个service之类的都抽出来?
不用啊,我们通过resttemplate调用接口的方式实现微服务接口调用

1、Entity 项目

1.1.简介

在 Spring Cloud 项目中,每个微服务都可能用到的内容,比如 Entity、Util 等,我们将这些内容单独放到一个项目内,成为其余微服务的依赖;

1.2.创建entity

1.创建骨架

创建 Java Maven 项目 java_spring_cloud_entity;

这时骨架就搭建好了,

2.导入依赖

  • Pom 添加依赖
<!-- servlet -->
<dependency>
	<groupId>javax.servlet</groupId>
	<artifactId>javax.servlet-api</artifactId>
	<version>4.0.1</version>
</dependency>

<!-- javax.persistence -->
<dependency>
	<groupId>javax.persistence</groupId>
	<artifactId>javax.persistence-api</artifactId>
	<version>2.2</version>
</dependency>

<!-- Jackson -->
<dependency>
	<groupId>com.fasterxml.jackson.core</groupId>
	<artifactId>jackson-core</artifactId>
	<version>${jackson.version}</version>
</dependency>
<dependency>
	<groupId>com.fasterxml.jackson.core</groupId>
	<artifactId>jackson-databind</artifactId>
	<version>${jackson.version}</version>
</dependency>
<dependency>
	<groupId>com.fasterxml.jackson.core</groupId>
	<artifactId>jackson-annotations</artifactId>
	<version>${jackson.version}</version>
</dependency>
<!-- 序列化 LocalDateTime 等时间类 -->
<dependency>
	<groupId>com.fasterxml.jackson.datatype</groupId>
	<artifactId>jackson-datatype-jsr310</artifactId>
	<version>${jackson.version}</version>
</dependency>

<!-- Commons -->
<dependency>
	<groupId>org.apache.commons</groupId>
	<artifactId>commons-lang3</artifactId>
	<version>3.12.0</version>
</dependency>

 

<jackson.version>2.11.3</jackson.version>

3. 包装:将之前项目各个模块中 Entity 移植到该项目;

1.在基础包下创建与之前项目模块各个相同的包名

2.复制之前的entity过来放在对应的包中

3.复制过来后,需要修改entity中的引用路径

全部修改完后就,没错了,下一步打成jar包

4.打包jar

  • Maven 打包项目,在本地仓库生成 Jar;
  • 命令:mvn clean install -Dmaven.test.skip=true

这里会出现问题情况:

这里是你的maven环境变量没有配置,步骤如下:

  • 环境变量
  • MAVEN_HOME:D:\Program Files\apache-maven-3.6.3
  • Path:%MAVEN_HOME%\bin;
  • cmd:mvn --version

d30d56e381a36a83ac96adff4fb36aa2.png

这样直接点确定,就好了,然后去测试,打开cmd

测试结果是正常这样后,就配好了,但是idea还没有感应到,重启idea,然后打开entity项目,重新输入打包命令

5.找到jar包,在target下面

这样就打好了,然后就可以把你的项目上传到远程仓库里面了

2.创建注册中心

2.1Netflix Eureka

简介

  • Spring Cloud 提供了多种注册中心的支持,如:Eureka、Consul、ZooKeeper 等,Netflix Eureka 本身是一个基于 REST 的服务,包含两个组件:Eureka Server 和 Eureka Client;
  • Eureka Server 提供服务注册服务,各个节点启动后,会在 Eureka Server 中进行注册,这样 Eureka Server 的服务注册表将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到;
  • Eureka Server 之间通过复制的方式完成数据的同步,Eureka 还提供了客户端缓存机制,即使所有的 Eureka Server 都挂掉,客户端依然可以利用缓存中的信息消费其他服务的 API;
  • Eureka Client:生产者或消费者;
  • 在应用启动后,Eureka Client 将会向 Eureka Server 发送心跳,默认周期为 30 秒,如果 Eureka Server 在多个心跳周期内(默认 90 秒)没有接收到某个节点的心跳,Eureka Server 将会进入自我保护机制;

Eureka Server(注册中心)

  • 骨架搭建
  • 创建 Spring Boot 项目 java_spring_cloud_register,选择 Eureka Service 组件;

  • 配置
  • application.properties
# for server
server.port=8760
# for Eureka  server
eureka.instance.hostname=localhost
eureka.client.registerWithEureka=false
eureka.client.fetchRegistry=false
eureka.client.servic

  • 启动类
  • 添加 @EnableEurekaServer 注解;
  • @EnableEurekaServer :范围大
@SpringBootApplication
@EnableEurekaServer
public class SpringCloudRegisterApplication {

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

}
  • 启动项目,访问注册中心:http://127.0.0.1:8760

3.Eureka Client Test微服务

3.1.Eureka Client Test (test微服务)

  • 创建 Spring Boot 项目 java_spring_cloud_test,选择 Eureka Discovery Client、Spring Web 组件;

  • pom,xml
<!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--eureka-client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <!--        mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>

        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.4.1</version>
        </dependency>

        <!--commons-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>

        <!--entity-->
        <dependency>
            <groupId>com.sfac</groupId>
            <artifactId>spring_cloudc_entity</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
  • application.properties
#for  server
server.port=8761

# for eureka client
spring.application.name=client-test
eureka.instance.hostname=localhost
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:8760/eureka

  • 启动类,添加 @EnableEurekaClient 注解;
  • @EnableEurekaClient只针对与eureka-client
@SpringBootApplication
@EnableDiscoveryClient
public class SpringCloudTestApplication {

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

}
  • 将之前项目中 Test 模块 RestFul 接口移植到该项目;

  • 启动项目;

  • 访问注册中心:http://127.0.0.1:8760 ---- 新启动的微服务 client-test 已经被注册;

访问微服务 client-test 接口:http://127.0.0.1:8761/api/contry/522

3.2 Eureka Client Account(Account微服务)

  • 创建 Spring Boot 项目 java_spring_cloud_account,选择 Eureka Discovery Client、Spring Web 组件;

    pom.xml:
 <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--eureka-client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <!--        mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>

        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.4.1</version>
        </dependency>

        <!--commons-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>

        <!--entity-->
        <dependency>
            <groupId>com.sfac</groupId>
            <artifactId>spring_cloudc_entity</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

​​​​​​​

  • application.properties
#for  server
server.port=8763

# for eureka client
spring.application.name=client-account
eureka.instance.hostname=localhost
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:8760/eureka

# for data source
# mysql 5
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test_city?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456

# hikari pool
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=180000
spring.datasource.hikari.auto-commit=true

# for mybatis
mybatis.configuration.map-underscore-to-camel-case=true

启动类,添加 @EnableEurekaClient 注解;

  • 将之前项目中 Account 模块 RestFul 接口移植到该项目,剔除不用的部分:Shiro、Redis 登录次数验证;

  • 启动项目;

这里如果出现了异常,找不到,执行mvn idea idea

  • 访问注册中心:http://127.0.0.1:8763 ---- 新启动的微服务 client-account 已经被注册;

  • 访问微服务 client-account 接口:http://127.0.0.1:8763/api/user/1

4.Eureka Client 相互调用,resttemplate,openfeign

1.Eureka Client 互相调用

1.1.1 需求

  • user接口,根据id查询user,需要返回对应的城市信息
  • uservo bean 没有城市信息
    • userVo对象,需要设置城市信息
    • 需要提供一个查询userVo的接口-----再service层,user对象从account微服务本身调用,city通过远程调用,访问test微服务,再组装为userVo对象。
  • client-account 接口需要调用 client-test 数据,此时,client-account 既是生产者又是消费者;

1.1.2 新建UserVo

  • 再spring_cloud_entity中新建一个 UserVo 对象,继承自 User,并添加 City 属性,在获取 UserVo 时候,根据默认 cityId 获取 City 信息;
public class UserVo  extends AbstractEntity {
    private static final long serialVersionUID = 1L;
    private String userName;
    private String email;
    private String password;
    private String userImage;
    private boolean rememberMe;
    private List<Role> roles;

    private City city;
}

然后重新打包.......其他引用了entity微服务的地方就可以加载出userVo类。

1.1.3 消费者注册 RestTemplate;

可以新建一个包,也可以写再启动类中,注册成为bean

@SpringBootApplication
@EnableEurekaClient
public class SpringCloudAccountApplication {

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


    //消费者注册 RestTemplate;
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return  new RestTemplate();
    }
}

1.1.4实现 RestFul 接口

  • Entity ---- Dao ---- Service ---- Controller;
//entity:上面写了,再entity微服务中
    
//userDao:不需要写,只需要调用写好的即可。

//userService:

	UserVo getUserVoByUserIDAndCityId(int userId,int CityId);

//userServiceImpl:

	 @Override
    public UserVo getUserVoByUserIDAndCityId(int userId, int CityId) {
        UserVo userVo = new UserVo();
        User user = userDao.getUserById(userId);
        BeanUtils.copyProperties(user,userVo);   //copy

        //调用test微服务,获取城市信息
        City city = restTemplate.getForObject(
                "http://client-test/api/city/{cityId}", City.class, CityId);
        userVo.setCity(city);
        return userVo;
    }

//userController:

	 /**
     * 127.0.0.1/api/userVo/1/1890 ---- get
     */
    @GetMapping(value = "/userVo/{userId}/{cityId}")
    public UserVo getUserVo(@PathVariable int userId,@PathVariable int cityId) {
        return userService.getUserVoByUserIDAndCityId(userId,cityId);
    }

1.1.5 启动

1.启动项目,先调用注册中心,加载进去

2.调用接口:http://127.0.0.1:8762/api/userVo/1/1890

2.Eureka 用户认证

  • 简介:用户无需登录便能访问 Eureka Server 注册中心,这样并不安全,我们需要为 Eureka 添加安全认证依赖;
  • Eureka Server
    • Pom 引入 Jar;
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

​​​​​​​

  • 配置
  • application.properties
# for spring security
# 高版本 spring cloud 舍掉该配置
#spring.security.basic.enable=true
spring.security.user.name=root
spring.security.user.password=root

添加配置类 WebSecurityConfig.java

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// Basic 认证是一种较为简单的 HTTP 认证方式,客户端通过明文(Base64编码格式)传输用户名和密码到服务端进行认证
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
http.csrf().disable();
http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
}
}    @Bean
    ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
            Environment environment, 
            LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        // 在此也可返回自定义负载均衡器
        return new RandomLoadBalancer(
                loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
    }
  • ureka Client
  • Pom 无需引入 Jar;
  • 配置
    • application.properties
      • eureka.client.serviceUrl.defaultZone=http://root:root@${eureka.instance.hostname}:8760/eureka
  • 启动项目,访问接口;

3.Eureka 自我保护机制

  • 简介
  • 微服务正常退出,比如用 Idea 停掉微服务,控制台输出 Completed shut down of DiscoveryClient,此时,注册中心立即注销该微服务;
  • 微服务非正常退出,比如使用 Eclipse 停掉微服务、拔掉网线、taskkill 等,Eureka Server 在运行期间会去统计心跳失败比例在 15 分钟之内是否低于 85%,如果低于 85%,Eureka Server 会将这些实例保护起来,让这些实例不会过期,此时消费者就会拿到一个无效的服务实例,导致调用失败,需要服务消费者端有一些容错机制,如重试,断路器等;
  • 触发条件:Renews(last min) < Renews threshold;
    • Renews (last min):Eureka Server 最后 1 分钟收到客户端实例续约的总数;
      • this.expectedNumberOfRenewsPerMin = count * 2; ---- 微服务数量,包括注册中心 * 每分钟心跳数;
      • this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold()); ---- getRenewalPercentThreshold 默认 85%;
    • Renews threshold:Eureka Server 期望每分钟收到客户端实例续约的总数;
      • count * 2 ---- 微服务数量,包括注册中心 * 每分钟心跳数;
  • 禁用自我保护机制
  • Eureka Server
    • application.properties
# 关闭注册中心自我保护机制
eureka.server.enable-self-preservation=false
# 清理间隔
eureka.server.eviction-interval-timer-in-ms=20000

​​​​​​​

  • Eureka Client
    • application.properties
# 告诉服务端,如果我 10s 之内没有给你发心跳,就代表我死了,将我踢出掉,默认 90s
eureka.instance.lease-expiration-duration-in-seconds=10
# 每间隔 3s,向服务端发送一次心跳,证明自己依然存活,默认 30s
eureka.instance.lease-renewal-interval-in-seconds=3

5.负载均衡(微服务添加集群,引入负载均衡器组件)

1.Netflix Ribbon

1.1简介

    • 适用于 Spring Cloud 2020 之前的版本;
    • Ribbon 是 Netflix 发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将 Netflix 的中间层服务连接在一起,Ribbon 客户端组件提供一系列完善的配置项如连接超时,重试等,简单的说,就是在配置文件中列出 Load Balancer 后面所有的机器,Ribbon 会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器,我们也很容易使用 Ribbon 实现自定义的负载均衡算法;
  • 负载均衡策略
  • RoundRobinRule ---- 轮询、默认;
  • RandomRule ---- 随机;
  • RetryRule ---- 重试机制;
  • BestAvailableRule ---- 选择最小并发 server;
  • AvailabilityFilteringRule ---- 过滤高并发、失败 server;
  • WeightedResponseTimeRule ---- 根据响应时长比重选择中间值;
  • ZoneAvoidanceRule ---- 判断 server 性能;
  • 查看 com.netflix.loadbalancer.IRule 接口;

1.2 实现

  • 启动 Eureka Server;
  • Eureka Client Test,不同端口启动多个实例;
  • Eureka Client Account
    • Pom 无需引入 Jar;
      • spring-cloud-starter-netflix-eureka-client 中已经包含了 spring-cloud-starter-netflix-ribbon;配置
      • application.properties,配置策略(可选配置);
      # for ribbon 
      client-test.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
      • 注册 RestTemplate 时,添加 @LoadBalanced 注解;
    • 应用
      • 启动 Client-Account,调用端口测试:http://127.0.0.1:8762/api/userVo/1;
      • 断点调试 LoadBalancerInterceptor、RibbonLoadBalancerClient;
        • 拦截器 LoadBalancerInterceptor 获取请求中注册的服务名 CLIENT-TEST,在 RibbonLoadBalancerClient 中获取服务器列表,通过负载均衡算法得到调用地址;

2.Spring Cloud Loadbalancer

2.1 简介

    • 适用于 Spring Cloud 2020+;
    • Spring Cloud Loadbalancer 负载均衡器,它并不是一个独立的项目,而是 Spring Cloud Commons 其中的一个模块,项目中用了 Eureka 相关的 Starter,想完全剔除 Ribbon 的依赖是不可能的,2020 版本默认关闭 Ribbon(spring.cloud.loadbalancer.ribbon.enabled=false),启用 Spring Cloud Loadbalancer;
    • Spring Cloud 2020 版本内置了轮询、随机的负载均衡策略,默认轮询,这点比 Ribbon 要少,我们可以通过 LoadBalancerClient 注解来指定负载均衡器;
    • 接口结构
      • ReactiveLoadBalancer
      • ReactorLoadBalancer
      • ReactorServiceInstanceLoadBalancer
      • RandomLoadBalancer || RoundRobinLoadBalancer

2.2 实现

  • 启动 Eureka Server;
  • Eureka Client Test,不同端口启动多个实例;
  • Eureka Client Account
    • Pom 无需引入 Jar;
      • spring-cloud-commons 和 spring-cloud-loadbalancer 已经被引入;
    • 配置
      • application.properties
        • 无需配置;
      • CustomLoadBalancerConfiguration.java
public class CustomLoadBalancerConfiguration {

    @Bean
    ReactorLoadBalancer randomLoadBalancer(
            Environment environment,
            LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        // 在此也可返回自定义负载均衡器
        return new RandomLoadBalancer(
                loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
    }
}
  • 应用
    • 启动 Eureka Client Account,调用接口 http://127.0.0.1:8762/api/userVo/1/1890 测试;
    • 从控制台日志记录中可看出,当 Client Account 选择不同策略时,访问 Client Test 微服务集群会按照既定策略访问;

6.熔断器

分区熔断性,就是当集群中的一个服务挂掉后,给他一个默认值,不让他影响整个集群的运行。

1.Resilience4j
1.1简介

  • 推荐 Spring Cloud 2020 之后版本使用;
  • Resilience4j 是 Spring Cloud G 版本推荐的容错方案,是一个轻量级的容错库,专为 Java 8 和函数式编程而设计,借鉴了 Hystrix 的设计,提供了断路器(CircuitBreaker)、并发调用隔离(Bulkhead)、限流(RateLimiter)、重试(Retry)、超时(Timeout) 等功能;
  • 断路器 CircuitBreaker
    • 断路器一般通过 3 个有限状态机来实现:CLOSED、OPEN、HALF_OPEN,此外,还有 2 个特殊的状态机:DISABLED 和 FORCED_OPEN,状态的存储更新必须是线程安全的,即只有一个线程能够在某个时间点更新状态;
    • 关闭 ----> 打开:当故障率等于或大于可配置的阈值时,CircuitBreaker 的状态将从“关闭”更改为“打开”。
    • 打开 ----> 半开:当 CircuitBreaker 打开时,它会拒绝带有 CallNotPermittedException 的调用,经过一段等待时间后,CircuitBreaker 状态从 OPEN 变为 HALF_OPEN,并允许可配置数量的服务调用是否仍然不可用或再次变为可用,用 CallNotPermittedException 拒绝其他调用,直到所有允许的调用完成,如果故障率或慢呼叫率等于或大于配置的阈值,则状态会变回 OPEN;
    • 半开 ----> 关闭:如果故障率和慢呼叫率低于阈值,则状态将变回“已关闭”;
    • DISABLED:始终拒绝调用;
    • FORCED_OPEN:始终允许调用;
  • 并发调用隔离 Bulkhead
    • 在系统设计中,需要预期故障的发生,将应用程序拆分成多个组件,通过资源隔离确保一个组件的故障不会影响其他的组件,就像轮船用隔板(Bulkhead)分成多个小隔间,每个隔间都被隔板密封,这样可以防止进洪水时整艘船沉没;
    • 两个服务 A 和服务 B,A 的某些 API 依赖 B,当服务 B 运行速度非常慢的时候,A 调用 B 的请求变多时,A 的性能会受到影响,服务 A 中那些不依赖于服务 B 的功能也无法处理,因此,需要隔离 A 中依赖 B 的请求,Resilience4j 提供了 SemaphoreBulkhead 和 FixedThreadPoolBulkhead 来实现 Bulkhead;
  • 限流 RateLimiter
    • 微服务在给定的时间内设置可以处置的最大请求数,控制吞吐量来帮助保护服务器免于过载;
  • 重试 Retry
    • 微服务体系中,多个服务互相依赖,当被依赖的服务出现问题而无法按预期响应时,就会级联到下游服务,导致不良的用户体验,通常我们会为每个微服务部署多个实例,如果其中一个实例有问题,无法响应我们的请求,我们则重试该请求,负载均衡器可以将请求发送到运行状况良好的节点并正确获得响应,通过重试,有更多机会获得正确的响应;
  • 超时 Timeout
    • 在微服务体系中,微服务相互依赖,可能因为网络的原因,导致消费者阻塞,在设计时需要设置超时来应对服务缓慢 、不可用性问题;

1.2 实现

  • 消费者 Pom 导入 Jar
    	<!-- resilience4j -->
        <dependency>
            <groupId>io.github.resilience4j</groupId>
            <artifactId>resilience4j-spring-boot2</artifactId>
        </dependency>

实现方式一:AOP 式;

    • 消费者 application.properties 配置不同的策略;
  • RecordFailurePredicate && IgnoredException
public class RecordFailurePredicate implements Predicate<Throwable> {
    @Override
    public boolean test(Throwable throwable) {
        return true;
    }
}
public class IgnoredException extends Exception {
}
  • 在消费者 Service 层添加相应的策略来启动配置项;
    • @Retry(name = "retryBackendA") ---- 启动重试策略;
    • @CircuitBreaker(name = "backendA") ---- 启动断路器策略,没法指定断路器默认返回值;
    • @RateLimiter(name = "backendA") ---- 启动限流策略;

实现方式二:编程式

无需配置,改造消费者 Service;

    @Override
    public UserVo getUserVoByUserIDAndCityId(int userId, int cityId) {
        UserVo userVo = new UserVo();
        User user = userDao.getUserById(userId);
        BeanUtils.copyProperties(user,userVo);   //copy

//        调用test微服务,获取城市信息,均衡负载方式
//        City city = restTemplate.getForObject(
//                "http://client-test/api/city/{cityId}", City.class, cityId);
//        userVo.setCity(city);
//        return userVo;


//        熔断器:就是一个服务挂掉后,给一个默认的值
        CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
                .failureRateThreshold(50)
                .waitDurationInOpenState(Duration.ofMillis(1000))
                .build();
        CircuitBreaker circuitBreaker = CircuitBreaker.of("circuitBreakerPloy", circuitBreakerConfig);
        Try<City> circuitBreakerSupplier = Try.ofSupplier(CircuitBreaker.decorateSupplier(
                circuitBreaker,
        () -> restTemplate.getForObject("http://client-test/api/city/{cityId}", City.class, cityId)
                )).recover(Exception.class, new City());
        userVo.setCity(circuitBreakerSupplier.get());
        return userVo;
}

  • 调用接口 http://127.0.0.1:8762/api/userVo/1 测试;
  • 挂掉test1.test2

7.远程调用

1.RestTemplate

  • 参见 Netflix Eureka ---- Eureka Client 互调;

2.OpenFeign

2.1 简介

    • Feign:是 Netflix 开发的声明式、模板化的 HTTP 客户端,用于访问注册中心接口,2019 年停更;
    • OpenFeign:Spring Cloud 在 Feign 的基础上进行了增强,使 Feign 支持了 Spring MVC 的注解,提高了 Feign 的易用性,Spring Boot 2.0 以上基本使用 OpenFeign;

2.2 实现

  • 消费者 Pom 导入 Jar;
    	<!-- openfeign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
  • 配置;
    • 启动类上添加 @EnableFeignClients 注解,Spring 启动后会扫描标注了 @FeignClient 的接口,然后生成代理类,并注入到 Spring IOC 容器中,才可以被注入使用;
  • 应用;
  • 在 service 包下创建 TestFeignClient 接口,用于调用 Client-Test 微服务接口;
@Component
@FeignClient(name = "client-test")
public interface TestFeignClient {
    /**
     *
     * /api/city/2259 ------put
     */
    @GetMapping(value = "/city/{cityId}")
    City selectCityById(@PathVariable int cityId);

}

改造消费者 Service,注入 TestFeignClient,替换掉 RestTemplate;

@Override
    public UserVo getUserVoByUserIDAndCityId(int userId, int cityId) {
        UserVo userVo = new UserVo();
        User user = userDao.getUserById(userId);
        BeanUtils.copyProperties(user,userVo);   //copy

//        调用test微服务,获取城市信息,均衡负载方式
//        City city = restTemplate.getForObject(
//                "http://client-test/api/city/{cityId}", City.class, cityId);
//        userVo.setCity(city);
//        return userVo;


//        熔断器:就是一个服务挂掉后,给一个默认的值
//        CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
//                .failureRateThreshold(50)
//                .waitDurationInOpenState(Duration.ofMillis(1000))
//                .build();
//        CircuitBreaker circuitBreaker = CircuitBreaker.of("circuitBreakerPloy", circuitBreakerConfig);
        Try<City> circuitBreakerSupplier = Try.ofSupplier(CircuitBreaker.decorateSupplier(
//                circuitBreaker,
//        () -> restTemplate.getForObject("http://client-test/api/city/{cityId}", City.class, cityId)
//                )).recover(Exception.class, new City());
//        userVo.setCity(circuitBreakerSupplier.get());
//        return userVo;


        //远程调用
        CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
                .failureRateThreshold(50)
                .waitDurationInOpenState(Duration.ofMillis(1000))
                .build();
        CircuitBreaker circuitBreaker = CircuitBreaker.of("circuitBreakerPloy", circuitBreakerConfig);

        Try<City> circuitBreakerSupplier = Try.ofSupplier(CircuitBreaker.decorateSupplier(
                circuitBreaker,
                () -> testFeignClient.selectCityById(cityId)
        )).recover(Exception.class, new City());
        userVo.setCity(circuitBreakerSupplier.get());
        return userVo;

    }
}
  • 调用接口 http://127.0.0.1:8762/api/userVo/1/1890 测试;

8.网关服务

1.Netflix Zuul( 开源的微服务网关 )

1.1 简介

    • 推荐 Spring Cloud 2020 之前版本使用;
    • 未加网关的微服务架构
      • 通过上面的学习,分布式架构基本成型,内部服务集群 Service A 和 Service B,它们都会到注册中心注册和订阅服务,而 Open Service 集群专门对外提供接口,这样的实现是否合理?是否有更好的方法?
      • 这种架构破坏了服务无状态特点:为了保证对外服务的安全性,我们需要实现对服务访问的权限控制,而 Open Service 的访问权限机制会贯穿并污染整个开放服务的业务逻辑,破坏了集群中 Rest Api 无状态的特点,从具体的开发来说,工作中除了要考虑实际业务逻辑之外,还要额外持续对接口的访问做权限处理;
      • 无法直接复用接口:当我们需要一个已有的集群内部接口,我们不得不在原有的接口上做校验逻辑,或者增加一个代理来做权限控制,无法直接复用原有的接口;
    • 网关服务
      • 将权限控制、日志收集从服务单元中抽离出去,最适合的地方是服务集群的最外端,我们需要一个功能更强大的负载均衡器,网关服务;
      • 网关服务是微服务架构中不可或缺的一部分,它具备统一向外提供 Rest Api、服务路由、负载均衡、权限控制等功能,为微服务架构提供了门前保护,除了安全之外,还能让服务集群具备高可用性和测试性;
      • Zuul 是 NetFlix 开源的微服务网关,可以和 Eureka、Ribbon、Hystrix 等组件配合使用,其核心是一系列的过滤器;
        • 身份认证和安全:识别每个资源的验证要求,拒绝不符合要求的请求;
        • 审查与监控:在边缘位置追踪有意义的数据和统计结果,从而带来精确的生产视图;
        • 动态路由:动态地将请求路由到不同的后端服务集群;
        • 压力测试:逐渐增加指向集群的流量;
        • 负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求;
        • 静态响应处理:在边缘位置直接建立部分响应,从而避免其转发到集群内部;
        • 多区域弹性:跨越 AWS Region 进行请求路由,实现 ELB(AWS Elastic Load Balancing ---- 负载均衡服务)使用的多样化,以及让系统的边缘更贴近系统的使用者;
    • 使用 Zuul 后的微服务架构
      • 客户端请求微服务时,先经过 Zuul,再调用微服务集群,这样对于校验、负载均衡等功能移到了 Zuul 中实现,而微服务只需关注自身的业务逻辑则可;

2.2 Zuul 工程

    • 创建 Spring Boot 项目 java_spring_cloud_gateway,选择 Eureka Discovery Client、Zuul 组件;
    • application.properties

  • 启动类添加 @EnableZuulProxy 注解;
  • 启动服务,进行测试;
    • Test 服务
      • 原生接口:http://127.0.0.1:8761/api/cities/522
      • 通过 Gateway 接口:http://127.0.0.1:8759/testService/api/cities/522
    • Account 服务
      • 原生接口:http://127.0.0.1:8762/api/user/1
      • 通过 Gateway 接口:http://127.0.0.1:8759/accountService/api/user/1
  • Zuul Filter
  • 简介
    • Zuul Filter 是一个抽象类,是 Zuul 的重要组件,自定义过滤器需实现以下四个方法;
      • shouldFilter:是否执行该过滤器;
      • run:过滤器业务逻辑;
      • filterType:返回过滤器类型;
        • pre:请求在被路由之前执行;
        • routing:在路由请求时调用;
        • post:在 routing 和 errror 过滤器之后调用;
        • error:处理请求时发生错误调用;
      • filterOrder:过滤器优先级,数字越小优先级越高;
    • 执行流程
  • 自定义过滤器

2.Spring Cloud Gateway

2.1.简介

    • Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0、Spring Boot 2.0 和 Project Reactor 等技术开发,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流;
    • Spring Cloud Gateway 目标是替代 Zuul,为了提升网关的性能,Spring Cloud Gateway 是基于 WebFlux 框架实现的,而 WebFlux 框架底层则使用了高性能的 Reactor 模式通信框架 Netty;

2.2.相关术语(断言)

    • Filter 过滤器:和 Zuul 的过滤器在概念上类似,可以使用它拦截和修改请求,并且对上游的响应,进行二次处理,过滤器为 org.springframework.cloud.gateway.filter.GatewayFilter 类的实例;
    • Predicate 断言:这是一个 Java 8 的 Predicate,可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数,断言的输入类型是一个 ServerWebExchange;
    • Route 路由:网关配置的基本组成模块,和 Zuul 的路由配置模块类似,一个 Route 模块由一个 ID,一个目标 URI,一组断言和一组过滤器定义,如果断言为真,则路由匹配,目标 URI 会被访问;
      • spring.cloud.gateway.routes[0].id=testServer
      • # 指向外部地址
      • spring.cloud.gateway.routes[0].uri=https://www.baidu.com
      • # 指向注册中心注册的服务
      • spring.cloud.gateway.routes[0].uri=lb://client-test
      • # 断言绑定各种过滤器,匹配成功则跳转到目标地址
      • # 请求 Path 匹配
      • spring.cloud.gateway.routes[0].predicates[0]=Path=/api/**
      • # 请求 Host 匹配
      • spring.cloud.gateway.routes[0].predicates[0]=Host=**.foo.org
      • # 请求方式匹配
      • spring.cloud.gateway.routes[0].predicates[2]=Method=GET
      • # 请求参数匹配
      • spring.cloud.gateway.routes[0].predicates[4]=Query=foo, ba.
      • # 请求 header 匹配
      • spring.cloud.gateway.routes[0].predicates[3]=Header=X-Request-Id, \d+
      • # 请求 Cookie 匹配
      • spring.cloud.gateway.routes[0].predicates[6]=Cookie=chocolate, ch.p
      • # 在该时间之前的请求都转到目标地址
      • spring.cloud.gateway.routes[0].predicates[7]=Before=2018-01-20T06:06:06+08:00[Asia/Shanghai]
      • # 在该时间之后的请求都转到目标地址
      • spring.cloud.gateway.routes[0].predicates[7]=After=2018-01-20T06:06:06+08:00[Asia/Shanghai]

2.3.实现

1.重构 client-test、client-account 接口;

      • 我们用 Path 断言来设置 Route,那么不同的微服务 Api Path 应该有明显的区别,故而做如下改变;
      • client-test:/api/** ---- /api/test/**;
      • client-account: /api/** ---- /api/account/**;
      • 修改 TestFeignClient.java 中 Feign 接口;

2.创建 Spring Boot 工程 java_spring_cloud_gateway,选择 Eureka Discovery Client、Gateway 组件;

2.配置application.properties,配置account,test的路由和端口

#for  server
server.port=8888

# for eureka client
spring.application.name=client-gateway
eureka.instance.hostname=localhost
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:8760/eureka

# for gateway route
spring.cloud.gateway.routes[0].id=testServer
# lb load balance 缩写
spring.cloud.gateway.routes[0].uri=lb://client-test
spring.cloud.gateway.routes[0].predicates[0]=Path=/api/test/**

# for gateway route
spring.cloud.gateway.routes[1].id=accountServer
# lb load balance 缩写
spring.cloud.gateway.routes[1].uri=lb://client-account
spring.cloud.gateway.routes[1].predicates[0]=Path=/api/account/**
    • 端口详情:

注册中心: 8760

test微服务: 8761 8762

account微服务:8763 8764

gateway网关:8888

    • 启动类添加 @EnableEurekaClient 注册服务;扫描微服务。

4.启动服务测试

    • Client-Test 微服务
      • 原生接口:127.0.0.1:8762/api/test/city/1890
      • 网关接口:127.0.0.1:8888/api/test/city/1890

    • Client-Account 微服务
      • 原生接口:127.0.0.1:8763/api/account/userVo/1/1890
      • 网关接口:127.0.0.1:8888/api/account/userVo/1/1890

2.4 过滤器

  • 局部过滤器:针对单个路由的过滤器,
    • Gateway 内置局部过滤器
      • spring.cloud.gateway.routes[0].filters[0]=StripPrefix=1
      • spring.cloud.gateway.routes[0].filters[1]=SetStatus=250
    • 自定义局部过滤器
      • 参照内置过滤器书写;
  • 全局过滤器:针对所有路由,无需配置;
    • Gateway 内置全局过滤器;
    • 自定义全局过滤器

2.5 跨域配置

  • 方式一:application.properties
# for Gateway Cros
spring.cloud.gateway.globalcors.cors-configurations.[/**].allowCredentials=true
spring.cloud.gateway.globalcors.cors-configurations.[/**].allowedOriginPatterns=*
spring.cloud.gateway.globalcors.cors-configurations.[/**].allowedMethods=*
spring.cloud.gateway.globalcors.cors-configurations.[/**].allowedHeaders=*
spring.cloud.gateway.globalcors.add-to-simple-url-handler-mapping=true

方式二:配置类

package com.sfac.springCloudGateway.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

@Configuration
public class CorsAutoConfiguration {
    @Bean
    public WebFilter corsFilter() {
        return (ServerWebExchange ctx, WebFilterChain chain) -> {
            ServerHttpRequest request = ctx.getRequest();
            if (CorsUtils.isCorsRequest(request)) {
                ServerHttpResponse response = ctx.getResponse();
                HttpHeaders headers = response.getHeaders();
                headers.set(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, request.getHeaders().getOrigin());
                // 允许的 header
                headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS,
                        "X-Token,Token,Authorization,x-requested-with,Content-Type");
                headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "PUT,POST, GET, OPTIONS, DELETE");
                headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
                headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
                headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "3600");
                if (request.getMethod() == HttpMethod.OPTIONS) {
                    response.setStatusCode(HttpStatus.OK);
                    return Mono.empty();
                }
            }
            return chain.filter(ctx);
        };
    }
}

9.配置中心

将微服务的配置文件全部整合一起,然后通过注册中心接口调用然后配置文件。

1.Spring Cloud Config

简介

    • 之前配置文件是否存在的问题?微服务各自拥有自己的配置文件,当配置修改之后,我们需要重启项目,如果数量庞大,维护成本增加,针对这个问题,我们采用 Spring Cloud Config 来做配置;
    • Spring Cloud Config 为分布式系统外部化配置提供了服务端和客户端的支持,它包含 Config Server 和 Config Client 两个部分,实现了 Spring Environment 和 PropertySource 抽象的映射,非常适合 Spring 应用程序;
    • Config Server 是一个可横向扩展、集中式的配置服务器,它用于集中管理应用程序各个环境下的配置,支持 Git、SVN、本地文件存储,默认使用 Git;
    • Config Client 是 Config Server 的客户端,用于操作存储在 Config Server 中的配置内容,微服务在启动时会请求 Config Server 获取配置文件的内容,请求到后再启动容器;

架构图

1.1 Config Server

  • 创建仓库
    • 创建 Git 远程仓库,并将微服务所有配置文件上传到仓库中,配置文件命名规则:

    • 环境配置:application-{profile}.properties;
    • 配置中心:{application}-{profile}.profiles;
      • 此案例只将 Client-Test、Client-Account 配置移植到配置中心;
      • applicationTest-dev.properties
      • applicationAccount-dev.properties
  • 创建工程
    • 创建 Spring Boot 工程 java_spring_cloud_config,选择 Eureka Discovery Client、Config Server 组件;
  • 导入pom:server config
	<!--集成spring config-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-server</artifactId>
    </dependency>
  • application.properties
注册中心: 8760
test微服务: 8761 8762
account微服务:8763 8764
gateway网关:8888
配置中心:8766
#for  server
server.port=

# for eureka client
spring.application.name=client-config
eureka.instance.hostname=localhost
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:8760/eureka

# for config server
# 使用本地配置文件,并在工程 resources 下添加相应的文件即可
# spring.profiles.active=native
# 配置文件 Git 仓库地址
spring.cloud.config.server.git.uri=https://gitee.com/ljr666666/config.git
# 配置文件在仓库中文件夹路径
spring.cloud.config.server.git.search-paths[0]=clazz
# 仓库用户名和密码,公有仓库无需设置
#spring.cloud.config.server.git.username=***
#spring.cloud.config.server.git.password=***
# 仓库分支
spring.cloud.config.label=master
  • 启动类添加 @EnableEurekaClient、@EnableConfigServer 注解;
  • 启动服务,进行测试,配置文件访问规则如下;
    • /{application}/{profile}/[label] ----
    • {application}:
    • http://127.0.0.1:8766/applicationTest/dev/master
    • /{label}/{application}-{profile}.properties ----
    • http://127.0.0.1:8766/applicationAccount/dev/master
    • /{application}-{profile}.properties ----
    • http://127.0.0.1:8766/applicationAccount-dev.properties
    • /{label}/{application}-{profile}.properties ---- http://127.0.0.1:8766/master/applicationAccount-dev.properties

1.2 Config Client

1.简介

    • 在微服务中添加 Config Client 支持,用来读取 Config Server 中配置文件的内容;
    • Spring Cloud 2020+ 版本默认不再启动 Bootstrap 父上下文配置,需要在微服务 Pom 中添加 spring-cloud-starter-bootstrap 依赖,并在配置中开启 spring.cloud.bootstrap.enabled=true;

2.流程

    • 微服务启动,到注册中心中去注册,才能获取配置中心提供的功能,去读取git仓库中的配置文件
    • 注册中心配置,就需要从application.properties移到bootstrap.properties
    ■ 加入bootstrap支持,手动添加,pom
    ■ 创建bootstrap.properties
    ■ 拆分application.properties
    ■ 注册部分,拆解到bootstrap.properties,其余拆解到git仓库对应的配置文件上。

3.实现(改造微服务 Client-Test/Account,其余微服务类似)

  • Pom 导入
<!-- config client -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-client</artifactId>
</dependency>
<!-- bootstrap for spring cloud 2020+ -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>

4.重构配置

  • 配置改动思路
  • 将本地 application.properties 中的配置分成两个部分
    • Eureka 相关配置、引入的其余配置文件配置(比如 logback.xml),移植到 bootstrap.properties 配置文件中,注册微服务;
    • 删除 application.properties 配置(我对之进行重命名备份);
    • 其余配置项,移植到远程配置仓库中对应的配置文件中;
  • 配置加载顺序:读本地 bootstrap.properties---- 到注册中心注册服务 ---- 连接 Config Server,加载远程配置 applicationTest-dev.properties ---- 加载本地 application.properties;
  • bootstrap.properties
Test:bootstrap.properties

#for  server
server.port=8762

# for eureka client
spring.application.name=client-test
eureka.instance.hostname=localhost
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:8760/eureka

#start bootstrap
spring.cloud.bootstrap.enabled=true;

# for config discovery 开启注册发现
spring.cloud.config.discovery.enabled=true
# 指定配置中心 id
spring.cloud.config.discovery.serviceId=client-config
# 配置中心中配置文件的名字:{application}-{profile}.properties 中 {application}
spring.cloud.config.name=applicationTest

# 配置文件“外观”,可以理解为“配置环境占位符”
spring.cloud.config.profile=dev
# 配置文件在 git 远程仓库的分支
spring.cloud.config.label=master
Account:bootstrap.properties

#for  server
server.port=8763

# for eureka client
spring.application.name=client-Account
eureka.instance.hostname=localhost
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:8760/eureka

#start bootstrap
spring.cloud.bootstrap.enabled=true

# for config discovery 开启注册发现
spring.cloud.config.discovery.enabled=true
# 指定配置中心 id
spring.cloud.config.discovery.serviceId=client-config
# 配置中心中配置文件的名字:{application}-{profile}.properties 中 {application}
spring.cloud.config.name=applicationAccount
# 配置文件“外观”,可以理解为“配置环境占位符”
spring.cloud.config.profile=dev
# 配置文件在 git 远程仓库的分支
spring.cloud.config.label=master
  • applicationTest-dev.properties
# for data source
# mysql 5
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test_city?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456

# hikari pool
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=180000
spring.datasource.hikari.auto-commit=true

# for mybatis
mybatis.configuration.map-underscore-to-camel-case=true
  • applicationAccount-dev.properties
# for data source
# mysql 5
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test_city?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456

# hikari pool
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=180000
spring.datasource.hikari.auto-commit=true

# for mybatis
mybatis.configuration.map-underscore-to-camel-case=true

删除 application.properties 或重命名备份;

5. 测试

  • 微服务启动顺序:
    • Java_Spring_Cloud_Register
    • Client_Config
    • Client_test
    • Client_Account
    • Client_Gateway;
  • 访问注册中心:
    • http://127.0.0.1:8760
  • 访问配置中心文件:
    • http://127.0.0.1:8766/applicationTest/dev/master
    • http://127.0.0.1:8766/applicationAccount/dev/master

    • =========================================
    • http://127.0.0.1:8766/applicationAccount-dev.properties
    • http://127.0.0.1:8766/master/applicationAccount-dev.properties
  • 访问 Client_test 接口;
  •   原生接口:127.0.0.1:8762/api/test/city/1890
    • 网关接口:127.0.0.1:8888/api/test/city/1890
  • 访问 Client_Account 接口;
  • 原生接口:127.0.0.1:8763/api/account/userVo/1/1890
    • 网关接口:127.0.0.1:8888/api/account/userVo/1/1890

  •  

10. 将页面移植到微服务中进行联调

1.启动接口

1.1 端口列表

注册中心:8760

配置中心:8766

test.微服务:8761、8762.

accounL 微服务:8763、8764

前端wcb项目:8765

网关:8888

1.2.测试 接口

  • 访问 Client_test 接口;
    • 原生接口:127.0.0.1:8762/api/test/city/1890
    • 网关接口:127.0.0.1:8888/api/test/city/1890
  • 访问 Client_Account 接口;
    • 原生接口:127.0.0.1:8763/api/account/userVo/1/1890
    • 网关接口:127.0.0.1:8888/api/account/userVo/1/1890

1.3.启动步骤

启动注册中心,访问注册中心页面

启动配置中心,访问注册中心页面,查看注册是否成功,调用配置中心接口,读取配置文件是否成功

启动test微服务,访问注册中心页面,查看注册是否成功

--------

启动网关微服务,访问注册中心页面,查看是否注册成功

对各个微服务接口进行测试

前端文本web项目:

spring mvc

2.创建web微服务:spring web、注册客户端

3.移植前端页面过来

将thmeleaf引进了:

pom,配置

静态资源、页面、页面控制器一直到web微服务,移植拦截器、webMVC配置类(只留下拦截器)

启动项目,检查是否报错,页面能否访问。

修改Ajax请求

页面测试,检查并修复。

spring cloud阶段就打上了句号!继续加油哟!

​​​​​​​

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值