首先介绍一下单体架构与微服务架构:
单体架构:
微服务:
SpringCloud:
版本:
标黑部分为目前企业使用最多的版本,因为它支持jdk8、jdk11,下面使用SpringCloud也会使用这个版本。
服务拆分:
拆分原则:
什么时候拆分?
怎么拆分?
工程结构:
拆分后的工程结构有两种:
1.独立Project,将拆分后的所有服务放到一个文件夹中,适合大型项目(有很多微服务)。
2.Maven聚合,创建一个Project,然后在其下面创建Module为微服务模块。
服务拆分后,不同微服务之间可能有调用,比如购物车服务中会调用到商品服务,那么需要进行远程调用。
远程调用:
1.方法一:(不推荐)
就可以利用RestTemplate在购物车服务中向商品服务发送http请求并获取响应体。
问题:
为了减小服务压力,http请求接收的服务可能会部署多个,所以并不能确定向哪个服务发送http请求。
2.方法二:
注册中心:
原理:
Nacos注册中心:
Nacos是目前国内企业中占比最多的注册中心组件。它是阿里巴巴的产品,目前以及假如SpringCloudAlibaba中。
使用时需要首先进行数据库中nacos数据库创建,并且向数据库中导入信息,并启动nacos镜像。
服务注册:
服务发现:
3.OpenFeign(推荐):
使用步骤:
注意:因为OpenFeign底层发送http请求是通过Client发送的,而Client每一次发送都需要重新创建连接,所以效率很低,因为我们使用连接池优化
连接池:
连接池使用:
实践方案:
方案一:(较推荐)
特点:
代码结构更合理,耦合度非常低,但是项目结构变复杂。
方案二:
特点:
结构更简单,使用更方便,但代码耦合度更高一点。
定义的FeignClient不在扫描包范围时:
日志:
网关:
网关就是网络的关口,负责请求的路由、转发、身份校验。
SpringCloud中的网关的实现:
这里我们使用Spring Cloud Gateway。
快速入门
路由属性:
路由断言:
路由过滤器:
如果想要给所有服务都配置一种路由过滤器,可以在与routes同级的位置配置default-filters,然后输入要配置的路由过滤器。
网关请求处理流程:
因此,我们要在网关内进行登录校验,需要自定义pre的过滤器进行jwt校验。
自定义过滤器:
自定义GlobalFilter:(大多数情况)
参数:
步骤:
可以实现Ordered接口,就能够定义过滤器优先级。
自定义GatewayFilter
实现登录校验:
我们可以在网关中通过自定义过滤器实现登录校验
实现网关传递用户信息:
网关保存用户到请求头:
完整代码:
@Component
@RequiredArgsConstructor
public class AuthGlobalFilter implements GlobalFilter, Ordered {
private final AuthProperties authProperties;
private final JwtTool jwtTool;
private final AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1.获取request
ServerHttpRequest request = exchange.getRequest();
//2.判断是否需要做登录拦截
String path = request.getPath().toString();
if (isExclude(path)) {
return chain.filter(exchange);
}
//3.获取token
String token = null;
HttpHeaders headers = request.getHeaders();
List<String> authorization = headers.get("Authorization");
if (authorization != null && authorization.size() > 0) {
token = authorization.get(0);
}
//4.校验并解析token
Long userId = null;
try {
userId = jwtTool.parseToken(token);
} catch (UnauthorizedException e) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//5.传递用户信息
String userInfo = userId.toString();
ServerWebExchange swe = exchange.mutate()
.request(builder -> builder.header("user-info", userInfo))
.build();
//6.放行
return chain.filter(swe);
}
private boolean isExclude(String path) {
List<String> excludePaths = authProperties.getExcludePaths();
for (String pathPattern : excludePaths) {
if (antPathMatcher.match(pathPattern, path)) {
return true;
}
}
return false;
}
@Override
public int getOrder() {
return 0;
}
}
antPathMather是一个路径字符匹配器API,它可以方便我们对路径进行匹配。
编写SpringMVC拦截器
编写拦截器以获取登录用户
步骤:
1.首先编写拦截器类:
需要让拦截器实现HandlerInterceptor接口,重写preHandle和afterCompletion方法,preHandle是在传给后续微服务前执行,所以在这个方法中将用户信息存入ThreadLocal。afterCompletion是在使用完后执行,所以删除存储的用户信息,防止内存泄漏。
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.判断是否为空,不为空则存入ThreadLocal
if(StrUtil.isNotBlank(userInfo)) {
UserContext.setUser(Long.parseLong(userInfo));
}
//3.放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserContext.removeUser();
}
}
2.编写配置类
编写完拦截器后,需要在编写配置类使之生效
需要注意的有
·配置类注解@Configuration表名它是一个配置类
·注解@ConditionalOnClass,是为了让网关不接收这个拦截器,让其他微服务接收,注解实现让具有DispatcherServlet.class的微服务接收拦截器,这是SpringMVC特有的class对象,网关中没有配置SpringMVC所以网关就不会接收。
·因为是SpringMVC中的拦截器,配置类要继承WebMvcConfigurer接口,并实现addInterceptors方法,添加刚刚编写的拦截器。
@Configuration
@ConditionalOnClass(DispatcherServlet.class)
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new UserInfoInterceptor());
}
}
3.将配置类放入SpringBoot配置文件中:
将配置类全类名放入spring.factories配置文件中
实现微服务之间传递用户信息:
可以使用OpenFeign实现
在Feign的配置类中直接定义拦截器:
public class DefaultFeignConfig {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
@Bean
public RequestInterceptor userInfoRequestInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate requestTemplate) {
Long userId = UserContext.getUser();
if (userId != null) {
requestTemplate.header("user-info", userId.toString());
}
}
};
}
}
注意微服务启动类上要有注解:
@EnableFeignClients(basePackages = "com.hmall.api.client", defaultConfiguration = DefaultFeignConfig.class)
总结:
配置管理:
共享配置:
为什么要使用配置管理:
使用配置管理服务:
我们在服务远程调用时使用的注册中心Nacos,就有配置管理服务的功能
1.添加配置到Nacos:
添加微服务中共享的那部分配置即可。
注意可以使用变量,即${配置文件中的路径},在原yml配置文件中配置变量,即可正常读取。
2.拉取共享配置
①引入依赖
②新建bootstrap.yaml
在微服务中创建bootstrap.yaml配置文件,在bootstrap.yaml中配置的信息不需要再配置了。
配置热更新:
配置热更新:当修改配置文件中的配置时,微服务无需重启即可使配置生效。
前提条件:
步骤
1.在nacos中定义一个与微服务名有关的配置文件
在上述bootstrap.yaml中已经有了这部分信息
2.加载属性
一般采用这种方式:
完成后,在nacos中配置一旦变更,就会实时更新。
服务保护和分布式事务:
雪崩问题:
雪崩问题是服务保护方面经常碰到的一个问题,即:
微服务中调用链路中的某个服务故障,引起整个链路中的所有微服务都不可用,这就是雪崩
产生原因:
解决思路:
服务保护方案:
请求限流:
线程隔离:
服务熔断:
解决方案总结:
服务保护技术:
我们可以使用服务保护技术方便我们完成上述解决方案。
Sentinel:
Sentinel是阿里巴巴开源的一款微服务流量控制组件。官网地址:home | Sentinel (sentinelguard.io)
簇点链路介绍:
使用方法:
引入依赖:
<!--sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
配置控制台:
spring
cloud:
sentinel:
transport:
dashboard: localhost:8090
http-method-specify: true #是否设置请求方式作为资源名称
请求限流:
在簇点链路后面点击流控按钮,即可对其做限流配置:
点击后弹出下面的窗口:
阈值类型默认为QPS,即每秒请求的数量,在单机阈值处填写数据点击新增即可。
线程隔离:
线程隔离也是要点击流控按钮,在窗口中配置并发线程数
Fallback:
实现Fallback要对FeignClient操作,所以需要让它成为Sentinel的簇点资源。
编写步骤:
步骤一:
在编写的cilent包下新建fallback包再新建FallbackFactory类即可。
步骤二:
步骤三:
服务熔断:
服务熔断通过断路器实现。
断路器原理:
使用方法:
点击簇点链路的熔断按钮,弹出下面窗口,默认选取的是慢调用比例,最大RT(response time)即最大响应时间,超过整个响应时间的请求被归为慢调用,比例阈值就是当慢调用的请求的比例超过比例阈值时,就会进行熔断,最小请求数量就是要对这么多次的请求一起判断,统计时长就是统计的周期。
分布式事务:
分布式事务调用了其他服务,举例:
如果不解决分布式事务,当程序正常进行,但是到第三步扣减商品库存出现问题,比如库存不足报错,这时库存没有正常扣减,但是购物车已经被清除,没有保证原子性。
注意这种情况不能使用@Transactional注解,它只适用于单个服务。
解决思路:
各个子事务之间必须能感知到批次的事务状态,才能保证状态一致。
Seata:
它本身也是一个微服务。
架构:
使用步骤:
建表:
Seata支持多种存储模式,但考虑到持久化的需要,我们一般选择基于数据库存储。
准备配置文件:
将seata的配置文件放入服务器或虚拟机/root目录下
用docker部署:
需要注意,要确保nacos、mysql都在hm-net网络中。
微服务整合Seata:
首先需要引入依赖:
<!--统一配置管理-->
<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>
我将它定义在了nacos共享配置中。
最后,新建bootstrap.yaml文件,定义如下配置
spring:
application:
name: ****-service # 服务名称
profiles:
active: dev
cloud:
nacos:
server-addr: ***.***.***.*** # nacos地址
config:
file-extension: yaml # 文件后缀名
shared-configs: # 共享配置
- dataId: shared-seata.yaml # 共享seata配置
Seata模式:
XA模式:
优点:
缺点:
实现:
AT模式:
实现:
XA模式与AT模式的区别:
(即XA模式在整个过程中数据库中的信息都是一致的,而AT模式在一阶段提交完成后,有服务出现问题,在二阶段根据数据快照恢复数据前,会出现短暂的数据不一致情况)
后续学习:
RabbitMQ:
详见作者的下一篇文章:Java_RabbitMQ
Elasticsearch(ES):
详见作者的下下篇文章:Java_Elasticsearch(ES)