一、前言
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”后,协调者和其中的参与者都宕机,没人知道事务是否提交;
- 协调者获得参与者“同意”时,发出“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
这样直接点确定,就好了,然后去测试,打开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
- application.properties
- 启动项目,访问接口;
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;配置
- Pom 无需引入 Jar;
-
-
- 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阶段就打上了句号!继续加油哟!