首先介绍一下单体架构与微服务架构:
单体架构:
微服务:
SpringCloud:
版本:
标黑部分为目前企业使用最多的版本,因为它支持jdk8、jdk11,下面使用SpringCloud也会使用这个版本。
服务拆分:
拆分原则:
什么时候拆分?
怎么拆分?
工程结构:
拆分后的工程结构有两种:
1.独立Project,将拆分后的所有服务放到一个文件夹中,适合大型项目(有很多微服务)。
2.Maven聚合,创建一个Project,然后在其下面创建Module为微服务模块。
服务拆分后,不同微服务之间可能有调用,比如购物车服务中会调用到商品服务,那么需要进行远程调用。
远程调用:
1.方法一:(不推荐)
就可以利用RestTemplate在购物车服务中向商品服务发送http请求并获取响应体。
问题:
为了减小服务压力,http请求接收的服务可能会部署多个,所以并不能确定向哪个服务发送http请求。
2.方法二:
Nacos注册中心:
原理:
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();@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//1.获取requestServerHttpRequest request = exchange.getRequest();//2.判断是否需要做登录拦截String path = request.getPath().toString();if (isExclude(path)) {return chain.filter(exchange);}//3.获取tokenString token = null;HttpHeaders headers = request.getHeaders();List<String> authorization = headers.get("Authorization");if (authorization != null && authorization.size() > 0) {token = authorization.get(0);}//4.校验并解析tokenLong 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;}@Overridepublic int getOrder() {return 0;}
}
antPathMather是一个路径字符匹配器API,它可以方便我们对路径进行匹配。
编写SpringMVC拦截器
编写拦截器以获取登录用户
步骤:
1.首先编写拦截器类:
需要让拦截器实现HandlerInterceptor接口,重写preHandle和afterCompletion方法,preHandle是在传给后续微服务前执行,所以在这个方法中将用户信息存入ThreadLocal。afterCompletion是在使用完后执行,所以删除存储的用户信息,防止内存泄漏。
public class UserInfoInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1.获取用户信息String userInfo = request.getHeader("user-info");//2.判断是否为空,不为空则存入ThreadLocalif(StrUtil.isNotBlank(userInfo)) {UserContext.setUser(Long.parseLong(userInfo));}//3.放行return true;}@Overridepublic 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 {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new UserInfoInterceptor());}
}
3.将配置类放入SpringBoot配置文件中:
将配置类全类名放入spring.factories配置文件中
实现微服务之间传递用户信息:
可以使用OpenFeign实现
在Feign的配置类中直接定义拦截器:
public class DefaultFeignConfig {@Beanpublic Logger.Level feignLoggerLevel() {return Logger.Level.FULL;}@Beanpublic RequestInterceptor userInfoRequestInterceptor() {return new RequestInterceptor() {@Overridepublic 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>
配置控制台:
springcloud:sentinel:transport:dashboard: localhost:8090http-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: devcloud: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)