
一.Nacos(管理服务)
1.服务注册
①引入 nacos discovery 依赖
bash
<!--nacos 服务注册发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
②配置Nacos地址
bash
spring:
application:
name: item-service # 服务名称
cloud:
nacos:
server-addr: 192.168.150.101:8848 # nacos地址
2.服务发现
①引入 nacos discovery 依赖和配置Nacos地址
这两步和上面注册一摸一样,这里略.....
③服务发现(后面有OpenFeign,这个了解一下)
java
private final DiscoveryClient discoveryClient;
private void handleCartItems(List<CartVO> vos) {
// 1.根据服务名称,拉取服务的实例列表
List<ServiceInstance> instances = discoveryClient.getInstances("item-service");
// 2.负载均衡,挑选一个实例
ServiceInstance instance = instances.get(RandomUtil.randomInt(instances.size()));
// 3.获取实例的IP和端口
URI uri = instance.getUri();
// ... 略
}
二.OpenFeign(服务通信)
OpenFeign是一个声明式的http客户端
1.快速入门
①引入依赖
bash
<!--openFeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--负载均衡器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
②通过@EnableFeignClients注解,启动OpenFeign功能
java
@EnableFeignClients
@SpringBootApplication
public class CartApplication { // ... 略 }
③编写FeignClient客户端
java
// 这个接口不用实现,OpenFeign会动态代理帮我们实现
@FeignClient("item-service") //这个是从nacos的item-service服务中获取实例列表,然后通过负载均衡算法获取一个实例
public interface ItemClient {
@GetMapping("/items") //告诉OpenFeign去服务实例中调用这个接口,这个接口是get请求,路径是/items
//返回值默认是json格式,转换为java对象 请求参数是ids 请求数据
List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);
}
@FeignClient("item-service")
:声明服务名称
@GetMapping
:声明请求方式
@GetMapping("/items")
:声明请求路径
@RequestParam("ids") Collection<Long> ids
:声明请求参数
List<ItemDTO>
:返回值类型
④使用FeignClient,实现远程调用
java
// 要先依赖注入 ItemClient这个接口,然后在调用
List<ItemDTO> items = itemClient.queryItemByIds(List.of(1,2,3));
2.连接池
前面讲了底层原理,动态代理听不懂。
每次发送请求的时候都是创建一个连接,效率比较低下,所以创建一个连接池效率高一些。
OpenFeign整合OKHttp开始连接池:
①引入依赖
bash
<!--ok-http-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
②开启连接池功能
bash
feign:
okhttp:
enabled: true # 开启OKHttp连接池支持
3.最佳实践
两种最佳实践的项目格式:
①每个微服务分为三个模块

好处:当前微服务写好自己给别人调用的接口,别人引入依赖直接使用即可。
弊端:项目结构变复杂了
②储存在一个公共的模块下

弊端:代码耦合度增加了。
4. 日志输出
OpenFeign 只会在 FeignClient 所在包的日志级别为 DEBUG 时,才会输出日志。而且其日志级别有 4 级:
- NONE:不记录任何日志信息,这是默认值,所以默认我们看不到日志。
- BASIC:仅记录请求的方法,URL 以及响应状态码和执行时间
- HEADERS:在 BASIC 的基础上,额外记录了请求和响应的头信息
- FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
使用:
①自定义日志级别需要声明一个类型为Logger.Level的Bean,在其中定义日志级别
java
public class DefaultFeignConfig {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.FULL;
}
}
②配置全局,让所有FeignClient 都按照这个日志配置,要在@EnableFeignClient注解中声明:
java
@EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)
一般情况下不用配置,调试的时候在配置。

这两个红色框的没看懂
三.Gateway(路由)
作用:请求的路由,转发,身份校验
Ⅰ.网关路由
1. 快速入门
①创建新模块
②引入网关依赖
bash
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
③编写启动类
④配置路由规则(重要)

