引言
为什么要学习微服务?
需要学习哪些知识?
1、认识微服务
1.1 服务架构演变
单体架构
分布式架构
服务治理:
总结
微服务是一种经过良好架构设计的分布式架构方案,微服务架构特征:
-
单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责,避免重复业务开发
-
面向服务:微服务对外暴露业务接口
-
自治:团队独立、技术独立、数据独立、部署独立
-
隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题
单体架构特点?
- 简单方便,高度耦合,扩展性差,适合小型项目。例如:学生管理系统
分布式架构特点?
- 松耦合,扩展性好,但架构复杂,难度大。适合大型互联网项目,例如:京东、淘宝
- 微服务:一种良好的分布式架构方案
- 优点:拆分粒度更小、服务更独立、耦合度更低
- 缺点:架构非常复杂,运维、监控、部署难度提高
1.2 微服务技术对比
微服务结构
微服务技术对比
企业需求
1.3 SpringCloud
介绍
SpringCloud 是目前国内使用最广泛的微服务框架。
官网地址。
SpringCloud 集成了各种微服务功能组件,并基于 SpringBoot 实现了这些组件的自动装配,从而提供了良好的开箱即用体验:
版本对应关系
2、服务拆分及远程调用
2.1 服务拆分注意事项
- 单一职责:不同微服务,不要重复开发相同业务
- 数据独立:不要访问其它微服务的数据库
- 面向服务:将自己的业务暴露为接口,供其它微服务调用
总结
- 微服务需要根据业务模块拆分,做到单一职责,不要重复开发相同业务
- 微服务可以将业务暴露为接口,供其它微服务使用
- 不同微服务都应该有自己独立的数据库
2.2 微服务远程调用
案例
总结:
微服务调用方式
- 基于
RestTemplate
发起的 http 请求实现远程调用 - http 请求做远程调用是与语言无关的调用,只要知道对方的 ip、端口、接口路径、请求参数即可。
服务者和消费者
- 服务提供者:一次业务中,被其它微服务调用的服务。(提供接口给其它微服务)
- 服务消费者:一次业务中,调用其它微服务的服务。(调用其它微服务提供的接口)
总结
服务调用关系
- 服务提供者:暴露接口给其它微服务调用
- 服务消费者:调用其它微服务提供的接口
- 提供者与消费者角色其实是相对的
- 一个服务可以同时是服务提供者和服务消费者
3、Eureka 注册中心
3.1 服务调用出现的问题
- 服务消费者该如何获取服务提供者的地址信息?
- 如果有多个服务提供者,消费者该如何选择?
- 消费者如何得知服务提供者的健康状态?
3.2 Eureka 的作用
- 消费者该如何获取服务提供者具体信息?
- 服务提供者启动时向 Eureka 注册自己的信息
- Eureka 保存这些信息
- 消费者根据服务名称向 Eureka 拉取提供者信息
- 如果有多个服务提供者,消费者该如何选择?
- 服务消费者利用负载均衡算法,从服务列表中挑选一个
- 消费者如何感知服务提供者健康状态?
- 服务提供者会每隔 30 秒向 EurekaServer 发送心跳请求,报告健康状态
- Eureka 会更新记录服务列表信息,心跳不正常会被剔除
- 消费者就可以拉取到最新的信息
总结:
在 Eureka 架构中,微服务角色有两类:
-
EurekaServer:服务端,注册中心
- 记录服务信息
- 心跳监控
-
EurekaClient:客户端
- Provider:服务提供者,例如案例中的 user-service
- 注册自己的信息到 EurekaServer
- 每隔 30 秒向 EurekaServer 发送心跳
- consumer:服务消费者,例如案例中的 order-service
- 根据服务名称从 EurekaServer 拉取服务列表
- 基于服务列表做负载均衡,选中一个微服务后发起远程调用
- Provider:服务提供者,例如案例中的 user-service
3.3 搭建注册中心
1、引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-serve</artifactId>
</dependency>
2、加注解
给启动类添加 @EnableEurekaServer
注解
3、编写 Spring Boot 配置类
server:
port: 10086 # 服务端口
spring:
application:
name: eurekaserver # eureka的服务名称
eureka:
client:
service-url: # eureka的地址信息
defaultZone: http://127.0.0.1:10086/eureka
总结
搭建 EurekaServer
- 引入 eureka-server 依赖
- 添加
@EnableEurekaServe
r注解 - 在
application.yml
中配置 eureka 地址
3.4 服务注册
1、引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2、编写配置类
eureka:
client:
service-url: # eureka的地址信息
defaultZone: http://127.0.0.1:10086/eureka
模拟多实例部署:
总结
- 服务注册
- 引入 eureka-client 依赖
- 在 application.yml 中配置 eureka 地址
- 无论是消费者还是提供者,引入 eureka-client 依赖
- 知道 eureka 地址后,都可以完成服务注册
3.5 服务发现
服务拉取是基于服务名称获取服务列表,然后在对服务列表做负载均衡:
- 修改 OrderService 的代码,修改访问的 url 路径,用服务名代替 ip、端口:
String url = "http://userservice/user/" + order.getUserId();
- 在 order-service 项目的启动类 OrderApplication 中的 RestTemplate 添加负载均衡注解:
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
3.6 总结
1、搭建 EurekaServer
- 引入 eureka-server 依赖
- 添加
@EnableEurekaServer
注解 - 在 application.yml 中配置 eureka 地址
2、服务注册
- 引入 eureka-client 依赖
- 在 application.yml 中配置 eureka 地址
3、服务发现
-
引入 eureka-client 依赖
-
在 application.yml 中配置 eureka 地址
-
给 RestTemplate 添加
@LoadBalanced
注解 -
用服务提供者的服务名称进行远程调用
4、Ribbon 负载均衡
4.1 负载均衡原理
负载均衡流程
4.2 负载均衡策略
通过定义 IRule 实现可以修改负载均衡规则,有两种方式:
1、代码方式:
在 order-service 中的 OrderApplication 类中,定义一个新的 IRule:
@Bean
public IRule randomRule() {
return new RandomRule();
}
2、配置文件方式:
在 order-service 的 application.yml 文件中,添加新的配置也可以修改规则
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
4.3 懒加载
Ribbon 默认是采用懒加载,即第一次访问时才会去创建 LoadBalanceClient,请求时间会很长。
而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:
ribbon:
eager-load:
enabled: true # 开启饥饿加载
clients: # 指定饥饿加载的服务名称
- userservice
4.4 总结
1、Ribbon 负载均衡规则
- 规则接口是 IRule
- 默认实现是 ZoneAvoidanceRule,根据 zone 选择服务列表,然后轮询
2、负载均衡自定义方式
- 代码方式:配置灵活,但修改时需要重新打包发布
- 配置方式:直观,方便,无需重新打包发布,但是无法做全局配置
3、饥饿加载
- 开启饥饿加载
- 指定饥饿加载的微服务名称
5、Nacos 注册中心
5.1 认识和安装 Nacos
认识 Nacos
Nacos 是阿里巴巴的产品,现在是 SpringCloud 中的一个组件。相比 Eureka 功能更加丰富,在国内受欢迎程度较高。
安装 Nacos
直接下载 zip 压缩包就行,然后解压即可。
双击 bin 目录下的 startup.cmd 文件就可以直接启动了。
5.2 Nacos 快速入门
1、引入依赖
<!-- 统一依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Boot 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- nacos 依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- 排除内置 Tomcat -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 使用 Jetty 服务器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
2、编写配置文件
spring:
application:
name: nacos-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # nacos 服务端地址
3、运行结果
总结
1、Nacos 服务搭建
(1)下载安装包
(2)解压
(3)在 bin 目录下运行指令:startup.cmd -m standalone
2、Nacos 服务注册或发现
(1)引入 nacos.discovery 依赖
(2)配置 nacos 地址 spring.cloud.nacos.server-addr
5.3 Nacos 服务分级存储模型
(1)更改配置文件
spring:
application:
name: nacos-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # nacos 服务端地址
cluster-name: HZ # 配置集群名称,也就是机房位置,例如:HZ,杭州
(2)运行结果
总结:
1、Nacos 服务分级存储模型
(1)一级是服务,例如 userservice
(2)二级是集群,例如杭州或上海
(3)三级是实例,例如杭州机房的某台部署了 userservice 的服务器
2、如何设置实例的集群属性
修改 application.yml 文件,添加 spring.cloud.nacos.discovery.cluster-name 属性即可
总结
1、NacosRule 负载均衡策略
(1)优先选择同集群服务实例列表
(2)本地集群找不到提供者,才去其它集群寻找,并且会报警告
(3)确定了可用实例列表后,再采用随机负载均衡挑选实例
总结
1、实例的权重控制
(1)Nacos 控制台可以设置实例的权重值,0 ~ 1 之间
(2)同集群内的多个实例,权重越高被访问的频率越高
(3)权重设置为 0 则完全不会被访问
5.4 Nacos 环境隔离
1、创建命名空间
总结:
1、Nacos 环境隔离
(1)每个 namespace 都有唯一 id
(2)服务设置 namespace 时要写 id 而不是名称
(3)不同 namespace 下的服务互相不可见
5.5 Nacos 注册原理
5.6 总结
1、Nacos 与 Eureka 的共同点
(1)都支持服务注册和服务拉取
(2)都支持服务提供者心跳方式做健康检测
2、Nacos 与 Eureka 的区别
(1)Nacos 支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
(2)临时实例心跳不正常会被剔除,非临时实例则不会被剔除
(3)Nacos 支持服务列表变更的消息推送模式,服务列表更新更及时
(4)Nacos 集群默认采用 AP 方式,当集群中存在非临时实例时,采用 CP 模式;Eureka 采用 AP 方式
6、Nacos 配置管理
6.1 统一配置管理
1、在 Nacos 中添加配置信息:
2、在表单中填写相应信息:
3、获取配置的步骤:
(1)引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
(2)编写 bootstrap.xml 文件
这个文件是引导文件,优先级高于 application.yml
spring:
profiles:
active: dev
cloud:
nacos:
config:
file-extension: yaml # 文件后缀名
总结:
将配置交给 Nacos 管理的步骤:
(1)在 Nacos 中添加配置文件
(2)在微服务中引入 nacos 的 config 依赖
(3)在微服务中添加 bootstrap.yml,配置 nacos 地址、当前环境、服务名称、文件后缀名。这些决定了程序启动时去 nacos 读取哪个文件
6.2 配置热更新
Nacos中 的配置文件变更后,微服务无需重启就可以感知。
两种方式:
(1)@RefreshScope
(2)@ConfigurationProperties
总结
Nacos 配置更改后,微服务可以实现热更新,两种方式:
(1)通过 @Value
注解注入,结合 @RefreshScope
来刷新
(2)通过 @ConfigurationProperties
注入,自动刷新
注意点:
(1)不是所有的配置都适合放到配置中心,维护起来比较麻烦
(2)建议将一些关键参数,需要运行时调整的参数放到 nacos 配置中心,一般都是自定义配置
6.3 配置共享
总结
微服务从 nacos 读取的配置文件:
(1)[服务名]-[spring.profile.active].yaml,环境配置
(2)[服务名].yaml,默认配置,多环境共享
优先级:
(1)[服务名]-[环境].yaml >[服务名].yaml > 本地配置
6.4 搭建 Nacos 集群
7、Feign Http 客户端
7.1 Feign 替代 RestTemplate
RestTemplate 方式调用存在的问题:
- 代码可读性差,编程体验不统一
- 参数复杂 URL 难以维护
Feign 的介绍
Feign是一个声明式的 http 客户端。
官方地址
其作用就是帮助我们优雅的实现 http 请求的发送,解决上面提到的问题。
使用 Feign 的步骤如下:
1、引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2、在启动类上添加注解
@EnableFeignClients
3、定义 Feign 接口
@FeignClient("userService")
public interface Client {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
说明:
主要是基于SpringMVC 的注解来声明远程调用的信息,比如:
- 服务名称:userservice
- 请求方式:GET
- 请求路径:/user/{id}
- 请求参数:Long id
- 返回值类型:User
4、进行使用
// 自动注入
@Autowired
private Client client;
// 调用相应的方法
client.findById(1L);
总结
Feign 的使用步骤:
(1)引入依赖
(2)添加 @EnableFeignClients
注解
(3)编写 FeignClient 接口
(3)使用 FeignClient 中定义的方法代替 RestTemplate
7.2 自定义配置
配置 Feign 日志有两种方式:
(1)全局生效
(2)局部生效
7.3 Feign 使用优化
Feign 底层的客户端实现:
- URLConnection:默认实现,不支持连接池
- Apache HttpClient :支持连接池
- OKHttp:支持连接池
因此优化 Feign 的性能主要包括:
- 使用连接池代替默认的 URLConnection
- 日志级别,最好用 basic 或 none
添加 HttpClient 的支持:
(1)引入依赖
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
(2)编写配置
feign:
httpclient:
# 连接超时时间
connection-timeout:
# 最大的连接数
max-connections:
# 每个路径的最大连接数
max-connections-per-route:
# 开启 Feign 对 HttpClient 的支持
enabled: true
总结
- 日志级别尽量用 basic
- 使用 HttpClient 或 OKHttp 代替 URLConnection
(1)引入 feign-httpClient 依赖
(2)配置文件开启 HttpClient 功能,设置连接池参数
7.4 最佳实践
方式 1:
方式 2:
总结
Feign 的最佳实践:
(1)让 Controller 和 FeignClient 继承同一接口
(2)将 FeignClient、POJO、Feign 的默认配置都定义到一个项目中,供所有消费者使用
注意事项
不同包的 FeignClient 的导入有两种方式:
(1)在 @EnableFeignClients
注解中添加 basePackages,指定 FeignClient 所在的包
(2)在 @EnableFeignClients
注解中添加 clients,指定具体 FeignClient 的字节码
8、GateWay 网关
8.1 为什么需要网关
在 SpringCloud 中网关的实现有两种:
- GateWay(主流,推荐)
- Zuul
Zuul 是基于 Servlet 的实现,属于阻塞式编程。而 SpringCloud Gateway 则是基于 Spring5 中提供的 WebFlux,属于响应式编程的实现,具备更好的性能。
总结
网关的作用:
(1)对用户请求做身份认证、权限校验
(2)将用户请求路由到微服务,并实现负载均衡
(3)对用户请求做限流
8.2 GateWay 快速入门
1、引入依赖
注意:网关同样也要注册到 Nacos 中去!
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
2、编写配置
spring:
application:
name: gateway
cloud:
nacos:
server-addr: nacos:8848 # nacos地址
gateway:
routes:
- id: user-service # 路由标示,必须唯一
uri: lb://userservice # 路由的目标地址,lb 表示负载均衡
predicates: # 路由断言,判断请求是否符合规则
- Path=/user/** # 路径断言,判断路径是否是以/user开头,如果是则符合
- id: order-service
uri: lb://orderservice
predicates:
- Path=/order/**
default-filters:
- AddRequestHeader=Truth,Itcast is freaking awesome!
总结
网关搭建步骤:
(1)创建项目,引入 nacos 服务发现和 gateway 依赖
(2)配置 application.yml,包括服务基本信息、nacos 地址、路由
路由配置包括:
(1)路由 id:路由的唯一标示
(2)路由目标(uri):路由的目标地址,http 代表固定地址,lb 代表根据服务名负载均衡
(3)路由断言(predicates):判断路由的规则
(4)路由过滤器(filters):对请求或响应做处理
8.3 断言工厂
网关路由可以配置的内容包括:
- 路由 id:路由唯一标示
- uri:路由目的地,支持 lb 和 http 两种
- predicates:路由断言,判断请求是否符合要求,符合则转发到路由目的地
- filters:路由过滤器,处理请求或响应
路由断言工厂:
- 我们在配置文件中写的断言规则只是字符串,这些字符串会被 Predicate Factory 读取并处理,转变为路由判断的条件
- 例如 Path=/user/** 是按照路径匹配,这个规则是由
org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory
类来处理的 - 像这样的断言工厂在 SpringCloud Gateway 还有十几个
总结:
1、PredicateFactory 的作用是什么?
读取用户定义的断言条件,对请求做出判断
2、Path=/user/** 是什么含义?
路径是以 /user 开头的就认为是符合的
8.4 过滤器工厂
总结
1、过滤器的作用是什么?
- 对路由的请求或响应做加工处理,比如添加请求头
- 配置在路由下的过滤器只对当前路由的请求生效
2、defaultFilters 的作用是什么?
- 对所有路由都生效的过滤器
8.5 全局过滤器
自定义全局过滤器:
@Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1.获取请求参数
ServerHttpRequest request = exchange.getRequest();
MultiValueMap<String, String> params = request.getQueryParams();
// 2.获取参数中的 authorization 参数
String auth = params.getFirst("authorization");
// 3.判断参数值是否等于 admin
if ("admin".equals(auth)) {
// 4.是,放行
return chain.filter(exchange);
}
// 5.否,拦截
// 5.1.设置状态码
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
// 5.2.拦截请求
return exchange.getResponse().setComplete();
}
@Override
public int getOrder() {
return -1;
}
}
总结
1、全局过滤器的作用是什么?
- 对所有路由都生效的过滤器,并且可以自定义处理逻辑
2、实现全局过滤器的步骤?
- 实现 GlobalFilter 接口
- 添加 @Order 注解或实现 Ordered 接口
- 编写处理逻辑
过滤器执行顺序:
- 每一个过滤器都必须指定一个 int 类型的 order 值,order 值越小,优先级越高,执行顺序越靠前。
- GlobalFilter 通过实现 Ordered 接口,或者添加 @Order 注解来指定 order 值,由我们自己指定
- 路由过滤器和 defaultFilter 的 order 由 Spring 指定,默认是按照声明顺序从1递增。
- 当过滤器的 order 值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。
可以参考下面几个类的源码来查看:
- org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters() 方法是先加载 defaultFilters,然后再加载某个route 的 filters,然后合并。
- org.springframework.cloud.gateway.handler.FilteringWebHandler#handle() 方法会加载全局过滤器,与前面的过滤器合并后根据 order 排序,组织过滤器链
总结
路由过滤器、defaultFilter、全局过滤器的执行顺序?
(1)order 值越小,优先级越高
(2)当 order 值一样时,顺序是 defaultFilter 最先,然后是局部的路由过滤器,最后是全局过滤器
8.6 跨域问题
跨域问题处理:
跨域:域名不一致就是跨域,主要包括:
- 域名不同: www.taobao.com 和 www.taobao.org 和 www.jd.com 和 miaosha.jd.com
- 端口不同:localhost:8080和localhost8081
- 协议不同:例如 http 和 https
解决方案:CORS
8.7 限流过滤器
限流:对应用服务器的请求做限制,避免因过多请求而导致服务器过载甚至宕机。
限流算法常见的包括两种:
- 计数器算法,又包括窗口计数器算法、滑动窗口计数器算法
- 漏桶算法(Leaky Bucket)
- 令牌桶算法(Token Bucket)
(1)计数器算法
(2)漏桶算法
(3)令牌桶算法
总结
1、限流有什么作用?
- 限流是保护服务器,避免因过多请求而导致服务器过载甚至宕机
2、限流算法
- 计数器算法
- 漏桶算法
- 令牌桶算法