一、
-
跨多个微服务
-
跨多个数据源
最本质的原因是,commit或者是rollback是基于connection的,如果存在多个connection,无法保证能同时commit或者rollback。
1.1、理论基础
CAP
-
C: 一致性
-
A: 可用性
-
P:分区容错性
BASE
-
BA:基本一致
-
S:软状态
-
E:最终一致
分阶段执行,2阶段
二、分布式事务解决方案Seata
-
XA
关系型数据库,需要数据库原生的支持
一致性最好,代码无侵入,但是效率是最差的
第一阶段
分支事务执行完以后,先不提交,相互等待所有的分支事务的执行结果
第二阶段
同时提交或者同时回滚
-
AT
代码无侵入,性能比XA好很多,不用加数据库的锁。
第一阶段
分支事务执行完以后,立即提交。
业务库中记录undo_log,补偿,记录before-image和after-image
全局锁,做事务隔离
第二阶段
删除undo_log、或者是补偿。
-
TCC
性能是最好的,支持非关系型数据库
代码侵入性很强,需要编写3个业务方法:try-confirm-cancel
空回滚、业务悬挂(1.5以上框架已经支持)
2.1、Seata服务端配置
registry.conf:
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
nacos {
application = "seata-tc-server" # seata服务的名字
serverAddr = "127.0.0.1:8848" # nacos注册中心地址
group = "DEFAULT_GROUP" # seata在nacos中的group
namespace = ""
cluster = "BJ" #seata的集群名称
username = "nacos"
password = "nacos"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "nacos"
nacos {
serverAddr = "127.0.0.1:8848"
namespace = ""
group = "SEATA_GROUP"
username = ""
password = ""
dataId = "seataServer.properties"
}
}
2.2、微服务客户端的配置
seata:
registry:
type: nacos
nacos:
server-addr: 192.168.137.136:8848 # nacos地址
namespace: ""
group: SEATA_GROUP # 分组,与seata服务端的配置的名称一致
application: seata-tc-server # seata服务名称,与seata服务端的配置的名称一致
username: nacos
password: nacos
#cluster: BJ
tx-service-group: seata-lead # 事务组名称
service:
vgroup-mapping: # 事务组与cluster的映射关系
seata-lead: BJ
【注意】微服务端的配置要与seata服务端的配置保持一致,微服务才可以连接上seata server。
三、微服务集成Seata的AT模式
)==所有参与分布式事务==的微服务添加seata的依赖:
<!--seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.5.0</version>
</dependency>
2)==所有参与分布式事务==的微服务添加seata的配置
seata:
registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
type: nacos # 注册中心类型 nacos
nacos:
server-addr: 192.168.137.136:8848 # nacos地址
namespace: "" # namespace,默认为空
group: SEATA_GROUP # 分组,默认是DEFAULT_GROUP
application: seata-tc-server # seata服务名称
username: nacos
password: nacos
cluster: BJ
tx-service-group: seata-lead # 事务组名称
service:
vgroup-mapping: # 事务组与cluster的映射关系
seata-lead: BJ
data-source-proxy-mode: AT
3)==所有参与分布式事务==的业务库中添加undo_log表
4)在全局事务开始的方法上添加@GlobalTransactional
四、用户信息存放到ThreadLocal
目的是为了方便将来使用。
1)写一个拦截器,从header中读取userId并存储到ThreadLocal
@Component
public class UserIdInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 拿request的header
String userId = request.getHeader("userId");// 抽常量
if(StringUtils.isEmpty(userId)){
log.error("请求中无法获取userId的header, url:{}", request.getRequestURI());
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
// header中的userId存入ThreadLocal
UserThreadLocalUtils.setUserId(Integer.valueOf(userId));
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 因为如果controller方法执行过程中除了异常,不会进这个方法
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 清理ThreadLocal
UserThreadLocalUtils.removeUser();
}
}
2)注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private UserIdInterceptor userIdInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(userIdInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/login/in")
.excludePathPatterns("/api/v1/wmuser/save");
}
五、完整的登录和身份认证过程
1)前端用户传递的用户名和密码,服务端收到请求,根据用户名到db查询记录,根据db中记录的password与前端传递的password做匹配,如果能匹配成功,则生成==jwt token==返回给前端,token中携带了userId。
2)前端会把token保存到==local storage==中,在随后的访问中,在header中携带上这个token。
3)前端的请求首先是到网关,网关中有一个==全局的过滤器==,会从请求中读取header中的token,解析,把解析出来的userId放入http的header中继续向下游的微服务传递,header的key=userId。
4)微服务中有一个==拦截器==,在拦截器中拦截请求,从header中解析出userId,然后保存到ThreadLocal中。
5)在微服务的接口中,就可以使用==ThreadLocal==来获取用户信息。
六、对象云存储Minio
1)key-value
key: aa/bb/cc/dd.jpg value:图片
2)相同的key会覆盖
七、如何自定义一个starter?请问你的项目中有没有自定义过starter?
1)引入starter所需要的依赖
2)定义starter需要的配置
3)定义实现业务逻辑的api实现类
4)在META-INF/spring.factories中添加自动配置,把业务逻辑的实现类注入到spring容器
八、素材管理
1)加依赖
<dependency>
<groupId>com.heima</groupId>
<artifactId>heima-leadnews-file-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
2)加配置
minio:
accessKey: minio
secretKey: minio123
bucket: leadnews
endpoint: http://192.168.137.136:9000
readPath: http://192.168.137.136:9000
【注意】
-
列表查询,查询条件中隐含了userId
-
素材上传,文件名需要重命名,防止同名文件覆盖。