资料:
微服务、MQ资料
链接: https://pan.baidu.com/s/1nzCJ-hNw854uFJQf6jWobg 提取码: yyds
单体架构
微服务
拆分与改进
将单体项目 拆分成微服务项目
1、拆分原则
2、 拆分服务
3、远程调用
RestTemplate
// 2.1. 利用RestTemplate发起http请求,得到http的响应
ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
"http://localhost:8081/items?ids={ids}",
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<ItemDTO>>() {},
Map.of("ids", CollUtil.join(itemIds, ","))
);
// 2.2. 解析响应
if (!response.getStatusCode().is2xxSuccessful()) {
// 查询失败,直接结束
return;
}
List<ItemDTO> items = response.getBody();
if (CollUtils.isEmpty(items)) {
return;
}
// 处理items数据...
4、服务 远程调用存在的 问题
pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.heima</groupId>
<artifactId>hmall</artifactId>
<packaging>pom</packaging>
<version>1.0.0</version>
<modules>
<module>hm-common</module>
<module>hm-service</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.12</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<org.projectlombok.version>1.18.20</org.projectlombok.version>
<spring-cloud.version>2021.0.3</spring-cloud.version>
<spring-cloud-alibaba.version>2021.0.4.0</spring-cloud-alibaba.version>
<mybatis-plus.version>3.4.3</mybatis-plus.version>
<hutool.version>5.8.11</hutool.version>
<mysql.version>8.0.23</mysql.version>
</properties>
<!-- 对依赖包进行管理 只是版本控制-->
<dependencyManagement>
<dependencies>
<!--spring cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud alibaba-->
<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>
<!-- 数据库驱动包管理 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- mybatis plus 管理 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!--hutool工具包-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- lombok 管理 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>11</source> <!-- depending on your project -->
<target>11</target> <!-- depending on your project -->
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
注册中心
原理
服务注册、发现、负载均衡
1、服务注册
<!-- nacos 服务注册发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
spring:
application:
name: item-service # 服务名称
cloud:
nacos:
server-addr: 192.168.150.101:8848 # nacos地址
2、服务发现
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private RestTemplate restTemplate;
/**
* 根据商品ID列表查询商品信息
*/
public List<ItemDTO> fetchItemsByIds(List<Long> itemIds) {
// 2.1.根据服务名称获取服务的实例列表
List<ServiceInstance> instances = discoveryClient.getInstances ("item-service");
if (CollUtil.isEmpty (instances)) {
return null; // 修改: 返回null或抛出异常,根据业务需求调整
}
// 2.2.手写负载均衡,从实例列表中挑选一个实例
ServiceInstance instance = instances.get (RandomUtil.randomInt(instances.size ()));
// 2.3.利用RestTemplate发起http请求,得到http的响应
ResponseEntity<List<ItemDTO>> response = restTemplate.exchange (
instance.getUri () + "/items?ids={ids}",
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<ItemDTO>> () {
},
Map.of ("ids", CollUtil.join (itemIds, ",")));
return response.getBody ();
}
OpenFeign 远程调用
声明式 HTTP客户端------>>> 发送HTTP请求
- 引入依赖
<!-- nacos 服务注册发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 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>
<!--httpClient的依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
- 启动类加注解 @EnableFeignClients,启用OpenFeign功能
@EnableFeignClients
@SpringBootApplication
public class CartApplication {
// ... 略
}
- yaml文件
# 配置nacos地址,找到提供者地址
spring:
application:
name: item-consumer # 服务名称
cloud:
nacos:
server-addr: 192.168.150.101:8848 # nacos地址
feign:
client:
config:
default: # default全局的配置
loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息
httpclient:
enabled: true # 开启 feign对HttpClient的支持
max-connections: 200 # 最大的连接数
max-connections-per-route: 50 # 每个路径的最大连接数
- 在消费者 编写代码
最佳实践
** 耦合度较高 将所有 feign-api 放在一起维护**
修改注解
@EnableFeignClients(basePackages = “com.example.feignapi”)—>不然 找不到会报错
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients(basePackages = "com.example.feignapi")
public class EurekaClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientApplication.class, args);
}
}
解决 Feign 远程调用包扫描 问题
报错:
解决
方式一:
指定Feign应该扫描的包:
@EnableFeignClients(basePackages = "com.example.feignapi")
方式二:
指定需要加载的Client接口:
@EnableFeignClients(clients = {UserClient.class})
网关
1、 快速入门
快速入门
<dependencies>
<!--common-->
<dependency>
<groupId>com.heima</groupId>
<artifactId>hm-common</artifactId>
<version>1.0.0</version>
</dependency>
<!--网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos discovery-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--负载均衡-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
路由配置
server:
port: 8080
spring:
application:
name: gateway
cloud:
nacos:
server-addr: 192.168.150.101:8848
gateway:
routes:
- id: item # 路由规则id,自定义,唯一
uri: lb://item-service # 路由的目标服务,lb代表负载均衡,会从注册中心拉取服务列表
predicates: # 路由断言,判断当前请求是否符合当前规则,符合则路由到目标服务
- Path=/items/**,/search/** # 这里是以请求路径作为判断规则
- id: cart
uri: lb://cart-service
predicates:
- Path=/carts/**
- id: user
uri: lb://user-service
predicates:
- Path=/users/**,/addresses/**
- id: trade
uri: lb://trade-service
predicates:
- Path=/orders/**
- id: pay
uri: lb://pay-service
predicates:
- Path=/pay-orders/**
2、路由属性
3、网关请求 处理流程
网关请求 处理流程
网关登录校验
1、 用户信息 保存到请求头
hm:
jwt:
location: classpath:hmall.jks # 秘钥地址
alias: hmall # 秘钥别名
password: hmall123 # 秘钥文件密码
tokenTTL: 30m # 登录有效期
auth:
excludePaths: # 无需登录校验的路径
- /search/**
- /users/login
- /items/**
package com.hmall.gateway.filter;
import com.hmall.common.exception.UnauthorizedException;
import com.hmall.common.utils.CollUtils;
import com.hmall.gateway.config.AuthProperties;
import com.hmall.gateway.util.JwtTool;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.List;
@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;
}
}
重启测试,会发现 访问/items开头的路径,未登录状态下不会被拦截
:
访问其他路径则,未登录状态下请求会被拦截
,并且返回401状态码
:
2、拦截器获取用户
默认 扫不到,需要配置
在hm-common中已经有一个用于保存登录用户的ThreadLocal工具:
拦截器
package com.hmall.common.interceptor;
import cn.hutool.core.util.StrUtil;
import com.hmall.common.utils.UserContext;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class UserInfoInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1.获取请求头中的用户信息
String userInfo = request.getHeader("user-info");
// 2.判断是否为空
if (StrUtil.isNotBlank(userInfo)) {
// 不为空,保存到ThreadLocal
UserContext.setUser(Long.valueOf(userInfo));
}
// 3.放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 移除用户,防止内存泄露,一次请求结束后 释放内存
UserContext.removeUser();
}
}
配置 拦截器
package com.hmall.common.config;
import com.hmall.common.interceptors.UserInfoInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@ConditionalOnClass(DispatcherServlet.class) // 网关 不需要拦截,微服务 是基于SpringMVC的
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new UserInfoInterceptor());
}
}
不过,需要注意的是,这个 配置类默认是不会生效的,因为它所在的包是com.hmall.common.config,与其它微服务的扫描包不一致,无法被扫描到,因此无法生效。
基于SpringBoot的自动装配原理,我们要将其添加到resources目录下的 META-INF/spring.factories
文件中:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.hmall.common.config.MyBatisConfig,\
com.hmall.common.config.MvcConfig
3、 恢复 购物车代码
4、微服务之间传递 用户信息
下单的过程中,需要调用商品服务 扣减库存,调用购物车服务 清理用户购物车。而清理购物车时必须知道当前登录的用户身份。
但是, 订单服务调用购物车时 并没有传递用户信息,购物车服务无法知道当前用户是谁!
因此 要想实现微服务之间的 用户信息传递,就必须在微服务发起调用时把用户信息存入请求头。
// 所有由 OpenFeign发起的请求都会 先调用拦截器处理请求:
public interface RequestInterceptor {
/**
* Called for every request.
* Add data using methods on the supplied {@link RequestTemplate}.
*/
void apply(RequestTemplate template);
}
@Configuration
public class DefaultFeignConfig {
@Bean
public RequestInterceptor userInfoRequestInterceptor() {
return new RequestInterceptor () {
@Override
public void apply(RequestTemplate template) {
// 获取登录用户
Long userId = UserContext.getUser ();
if (userId == null) {
// 如果为空则直接跳过
return;
}
// 如果不为空则放入请求头中,传递给下游微服务
template.header ("user-info", userId.toString ());
}
};
}
}
启动类 指明 feign的配置类
@SpringBootApplication
@EnableFeignClients(basePackages = "com.hmall.api.client", defaultConfiguration = DefaultFeignConfig.class)
public class MyServe {
}
网关 解决跨域问题
跨域问题
从 localhost:8090
访问 localhost:10010
, 端口不同,显然是跨域的请求。
解决
在gateway服务的application.yml 文件中,添加下面的配置:
spring:
cloud:
gateway:
# 。。。
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: # 允许哪些网站的跨域请求
- "http://localhost:8090"
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期
配置管理
1、共享配置
2、配置热更新
@Component
@ConfigurationProperties(prefix = "hm.cart")
@Data
public class CartProperties {
private Integer maxItems;
}
添加配置到Nacos
首先,我们在nacos中 添加一个配置文件,将 购物车的上限数量添加到配置中:
hm:
cart:
maxAmount: 1 # 购物车商品数量上限
提交配置,在 控制台能看到 新添加的配置:
微服务保护 (熔断、降级、限流)
1、 服务雪崩
2、限流 、熔断、降级
3、 服务保护技术
4、Sentinel 使用
https://blog.csdn.net/qq_30659573/article/details/127577889
分布式事务
1、Seata
https://blog.csdn.net/qq_30659573/article/details/127563258
2、本地消息表 + 定时任务扫表
3、MQ 分布式事务
4、最大努力通知