2.路由属性
四个属性含义如下:
id
:路由的唯一标示
predicates
:路由断言,其实就是匹配条件
filters
:路由过滤条件,后面讲
uri
:路由目标地址,lb://
代表负载均衡,从注册中心获取目标微服务的实例列表,并且负载均衡选择一个访问。
①路由断言 predicates:
名称 | 说明 | 示例 |
---|---|---|
After | 是某个时间点后的请求 | - After=2037-01-20T17:42:47.789-07:00[America/Denver] |
Before | 是某个时间点之前的请求 | - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] |
Between | 是某两个时间点之前的请求 | - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver] |
Cookie | 请求必须包含某些cookie | - Cookie=chocolate, ch.p |
Header | 请求必须包含某些header | - Header=X-Request-Id, \d+ |
Host | 请求必须是访问某个host(域名) | - Host=**.somehost.org,**.anotherhost.org |
Method | 请求方式必须是指定方式 | - Method=GET,POST |
Path | 请求路径必须符合指定规则 | - Path=/red/{segment},/blue/** |
Query | 请求参数必须包含指定参数 | - Query=name, Jack或者- Query=name |
RemoteAddr | 请求者的ip必须是指定范围 | - RemoteAddr=192.168.1.1/24 |
weight | 权重处理 |
②路由过滤器 filters:
名称 | 说明 | 示例 |
---|---|---|
AddRequestHeader | 给当前请求添加一个请求头 | AddrequestHeader=headerName,headerValue |
RemoveRequestHeader | 移除请求中的一个请求头 | RemoveRequestHeader=headerName |
AddResponseHeader | 给响应结果中添加一个响应头 | AddResponseHeader=headerName,headerValue |
RemoveResponseHeader | 从响应结果中移除有一个响应头 | RemoveResponseHeader=headerName |
RewritePath | 请求路径重写 | RewritePath=/red/(?<segment>.*), /${segment} |
StripPrefix | 去除请求路径中的 N 段前缀 | StripPrefix=1,则路径 /a/b 转发时只保留 /b |
...总共33种 | ... | ... |

Ⅱ.网关登录校验
1.自定义拦截器
①GlobalFilter(常用这个)

java
@Component
public class MyGlobalFilter implements GlobalFilter , Ordered {
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 登录校验逻辑
ServerHttpRequest request = exchange.getRequest();
HttpHeaders headers = request.getHeaders();
// 放行
return chain.filter(exchange);
}
public int getOrder() { // 过滤的顺序,数字越大越靠后
return 0;
}
}
需要实现GlobalFilter和Ordered接口
ServerWebExchange:网关内部的上下文对象,保存网关共享对象如:request,respone,session,或者一些自定的共享属性。
GatewayFilterChain:当前过滤器执行完后,要调用过滤器链中的下一个过滤器。
Ordered接口作用:是我们自定义的过滤器要在 NettyRoutingFilter 的前面执行,因为NettyRoutingFilter 是用于路由转发到微服务的。
②GatewayFilter(这个写的麻烦一点)
相比GlobalFilter可以指定作用范围 ,还可以配置自定义参数如下
无参数:图片是代码模板

代码实现
java
@Component
public class PrintAnyGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {
@Override
public GatewayFilter apply(Object config) {
return new OrderedGatewayFilter(new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 登录校验逻辑
System.out.println("print any filter running");
// 放行
return chain.filter(exchange);
}
},1);
}
}
java
spring:
cloud:
gateway:
default-filters:
- PrintAny
该类的名称一定要以
GatewayFilterFactory
为后缀!
有参数:这里只给代码模板,就不给具体代码实现了,因为太多了。

2.实现登录校验
java
@Component
@RequiredArgsConstructor
@EnableConfigurationProperties(AuthProperties.class)
public class AuthGlobalFilter implements GlobalFilter, Ordered {
private final JwtTool jwtTool;
private final AuthProperties authProperties;
private final AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1.获取Request
ServerHttpRequest request = exchange.getRequest();
// 2.判断是否不需要拦截,
if(isExclude(request.getPath().toString())){
// 无需拦截,直接放行
return chain.filter(exchange);
}
// 3.获取请求头中的token
String token = null;
List<String> headers = request.getHeaders().get("authorization");
if (!CollUtils.isEmpty(headers)) {
token = headers.get(0);
}
// 4.校验并解析token
Long userId = null;
try {
userId = jwtTool.parseToken(token);
} catch (UnauthorizedException e) {
// 如果无效,拦截
ServerHttpResponse response = exchange.getResponse();
response.setRawStatusCode(401);
return response.setComplete(); // 拦截
}
// TODO 5.如果有效,传递用户信息
System.out.println("userId = " + userId);
// 6.放行
return chain.filter(exchange);
}
private boolean isExclude(String antPath) {
for (String pathPattern : authProperties.getExcludePaths()) {
if(antPathMatcher.match(pathPattern, antPath)){
return true; // 如果这个地址在排除拦击的路径里面,直接放即可
}
}
return false;
}
@Override
public int getOrder() {
return 0;
}
}
3.网关传递用户
实现思路:

网关过滤器储存用户信息到请求头:
java
// 5.如果有效,传递用户信息
String userInfo = userId.toString();
ServerWebExchange swe = exchange.mutate()
.request(builder -> builder.header("user-info", userInfo))
.build();
拦截器获取请求头,并储存到ThreadLocal中:
java
public class UserInfoInterceptor implements HandlerInterceptor {
public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) throws Exception {
String userInfo = request.getHeader("user-info");
if(StrUtil.isNotBlank(userInfo)) {
UserContext.setUser(Long.valueOf(userInfo));
}
return true;
}
public void afterCompletion(final HttpServletRequest request, final HttpServletResponse response, final Object handler, final Exception ex) throws Exception {
UserContext.removeUser();
}
}
然后接下来就是注册拦截器,但是注册完之后还是存在问题,因为代码添加到了common这个模块中,网关也 也引入了common的依赖,但是gateway不是有SpringMVC实现的,所以不能运行拦截器的代码,我们需要条件装配,解决方案:在添加注册拦截器的类上添加
@ConditionalOnClass(DispatcherServlet.class) 注解即可
4.OpenFeign传递用户
使用OpenFegin拦截器。
java
public class DefaultFeignConfig {
@Bean
public RequestInterceptor userInfoRequestInterceptor() {
return new RequestInterceptor() {
public void apply(RequestTemplate template) {
Long userId = UserContext.getUser();
if (userId != null) {
template.header("user-info", userId.toString());
}
}
};
}
}
DefaultFeignConfig 这个配置类要在启动类上声明:
@EnableFeignClients(basePackages = "com.hmall.api.client",defaultConfiguration = DefaultFeignConfig.class)
basePackages
参数:
- 作用:指定扫描哪些包下的 Feign 客户端接口。Spring 会扫描
basePackages
指定包及其子包中的所有被@FeignClient
注解标注的接口,然后创建这些接口的代理实例,以便在其他组件中可以通过依赖注入的方式使用这些 Feign 客户端来调用远程服务。
defaultConfiguration
参数:
- 作用:指定 Feign 客户端的默认配置类。这个配置类可以用来配置 Feign 客户端的一些通用属性,例如日志级别、解码器、编码器、负载均衡策略等。通过设置
defaultConfiguration
,可以为所有 Feign 客户端应用相同的配置。
整体代码思路流程图:

Ⅲ.配置管理(Nacos)
1.共享配置(更好的管理配置文件)
在nacos里面配置 配置文件,这个配置文件是提供给每个微服务的。
在springcloud环境下配置文件加载流程:
使用步骤:
①引入依赖
java
<!--nacos配置管理-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--读取bootstrap文件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
② 新建bootstrap.yaml
然后写nacos的配置,根据上面的流程图,我们必须写nacos的配置不写就无法找到nacos。
2.配置热更新
微服务无需重启即可使配置生效。
3.动态路由
四.Sentinel(微服务保护)
1.微服务整合Sentinel
①引入依赖
bash
<!--sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
②配置控制台
bash
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8090
http-method-specify: true # 是否设置请求方式作为资源名称
2.请求限流

3.线程隔离

4.FallBack
触发限流或熔断后的请求不一定要直接报错,也可以返回一些默认数据或者友好提示,用户体验会更好。
①将FeignClient作为Sentinel的簇点资源
bash
feign:
okhttp:
enabled: true # 开启OKHttp功能
sentinel:
enabled: true # 开启sentinel对OpenFeign的监控
②自定义类,实现FallbackFactory,编写对某个FeignClient的fallback逻辑
java
// 对不同的方法写失败后的逻辑
@Slf4j
public class ItemClientFallbackFactory implements FallbackFactory<ItemClient> {
@Override
public ItemClient create(Throwable cause) {
return new ItemClient() {
@Override
public List<ItemDTO> queryItemByIds(Collection<Long> ids) {
log.error("远程调用ItemClient#queryItemByIds方法出现异常,参数:{}", ids, cause);
// 查询购物车允许失败,查询失败,返回空集合
return CollUtils.emptyList();
}
@Override
public void deductStock(List<OrderDetailDTO> items) {
// 库存扣减业务需要触发事务回滚,查询失败,抛出异常
throw new RuntimeException(cause);
}
};
}
}
③将刚刚定义的UserClientFallbackFactory注册为一个Bean
java
public class DefaultFeignConfig {
@Bean
public ItemClientFallbackFactory itemClientFallbackFactory() {
return new ItemClientFallbackFactory();
}
}
④在UserClient接口中使用UserClientFallbackFactory
java
// 添加到FeignClient 注解上
@FeignClient(value = "item-service",fallbackFactory = ItemClientFallbackFactory.class)
public interface ItemClient {
@GetMapping("/items")
List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);
@PutMapping("/items/stock/deduct")
void deductStock(@RequestBody List<OrderDetailDTO> items);
}
value
参数:
- 作用:指定要调用的远程服务的名称。这个名称通常对应于服务注册中心(如 Eureka、Consul、Nacos 等)中注册的服务名。Feign 会根据这个服务名,从服务注册中心获取服务的实例信息,并通过负载均衡选择一个实例进行调用。
fallbackFactory
参数:
- 作用:指定一个降级工厂类,用于在远程服务调用失败时提供降级逻辑。降级工厂类需要实现
FallbackFactory<T>
接口,其中T
是@FeignClient
注解标注的接口类型。通过降级工厂,可以为不同的异常情况提供不同的降级处理逻辑。
5.服务熔断

三个状态
closed:关闭状态,断路器放行所有请求,并开始统计异常比例、慢请求比例。超过阈值则切换到open状态
open :打开状态,服务调用被熔断,访问被熔断服务的请求会被拒绝,快速失败,直接走降级逻辑。Open状态持续一段时间后会进入half-open状态
half-open:半开状态,放行一次请求,根据执行结果来判断接下来的操作。
请求成功:则切换到closed状态
请求失败:则切换到open状态

五.Seata(分布式事务)
在Seata的事务管理中有三个重要的角色:
TC ( Transaction Coordinator ) - **事务协调者:**维护全局和分支事务的状态,协调全局事务提交或回滚。
TM (Transaction Manager) - **事务管理器:**定义全局事务的范围、开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - **资源管理器:**管理分支事务,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
1.微服务整合Seata
引入依赖
bash
<!--统一配置管理-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--读取bootstrap文件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!--seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
添加配置文件
bash
seata:
registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
type: nacos # 注册中心类型 nacos
nacos:
server-addr: 192.168.150.101:8848 # nacos地址
namespace: "" # namespace,默认为空
group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUP
application: seata-server # seata服务名称
username: nacos
password: nacos
tx-service-group: hmall # 事务组名称
service:
vgroup-mapping: # 事务组与tc集群的映射关系
hmall: "default"
2.XA模式
①工作原理图:
RM
一阶段的工作:
注册分支事务到
TC
执行分支业务sql但不提交
报告执行状态到
TC
TC
二阶段的工作:
TC
检测各分支事务执行状态
如果都成功,通知所有RM提交事务
如果有失败,通知所有RM回滚事务
RM
二阶段的工作:
- 接收
TC
指令,提交或回滚事务
②优缺点:
XA
模式的优点是什么?
事务的强一致性,满足ACID原则
常用数据库都支持,实现简单,并且没有代码侵入
XA
模式的缺点是什么?
因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
依赖关系型数据库实现事务
③使用
修改 application.yml 文件(每个参与事务的微服务),开启 XA 模式:
bashseata: data-source-proxy-mode: XA
给发起全局事务的入口方法添加 @GlobalTransactional 注解:
java@GlobalTransactional public Long createOrder(OrderFormDTO order) { // 创建订单 ... 略 // 清理购物车 ...略 // 扣减库存 ...略 return order.getId(); }
3.AT模式
①工作原理图:
阶段一
RM
的工作:
注册分支事务
记录undo-log(数据快照)
执行业务sql并提交
报告事务状态
阶段二提交时
RM
的工作:
- 删除undo-log即可
阶段二回滚时
RM
的工作:
- 根据undo-log恢复数据到更新前
②优缺点:
优:性能比XA模式高。
缺:不是强一致性,中间会有短暂的不一致,称为最终一致。
③使用
1.给每个微服务创建一个 undo_log 表sql语句如下
sqlCREATE TABLE IF NOT EXISTS `undo_log` ( `branch_id` BIGINT NOT NULL COMMENT '分支事务id', `xid` VARCHAR(128) NOT NULL COMMENT '全局事务id', `context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization', `rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info', `log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status', `log_created` DATETIME(6) NOT NULL COMMENT 'create datetime', `log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime', UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`) ) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
2.改配置文件
bashseata: data-source-proxy-mode: AT
3.使用和上面一样的分布式注解 @GlobalTransactional