分布式事务

一、

  • 跨多个微服务

  • 跨多个数据源

    最本质的原因是,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

  • 素材上传,文件名需要重命名,防止同名文件覆盖。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值