微服务(microservice)是一种软件架构风格,它是以专注于单一职责的很多小型项目为基础,组合出复杂的大型应用。
相关操作:服务拆分、远程调用、服务治理、请求路由、身份认证、配置管理、服务保护、分布式事务、异步通信、消息可靠性、延迟消息、分布式搜索、倒排索引、数据聚合
详细教程:飞书文档
目录
1 基本概念
1.1 单体架构与微服务架构
单体架构:将业务的所有功能集中在一个项目中开发,打成一个包部署。
- 优点:架构简单;部署成本低
- 缺点:团队协作成本高;系统发布效率低;系统可用性差
- 总结:单体架构适合开发功能相对简单,规模较小的项目。
微服务架构:在服务化思想指导下的一套最佳实践架构方案。服务化指把单体架构中的功能模块拆分为多个独立项目。
- 特点:粒度小;团队自治;服务自治
1.2 Spring Cloud
Spring Cloud(官网:spring.io/projects/spring-cloud)是目前国内使用最广泛的微服务框架,集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验。
- 服务注册发现:Eureka、Nacos、Consul
- 服务远程调用:OpenFeign、Dubbo
- 服务链路监控:Zipkin、Sleuth
- 统一配置管理:SpringCloudConfig、Nacos
- 统一网关路由:SpringCloudGateway、Zuul
- 流控、降级、保护:Hystix、Sentinel
2 服务拆分
2.1 拆分方式
适合拆分的两种情况:
- 创业型项目:先采用单体架构,快速开发,快速试错。随着规模扩大,逐渐拆分。
- 确定的大型项目:资金充足,目标明确,可以直接选择微服务架构,避免后续拆分的麻烦。
拆分的目标:
- 高内聚:每个微服务的职责要尽量单一,包含的业务相互关联度高、完整度高。
- 低耦合:每个微服务的功能要相对独立,尽量减少对其它微服务的依赖。
两种拆分方式:
- 纵向拆分:按照业务模块来拆分
- 横向拆分:抽取公共服务,提高复用性
两种工程结构:
- 独立Project
- Maven聚合
2.2 远程调用
Spring提供了RestTemplate工具,可以方便地实现Http请求的发送。使用步骤如下:
- 注入RestTemplate到Spring容器
- 发起远程调用
3 服务治理
3.1 注册中心原理
服务治理中的三个角色:
- 服务提供者:暴露服务接口,供其它服务调用
- 服务消费者:调用其它服务提供的接口
- 注册中心:记录并监控微服务各实例状态,推送服务变更信息
服务提供者会在启动时注册自己信息到注册中心,消费者可以从注册中心订阅和拉取服务信息。
服务提供者通过心跳机制向注册中心报告自己的健康状态,当心跳异常时注册中心会将异常服务剔除,并通知订阅了该服务的消费者。
消费者可以通过负载均衡算法,从多个实例中选择一个。
3.2 Nacos
Nacos是目前国内企业中占比最多的注册中心组件,是阿里巴巴的产品,目前已经加入SpringcloudAlibaba中。
3.3 服务注册与服务发现
服务注册:
- 引入nacos discovery依赖
- 配置Nacos地址
服务发现:消费者需要连接nacos以拉取和订阅服务,因此服务发现的前两步与服务注册是一样,后面再加上服务调用即可:
- 引入nacos discovery依赖
- 配置nacos地址
- 服务发现
3.4 OpenFeign
OpenFeign(官方地址:github.com/OpenFeign/feign)是一个声明式的http客户端,是SpringCloud在Eureka公司开源的Feign基础上改造而来。作用:基于SpringMVC的常见注解,使得能够优雅的实现http请求的发送。
3.4.1 快速入门
OpenFeign已经被SpringCloud自动装配,实现起来非常简单:
- 引入依赖,包括OpenFeign和负载均衡组件SpringCloudLoadBalancer
- 通过
@EnableFeignClients
注解,启用OpenFeign功能 - 编写FeignClient
- 使用FeignClient,实现远程调用
3.4.2 连接池
OpenFeign对Http请求做了优雅的伪装,但其底层发起http请求依赖于其它的框架。这些框架可自行选择,包括
以下三种:
- HttpURLConnection:默认实现,不支持连接池
- Apache HttpClient:支持连接池
- OKHttp:支持连接池
具体源码可参考FeignBlockingLoadBalancerClient类中的delegate成员变量。
OpenFeign整合OKHttp的步骤如下:
- 引入依赖
- 开启连接池功能
3.4.3 最佳实践
当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用。有两种方式解决:
- 方式一:指定FeignClient所在包
- 方式二:指定FeignClient字节码
3.4.4 日志输出
OpenFeign仅在FeignClient所在包的日志级别为DEBUG时才会输出日志,其日志级别有4级:
- NONE: 不记录任何日志信息【默认】
- BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
- HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
- FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
由于Feign默认的日志级别为NONE,故默认看不到请求日志。
要自定义日志级别需要声明一个类型为Logger.Level的Bean,在其中定义日志级别。但此时这个Bean并未生效,要想配置某个FeignClient的日志,可以在@FeignClient注解中声明。如果想要全局配置,让所有FeignClient都按照这个日志配置,则需要在@EnableFeignClients
注解中声明。
4 网关
网关:网络的关口,负责请求的路由、转发、身份校验。
在SpringCloud中网关的实现包括两种:
- Spring Cloud Gateway
- Spring官方出品
- 基于WebFlux响应式编程
- 无需调优即可获得优异性能
- Netfilx Zuul
- Netflix出品
- 基于Servlet的阻塞式编程
- 需要调优才能获得与SpringCloudGateway类似的性能
4.1 网关路由
步骤:
- 创建新模块
- 引入网关依赖
- 编写启动类
- 配置路由规则
spring:
cloud:
gateway:
routes:
- id: item # 路由规则id,自定义,唯一
uri: lb://item-service # 路由目标微服务,lb表示负载均衡
predicates: # 路由断言,判断请求是否符合规则,符合则路由到目标
- Path=/item/**
- id: xx
uri: lb://xx-service
predicates:
- Path=/xx/**
网关路由对应的lava类型是RouteDefinition,其中常见的属性有:
id
:路由唯一标示uri
:路由目标地址predicates
:路由断言,判断请求是否符合当前路由。- Spring提供了12种基本的RoutePredicateFactory实现(详见Spring Cloud官网)
filters
:路由过滤器,对请求或响应做特处理。- 网关中提供了33种路由过滤器,每种过滤器都有独特的作用。
4.2 网关登录校验
网关请求处理流程:
- 路由映射器
- HandlerMapping的默认实现是RoutePredicateHandlerMapping
- HandlerMapping根据请求找到匹配的路由井存入上下文,然后把请求交给webHandler处理
- 请求处理器
- 默认实现是FilteringWebHandler,顾名思义是一个过滤器处理器。它会加载网关中配置的多个过滤器,放入集合并排序,形成过滤器链。然后依次执行这些过滤器。
- Netty路由过滤器
- 负责将请求转发到微服务,当微服务返回结果后存入上下文
- 过滤器
- 过滤器内部包含两部分逻辑——pre和post,分别会在请求路由到微服务之前和之后执行。
- 当所有Filter的pre逻辑都依次顺序执行通过后,请求才会被路由到微服务,否则会被拦截,后续过滤器不再执行。
- 微服务返回结果后,再倒序执行Filter的post逻辑
网关过滤器有两种,分别为:
- GatewayFilter:路由过滤器,作用于任意指定的路由;默认不生效,要配置到路由后生效。
- GlobalFilter:全局过滤器,作用范围是所有路由;声明后自动生效。
自定义GlobalFilter比较简单,直接实现GlobalFilter接口即可:
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1. 获取请求
ServerHttpRequest request = exchange.getRequest();
// 2. 过滤器业务处理
HttpHeaders headers = request.getHeaders();
System.out.println("headers = " + headers);
// 3. 放行
return chain.filter(exchange);
}
@Override
public int getOrder() {
// 过滤器执行顺序:值越小,优先级越高
return 0;
}
}
- filter方法中的两个参数:
ServerWebExchange exchange
:请求上下文,包含整个过滤器链内共享数据,例如request、response等。GatewayFilterChain chain
:过滤器链。当前过滤器执行完后,调用过滤器链中的下一个过滤器。