一、微服务介绍
1、简介
微服务(或称微服务架构)是一种分布式架构方案,即,一个应用应该是一组小型服务,每个服务器只负责一种服务,服务之间可以通过 HTTP 的方式进行互通。每一个功能元素最终都是一个可独立替换和独立升级的软件单元。
微服务架构特征:
- 单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责,避免重复业务开发
- 面向服务:微服务对外暴露业务接口
- 自治:团队独立、技术独立、数据独立、部署独立
- 隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题
微服务:一种良好的分布式架构方案
优点:拆分粒度更小、服务更独立、耦合度更低
缺点:架构非常复杂,运维、监控、部署难度提高
2、学习的知识点
3、学习路线
二、单体架构和分布式架构
1、单体架构
简单方便,高度耦合,扩展性差,适合小型项目。例如:学生管理系统
2、分布式架构
松耦合,扩展性好,但架构复杂,难度大。适合大型互联网项目,例如:京东、淘宝
三、微服务结构 与 主流技术对比
1、微服务结构
2、微服务技术对比
四、Spring Cloud 介绍
1、简介
- SpringCloud是目前国内使用最广泛的微服务框架。官网地址:https://spring.io/projects/spring-cloud。
- SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验。
2、兼容的 SpringBoot 版本
五、服务拆分
1、注意事项
- 单一职责:不同微服务,不要重复开发相同业务
- 数据独立:不要访问其它微服务的数据库
- 面向服务:将自己的业务暴露为接口,供其它微服务调用
2、服务拆分项目
这是一个拆分好的基于 SpringBoot 的 Spring Cloud 项目,如下:
链接:https://pan.baidu.com/s/1DG4B-Hlyab1bVDRxjSh5nQ?pwd=k1f4
提取码:k1f4
六、服务远程调用(RestTemplate)
各服务之间可以通过发起 http 请求获取对方数据
1、注册 RestTemplate 工具类
Spring 提供了 RestTemplate 工具类,可以用于各服务之间发起 HTTP 请求
通过编写配置类,将 RestTemplate 注入到 Spring 容器中
2、发起HTTP请求
在消费者端的 Service 层编写代码
例如 OrderService 向 UserService 发起请求获取数据:
getForObject 方法是发起 Get 方式请求,具体的请求方式以 要请求的接口规范更改
七、提供者 与 消费者概念
- 服务提供者:一次业务中,被其它微服务调用的服务。(提供接口给其它微服务)
- 服务消费者:一次业务中,调用其它微服务的服务。(调用其它微服务提供的接口)
服务调用关系
- 服务提供者:暴露接口给其它微服务调用
- 服务消费者:调用其它微服务提供的接口
- 提供者与消费者角色其实是相对的
- 一个服务可以同时是服务提供者和服务消费者
八、Eureka 注册中心组件
1、没有注册中心引出的问题
根据前面的服务远程调用案例来看,从一个服务消费者向一个服务提供者发起请求,需要将请求路径写死在代码中,容易引发如下问题:
- 若服务提供者地址有变动时,需要手动修改请求路径,并重新启动服务器才能生效。
- 若有多个服务提供者(服务器集群),消费者想要向不同的服务提供者发起请求,也是需要每次手动修改请求路径,并重新启动服务器才能生效。
2、Eureka 注册中心作用
- 消费者该如何获取服务提供者具体信息?
- 服务提供者启动时向eureka注册自己的信息
- eureka保存这些信息
- 消费者根据服务名称向eureka拉取提供者信息
- 如果有多个服务提供者,消费者该如何选择?
- 服务消费者利用负载均衡算法,从服务列表中挑选一个
- 消费者如何感知服务提供者健康状态?
- 服务提供者会每隔30秒向EurekaServer发送心跳请求,报告健康状态
- eureka会更新记录服务列表信息,心跳不正常会被剔除
- 消费者就可以拉取到最新的信息
Eureka-Server:注册中心,存放 Eureka 客户端信息
Euraka-Client:Euraka 客户端,包含服务提供者和服务消费者。
在Eureka架构中,微服务角色有两类:
- EurekaServer:服务端,注册中心
- 记录服务信息
- 心跳监控
- EurekaClient:客户端
- Provider:服务提供者,例如案例中的 user-service
- 注册自己的信息到EurekaServer
- 每隔30秒向EurekaServer发送心跳
- consumer:服务消费者,例如案例中的 order-service
- 根据服务名称从EurekaServer拉取服务列表
- 基于服务列表做负载均衡,选中一个微服务后发起远程调用
九、搭建 Eureka 服务
1、搭建 Eureka 服务端
- 创建项目,引入spring-cloud-starter-netflix-eureka-server的依赖
-
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
-
- 编写启动类,添加@EnableEurekaServer注解
- 添加application.yml文件,编写下面的配置:
-
server: port: 10086 # 服务端口 spring: application: name: eurekaserver # eureka 的服务名称 eureka: client: service-url: # eureka 服务的地址信息,若有多个 eureka 服务地址用空格隔开,组成集群 defaultZone: http://127.0.0.1:10086/eureka/
-
- 启动 Eureka 服务
2、搭建 Eureka 客户端
多个客户端的注册步骤相同
注册成功示例:
所有注册到 Eureka 的服务都会在实例列表中展示出来
3、在 IDEA 中多开实例(拓展)
4、Eureka 服务拉取
示例:
5、Eureka 注册中心搭建总结
十、Ribbon 负载均衡
1、负载均衡原理
原理流程:
Ribbon 通过 @LoadBalanced 注解标记了 RestTemplate 工具类,所以 Ribbon 就会去拦截 RestTemplate 所发起的 HTTP 请求,并做出一系列对策。
首先,Ribbon 底层通过 LoadBalancerInterceptor 拦截器对 RestTemplate 的HTTP请求进行拦截,再将拦截到的服务名称传递给 Ribbon 的负载均衡客户端 RibbonLoadBalancerClient
负载均衡客户端会根据服务名称去找 Eureka 拉取服务列表
负载均衡客户端再通过 IRule 接口选出负载均衡策略,并根据策略返回服务地址
最后 RestTemplate 会将返回的服务地址替换掉原本服务名称,并通过HTTP请求访问真实的服务地址,最终实现负载均衡。
2、负载均衡策略
Ribbon的负载均衡规则是一个叫做IRule的接口来定义的,每一个子接口都是一种规则:
3、配置负载均衡策略
可知默认的负载均衡策略是 RoundRobinRule 轮询策略,想要修改轮询策略可以通过以下两种方式:
1)方式一:通过 @Bean 注解注入到容器
创建新的 IRule 策略对象,并注入到 Spring 容器中
示例:
2)方式二:修改配置文件
示例:
4、配置饥饿加载策略
Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。 而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:
示例:
多个服务名称,换行并用 - 隔开
懒加载,第一次访问耗时约:
饥饿加载,第一次访问耗时约:
5、Ribbon 负载均衡小结
- Ribbon负载均衡规则
- 规则接口是IRule
- 默认实现是ZoneAvoidanceRule,根据zone选择服务列表,然后轮询
- 负载均衡自定义方式
- 代码方式:配置灵活,但修改时需要重新打包发布
- 配置方式:直观,方便,无需重新打包发布,但是无法做全局配置
- 饥饿加载
- 开启饥饿加载
- 指定饥饿加载的微服务名称
十一、Nacos 注册中心组件
Nacos是阿里巴巴的产品,现在是SpringCloud中的一个组件。相比Eureka功能更加丰富,在国内受欢迎程度较高。
1、安装
Nacos 安装指南:
链接:https://pan.baidu.com/s/1WpYPm9qIx_CXE8RIGxCJHA?pwd=l827
提取码:l827
1)下载安装包
在Nacos的GitHub页面,提供有下载链接,可以下载编译好的Nacos服务端或者源代码:
GitHub主页:https://github.com/alibaba/nacos
GitHub的Release下载页:https://github.com/alibaba/nacos/releases
2)解压
将这个包解压到任意非中文目录下,如图:
目录说明:
bin:启动脚本
conf:配置文件
3)端口配置
Nacos的默认端口是8848,如果你电脑上的其它进程占用了8848端口,请先尝试关闭该进程。
如果无法关闭占用8848端口的进程,也可以进入nacos的conf目录,修改配置文件中的端口:
修改其中的内容:
4)启动
启动非常简单,进入bin目录,结构如下:
然后执行命令即可:
-
windows命令:
startup.cmd -m standalone
执行后的效果如图:
5)访问
在浏览器输入地址:http://127.0.0.1:8848/nacos即可:
默认的账号和密码是nacos,进入:
2、Nacos 服务注册和发现
- 在cloud-demo父工程中添加spring-cloud-alilbaba的管理依赖:
- 注释掉order-service和user-service中原有的eureka依赖。
- 添加nacos的客户端依赖:
- 修改user-service&order-service中的application.yml文件,注释eureka地址,添加nacos地址:
- 启动并测试
3、Nacos 服务集群分级和部署
1)服务分级存储模型
- 一级是服务,例如userservice
- 二级是集群,例如杭州或上海
- 三级是实例,例如杭州机房的某台部署了userservice的服务器
2)服务跨集群调用问题
服务调用尽可能选择本地集群的服务,跨集群调用延迟较高
本地集群不可访问时,再去访问其它集群
3)服务集群属性配置
给实例分配集群
- 修改application.yml,添加如下内容
- 在Nacos控制台可以看到集群变化
可以配置多个集群,如下:
4、Nacos 负载均衡
1)配置负载均衡
Nacos 注册中心的默认策略方案是 NacosRule
NacosRule策略特点:
- 优先选择同集群服务实例列表
- 本地集群找不到提供者,才去其它集群寻找,并且会报警告
- 确定了可用实例列表后,再采用随机负载均衡挑选实例
NacosRule 策略会优先访问本地集群,然后在通过随机策略访问集群中的服务实例。
(只有本地集群无法访问到才会访问外地集群,访问外地集群时,会输出类似以下的提醒信息)
- 修改order-service中的application.yml,设置集群为HZ:
- 然后在order-service中设置负载均衡的IRule为NacosRule,这个规则优先会寻找与自己同集群的服务
2)权重负载均衡策略
权重负载均衡策略特点:
- Nacos控制台可以设置实例的权重值,0~1之间
- 同集群内的多个实例,权重越高被访问的频率越高
- 权重设置为0则完全不会被访问
进入 Nacos 注册中心控制台,可直接编辑实例的权重值,如下图:
5、Nacos 环境隔离
1)环境隔离概念
Nacos中服务存储和数据存储的最外层都是一个名为namespace的东西,用来做最外层隔离
2)环境隔离配置
特点:
- 每个namespace都有唯一id
- 服务设置namespace时要写id而不是名称
- 不同namespace下的服务互相不可见(无法相互访问)
- 在Nacos控制台可以创建namespace,用来隔离不同环境
- 填写一个新的命名空间信息
- 保存后会在控制台看到这个命名空间的id
- 修改order-service(消费者)的application.yml,添加namespace
- 重启order-service后,再来查看控制台
- 此时访问order-service,因为namespace不同,会导致找不到userservice,控制台会报错
十二、Nacos 注册中心与 Eureka 注册中心的对比
1、Nacos 注册中心原理图
2、Nacos 配置临时实例和非临时实例
服务注册到Nacos时,可以选择注册为临时或非临时实例,通过下面的配置来设置:
临时实例宕机时,会从nacos的服务列表中剔除,而非临时实例则不会
3、Nacos 注册中心与 Eureka 注册中心的共同点和区别
共同点:
- 都支持服务注册和服务拉取
- 都支持服务提供者心跳方式做健康检测
区别:
- Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
- 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
- Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
- Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式
(AP模式:强调集群的可用性;CP模式:强调集群的可靠性、一致性)
十三、Nacos 配置管理
1、添加统一配置
- 在Nacos中添加配置信息:
- 在弹出表单中填写配置信息
- profile:指的是环境名称,例如:dev开发环境,test测试环境等
- 配置内容:填写所有服务中有热更新需求的内容(可以开关的内容),例如上面的时间展示功能
- 发布
2、拉取统一配置
1)项目启动路线的变更
2)拉取配置步骤
- 在 userservice 引入Nacos的配置管理客户端依赖
- 在userservice中的resource目录添加一个bootstrap.yml文件,这个文件是引导文件,优先级高于application.yml:
-
- 注:服务名称 + 开发环境 + 文件后缀名 = 配置文件id
-
- 属性注入,我们将配置文件中的pattern.dateformat属性通过@Value注解注入到dateformat属性中做测试
3、配置热更新(自动更新)
Nacos中的配置文件变更后,微服务无需重启就可以感知。有以下两种方式实现:
1)方式一:在@Value注入的变量所在类上添加注解@RefreshScope
2)方式二:使用@ConfigurationProperties注解
通过定义配置类的方式注册到 Spring 容器中,再通过 @Autowired 注解实现属性注入
prefix 是统一配置文件中属性的前缀
可以写多个属性
3、多环境配置共享
微服务启动时会从nacos读取多个配置文件:
- [spring.application.name]-[spring.profiles.active].yaml,例如:userservice-dev.yaml
- [spring.application.name].yaml,例如:userservice.yaml
无论profile如何变化,[spring.application.name].yaml这个文件一定会加载,因此多环境共享配置可以写入这个文件
只以服务名称命名的配置文件,就是多环境共享的。
例如:下图的 userservice.yaml
案例测试:
- 新建一个 userservic.yaml 共享的统一配置文件,并定义一个变量为:envSharedValue
- 如下图所示,之前配置了一个 dev 环境的配置文件,里面有个属性是 dateformat,现在一共有两个统一配置文件
- 将共享配置文件的属性页配置到配置类中,并注入到 Controller 类中
- 页面会将对象类型的 方法返回值解析为JSON格式,并展示到页面
- 启动两个不同环境的服务 userservice1(dev环境,8081端口)、userservice2(test环境,8082端口),会发现两个不同环境的服务都能共享到 userservice.yaml 配置文件中的 envSharedValue 属性。
4、配置文件的优先级
当本地配置 和 nacos 的远端配置 都定义了同名的属性时,遵循以下优先级:
服务名-开发环境.yaml > 服务名.yaml > 本地配置
注:本地配置 是指 application.yml
举例:
当有三个配置文件 userservice-dev.yaml 、 userservice.yaml 、application.yml 都有 name 属性时,优先级为:userservice-dev.yaml > userservice.yaml > application.yml
十四、搭建 nacos 集群
nacos 集群搭建教程:
链接:https://pan.baidu.com/s/1dk9sAOXJOZ1QSJqNTNH6Fg?pwd=quw5
提取码:quw5
集群搭建步骤
- 搭建MySQL集群并初始化数据库表
- 下载解压nacos
- 修改集群配置(节点信息)、数据库配置
- 分别启动多个nacos节点
- nginx反向代理
由于只有一台机器,不方便搭建集群。所以就不演示了
十五、Feign 客户端
1、Feign 介绍
Feign是一个声明式的http客户端,官方地址:https://github.com/OpenFeign/feign
其作用就是帮助我们优雅的实现http请求的发送,解决上面提到的问题。
为什么要学习?
先来看我们以前利用RestTemplate发起远程调用的代码:
存在下面的问题:
- 代码可读性差,编程体验不统一
- 参数复杂URL难以维护
RestTemplate 如果在 URL 上传递多个参数时,会变得很复杂,难以维护。
例如:
百度搜索时,URL 的复杂程度就知道了
2、定义和使用 Feign 客户端
使用Feign的步骤如下:
- 引入依赖:
- 在 order-service 的启动类添加注解开启 Feign 的功能:
- 编写Feign客户端:
- 主要是基于SpringMVC的注解来声明远程调用的信息,比如:
- 目标服务名称:userservice
- 请求方式:GET
- 请求路径:/user/{id}
- 请求参数:Long id
- 返回值类型:User
- 用 Feign 客户端代替RestTemplate
- UserClient 本质是个接口,是无法直接创建对象的。所以底层通过动态代理的技术创建对象。
- 启动 orderservice 服务,向 userservice 发起请求即可
十六、Feign 自定义配置
Feign运行自定义配置来覆盖默认配置,可以修改的配置如下:
一般我们需要配置的就是日志级别。
1、配置 Feign 日志
日志等级建议:一般推荐 NONE 或 BASIC,需要调试错误时 FULL(因为更详细)
1)方式一:通过配置文件 application.yml
①全局生效配置:
②局部生效配置:
若是局部生效就写服务名,若是全局生效就写 default
2)方式二:通过自定义配置类
①先定义配置类
②全局生效配置:
如果是全局配置,则把它放到@EnableFeignClients这个注解中:
③局部生效配置:
如果是局部配置,则把它放到@FeignClient这个注解中:
十七、Feign 性能优化
1、前言
由于 Feign 是基于 HTTP 技术实现的,HTTP 底层又使用的 TCP 协议,建立连接时需要“三次握手四次挥手”,所以使用 Feign 每次发起请求时都会有性能消耗,这时候可以采用 连接池 的方式对 Feign 进行性能优化。
Feign 底层客户端实现:
- URLConnection:默认实现方式,不支持连接池
- Apache HttpClient:支持连接池
- OKHttp:支持连接池
因此优化Feign的性能主要包括:
- 使用连接池代替默认的URLConnection
- 日志级别,最好用basic或none
2、连接池配置
- 引入 HttpClient 依赖
- 配置连接池
- application.yml
十八、Feign 最佳实践
- 让controller和FeignClient继承同一接口
- 将FeignClient、POJO、Feign的默认配置都定义到一个项目中,供所有消费者使用
原因:
消费者的客户端 FeignClient 代码与提供者的 Controller 层代码高度重合。
1、方式一(统一接口)(不推荐)
给消费者的 Feign 客户端与提供者的 controller 定义统一的父接口作为标准。
缺点:
2、方式二(抽取模块)
将FeignClient抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用。
实现步骤
实现方式二的步骤如下:
- 首先创建一个module,命名为feign-api,然后引入feign的starter依赖
- 将order-service中编写的UserClient、User、DefaultFeignConfiguration都移动到feign-api项目中(剪切)
- 在order-service中引入feign-api的依赖
- 修改order-service中的所有与上述三个组件有关的import部分,改成导入feign-api中的包
- 在消费者启动类中,添加 Feign 客户端扫描范围
- 扫描整个包
- 扫描单个类
- 重启测试
十九、Gateway 统一网关
1、网关的作用
对用户请求进行:
- 身份认证
- 权限校验
- 服务路由
- 负载均衡
- 请求限流
2、搭建网关服务
- 创建新的 module,引入 SpringCloudGateway 的依赖、nacos 服务发现依赖
- 创建module
- 引入依赖
- 编写启动类
配置路由和 nacos 地址
网关路由可以配置的内容包括:
- 路由id:路由唯一标示
- uri:路由目的地,支持lb和http两种
- predicates:路由断言,判断请求是否符合要求,符合则转发到路由目的地
- filters:路由过滤器,处理请求或响应
在 application 配置文件中编写
也可以配置多个路由
3、路由断言工厂 Route Predicate Factory
Predicate Factory 的作用:读取用户定义的断言规则,并解析成对应的判断条件
Path=/user/** 的含义:只匹配 /user 开头的请求路径
- 我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件
- 例如Path=/user/**是按照路径匹配,这个规则是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的
- 像这样的断言工厂在SpringCloudGateway还有十几个
例:新增 Before 规则,只接收某个时间之前的用户请求
4、路由过滤器 GatewayFilter 介绍
GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理
5、路由过滤器工厂 GatewayFilter Factory
1)路由过滤器
路由过滤器:只对当前路由的请求生效。
案例:给所有进入userservice的请求添加一个请求头
需要添加的请求头:Truth=itcast is freaking awesome!
实现方式:在gateway中修改application.yml文件,给userservice的路由添加过滤器
在 Controller 层获取请求头参数:
2)默认过滤器
以上案例中,采用的是局部过滤器,只对某个路由生效
- 局部过滤器:对当前路由生效
- 默认过滤器:对所有路由生效
默认过滤器配置如下
3)全局过滤器
全局过滤器是一个能对所有请求进行过滤的过滤器,可以通过编写代码来控制过滤逻辑业务。
默认过滤器与全局过滤器的区别:
- 相同点:
- 都是对所有的请求路由进行过滤。
- 不同点:
- 默认过滤器:通过配置文件方式定义,处理逻辑是固定的。
- 全局过滤器:通过编写代码方式定义,处理逻辑是自定义的。
定义方式:实现 GlobalFilter接口,重写 filter 方法。
全局过滤器案例:定义全局过滤器,拦截并判断用户身份
需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件
- 参数中是否有authorization
- authorization参数值是否为admin
如果同时满足则放行,否则拦截
代码如下:
@Order注解是用于定义拦截器的优先级,值越小优先级越高。
定义拦截器优先级的方式有两种:
- 通过 @Order 注解
- 通过实现 Ordered 接口,并重写 getOrder 方法(与注解方式作用相同),如下图:
6、过滤器执行顺序
过滤器链的执行顺序:
在一整个过滤器链中,包含三个不同的过滤器,并且按照以下执行顺序排序:
默认过滤器 > 路由过滤器 > 全局过滤器
各过滤器执行优先级:
- 每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前。
- GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
- 路由过滤器和默认过滤器的order由Spring指定,默认是按照声明顺序从1递增。
- 当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。
可以参考下面几个类的源码来查看:
过滤器链组成原理:
因为这三个过滤器都是相同的类型(GatewayFilter),所以才能组成一个过滤器链(集合)
在过滤器工厂 GatewayFilter Factory 执行时,会去读取 application 文件中定义的 路由过滤器、默认过滤器的配置,并会根据配置生成一个真正的 GatewayFilter 类型的过滤器。
并且由于 全局过滤器 是 GlobalFilter类型的过滤器,则由网关过滤器适配器类 GatewayFilterAdapter 来将 GlobalFilter 类型的过滤器转换成 GatewayFilter 类型的过滤器。
7、跨域问题处理
1)跨域问题介绍和处理方式
跨域:地址不一致时,就是跨域,主要包括:
- 域名不同:www.taobao.com 和 www.taobao.org 和 www.jd.com 和 miaosha.jd.com
- 域名相同,端口不同:localhost:8080 和 localhost:8081
跨域问题:浏览器禁止请求的发起者与服务端发生跨域Ajax请求,请求被浏览器拦截的问题
跨域问题解决方案:CORS
CORS 作用:允许浏览器发起跨域的Ajax请求。
(服务器之间互相访问时不会存在跨域问题,因为只是浏览器禁止Ajax请求跨域)
网关处理跨域采用的同样是CORS方案,并且只需要简单配置即可实现:
2)案例测试
前端页面代码,发起ajax跨域请求
后端服务器代码:
请求成功结果:
请求失败结果:
(提示 CORS 策略出问题)