逸仙电商Seata企业级落地实践

简介: 本文将会以逸仙电商的业务作为背景, 先介绍一下seata的原理, 并给大家进行线上演示, 由浅入深去介绍这款中间件, 以便读者更加容易去理解 Seata 这个中间件。

作者 | 张嘉伟(GitHub ID:l81893521)

就职于逸仙电商交易中心;Seata Committer,加入 Seata 社区已有一年半,见证了从 Fescar 到 Seata 的变更,GA等。


 

你可能没有听说过逸仙电商,但是你的女朋友不可能没有听说过它。逸仙电商旗下有完美日记、小奥汀、完子心选等品牌。完美日记作为国货美妆界的黑马用了不到三年时间,达到了行业龙头企业通常需要十年以上才能达到的营收规模。2020 年正式登陆纽约证券交易所,成为第一家在美国上市的“国货美妆品牌”。在快速增长的业务下,系统流量增长速度越来越快,服务数量不断增多,调用链路错综复杂,数据不一致的问题日渐显现,为了降低人力成本和系统资源,我们选择了 Seata。

 

本文将会以逸仙电商的业务作为背景, 先介绍一下seata的原理, 并给大家进行线上演示, 由浅入深去介绍这款中间件, 以便读者更加容易去理解 Seata 这个中间件。

 

image.png

 

1. 问题背景

 

在微服务的架构下,数据不一致的产生原因

 

2. 业务介绍

 

挑选了逸仙电商一些比较简单易懂的业务作为开展背景

 

3. 原理分析

 

Seata的实现原理和故障解决以及部署方案

 

4. Demo演示

 

如何在线体验这款中间件,无需整合和下载任何代码

 

数据不一致的原因

 

image.png

 

在微服务的环境下,由于调用链路跨越多个应用,甚至跨越多个数据源,数据的一致性在普通情况下难以保证,导致数据不一致的原因非常多,这里列举了三个最常见的原因

 

  1. 业务异常一个服务链路调用中,如果调用的过程出现业务异常,产生异常的应用独立回滚,非异常的应用数据已经持久化到数据库。
  2. 网络异常调用的过程中,由于网络不稳定,导致链路中断,部分应用业务执行完成,部分应用业务未被执行。
  3. 服务不可用若服务不可用,无法被正常调用,也会导致问题的产生

 

image.png

 

这里挑选了逸仙电商业务体系里面一个非常通俗容易理解的调用方式,并且去掉了多余复杂的链路,方便在阅读过程中更加关注重点。

 

在以往如果出现数据不一致的问题,相信大多数的解决方案是这样的

 

  • 人工补偿数据
  • 定时任务检查和补偿数据

 

但是这两种方式的缺点也是显然意见的,一种是浪费大量的人力成本和时间,另外一种是浪费大量的系统资源去检查数据是否一致和额外的人力成本。

 

接下来我会根据逸仙在生产上稳定运行将近一年总结的经验并且尽可能简单的去描述Seata是如何保证数据一致的。

 

原理

 

image.png

 

在接触一项新技术之前,我们应该先从宏观的角度去理解它大概包含些什么。在Seata中,它大概分为以下三个角色。

 

  • 黄色,Transaction Manager(TM),client端
  • 蓝色,Resource Manager(RM),client端
  • 绿色,Transaction Coordinator(TC),server端

 

你可以根据颜色,名字,缩写甚至客户端/服务端去区分这三者的关系,同时简单去理解它们每一个自身的职责大概是要干些什么事情,后面的讲解我也会保持一样的颜色和名字来区分它们。

 

image.png

 

Seata其中只一个核心是数据源代理,意味着在你执行一句Sql语句时,Seata会帮你在执行之前和之后做一些额外的操作,从而保证数据的一致性,并且尽可能做到无感知,让你使用起来感觉非常方便和神奇。这里首先要去理解两个知识点。

 

  • 前置镜像(Before Image):保存数据变更前的样子
  • 后置镜像(After Image):保存数据变更后的样子
  • Undo Log:保存镜像

 

有时候新项目接入的时候,有同事会问,为什么事务不生效,如果你也遇到过同样的问题,那首先要检查一下自己的数据源是否已经代理成功。

 

当执行一句Sql时,Seata会尝试去获取这条/批数据变更前的内容,并保存到前置镜像中(Insert语句没有前置镜像),然后执行业务Sql,执行完后会尝试去获取这条/批数据变更后的内容,并保存到后置镜像中(Delete语句没有后置镜像),之后会进行分支事务注册,TC在收到分支事务注册请求时,会持久化这些分支事务信息和根据操作数据的主键为维度作为全局锁并持久化,可选持久化方式有

 

  • file
  • db
  • redis

 

在收到TC返回的分支注册成功响应后,会把镜像持久化到应用所在的数据源的Undo Log表中,最后提交本地事务。

 

以上所有操作都会保证在同一个本地事务中,保证业务操作和Undo Log操作的原子性

 

一阶段

 

image.png

 

理解了单个应用的处理流程,再从一个完全的调用链路,去看Seata的处理过程,相信理解起来会简单很多,

 

  1. 首先一个使用了@GlobalTransactional的接口被调用,Seata会对其进行拦截,拦截的角色我们称之为TM,这个时候会访问TC开启一个新的全局事务,TC收到请求后会生成XID和全局事务信息并持久化,然后返回XID。
  2. 在每一层的调用链路中,XID都必须往下传递,然后每一层都经过之前说过的处理逻辑,直到执行完成/异常抛出。

 

直到目前,一阶段已经执行完成。

 

另外一个需要注意的问题是,如果发现事务不生效,需要检查XID是否成功往下传递

 

二阶段提交

 

image.png

 

如果在整个调用链路的过程,没有发生任何异常,那么二阶段提交的过程是非常简单而且非常的高效,只有两步

 

  • TC清理全局事务对应的信息
  • RM清理对应Undo Log信息

 

二阶段回滚

 

image.png

 

若调用过程中出现异常,会自动触发反向回滚

 

反向回滚表示,如果调用链路顺序为 A -> B -> C,那么回滚顺序为 C -> B -> A。

例:A=Insert,B=Update,如果回滚时不按照反向的顺序进行回滚,则有可能出现回滚时先把A删除了,再更新A,引发错误

 

在回滚的过程中有可能会遇到一种非常极端的情况,回滚到对应的模块时,找不到对应的Undo Log,这种情况主要发生在

 

  • 分支事务注册成功,但是由于网络原因收不到成功的响应,Undo Log未被持久化
  • 同时全局事务超时(超时时间可自由配置)触发回滚

 

这时候RM会持久化一个特殊的Undo Log,状态为GlobalFinished。由于这个全局事务已经回滚,需要防止网络恢复时,未持久化Undo Log的应用收到了分支注册成功的响应和持久化Undo Log,并提交本地最终引发的数据不一致。

 

读已提交

 

由于在一阶段的时候,数据已经保存到数据库并提交,所以Seata默认的隔离级别为读未提交,如果需要把隔离级别提升至读已提交则需要使用@GlobalLock标签并且在查询语句上加上for update

 

@GlobalLock
@Transactional
public PayMoneyDto detail(ProcessOnEventRequestDto processOnEventRequestDto) {return baseMapper.detail(processOnEventRequestDto.getProcessInfoDto().getBusinessKey())
}@Mapper
public interface PayMoneyMapper extends BaseMapper<PayMoney> {@Select("select id, name, amount, account, has_repayment, pay_amount from pay_money m where m.business_key = #{businessKey} for update")PayMoneyDto detail(@Param("businessKey") String businessKey);
}

 

这个时候Seata会对添加了for update的查询语句进行代理

 

image.png

 

如果一个全局事务1正在操作,并且未进行二阶段提交/回滚的时候,全局锁是被全局事务1锁持有的,同时另外一个全局事务2尝试去查询相同的数据,由于查询语句被代理,seata会尝试去获取这条数据的全局锁,直到获取成功/失败(重试次数达到配置值)为止。

 

问题

 

在生产上运行接近1年时间,总体来说遇到的问题不算多,解决起来也比较容易,比如以下这个问题

 

image.png

 

经过排查发现,由于Seata会使用jdbc标准接口尝试获取业务操作所对应的表结构,由于表结构改动频率较少,并且考虑到表结构变更后应用会进行重启,所以会对表结构进行缓存,如果表结构改动后不对应用进行重启,有可能引发构建镜像时出现NullPointerException。下面贴出关键代码

 

@Override
public TableMeta getTableMeta(final Connection connection, final String tableName, String resourceId) {if (StringUtils.isNullOrEmpty(tableName)) {throw new IllegalArgumentException("TableMeta cannot be fetched without tableName");}TableMeta tmeta;final String key = getCacheKey(connection, tableName, resourceId);//错误关键处,尝试从缓存获取表结构tmeta = TABLE_META_CACHE.get(key, mappingFunction -> {try {return fetchSchema(connection, tableName);} catch (SQLException e) {LOGGER.error("get table meta of the table `{}` error: {}", tableName, e.getMessage(), e);return null;}});if (tmeta == null) {throw new ShouldNeverHappenException(String.format("[xid:%s]get table meta failed," +" please check whether the table `%s` exists.", RootContext.getXID(), tableName));}return tmeta;
}

 

修改表结构,需要对应用进行重启,即可解决此问题,非常简单

 

第二个遇到的问题就是在生产运行一段时间后,发现branch_table和lock_table存在数据残留,并且根据xid查询global_table没有对应的数据,导致后续操作相同的数据行会出现获取全局锁失败,并且会每隔一段时间小量出现。这个异常隐藏的比较深,而且在开发环境和测试环境无法复现,通过跟踪源码和总结原因发现,是由于开启了Mysql主从,导致提交/回滚时,Seata通过xid查询分支事务时,数据未同步到从库,导致遗漏了一部分分支事务数据。

 

源码部分

 

@Override
public GlobalStatus commit(String xid) throws TransactionException {//根据xid查询信息,如果开启主从,会有可能导致查询信息不完整GlobalSession globalSession = SessionHolder.findGlobalSession(xid);if (globalSession == null) {return GlobalStatus.Finished;}globalSession.addSessionLifecycleListener(SessionHolder.getRootSessionManager());// just lock changeStatusboolean shouldCommit = SessionHolder.lockAndExecute(globalSession, () -> {// Highlight: Firstly, close the session, then no more branch can be registered.globalSession.closeAndClean();if (globalSession.getStatus() == GlobalStatus.Begin) {if (globalSession.canBeCommittedAsync()) {globalSession.asyncCommit();return false;} else {globalSession.changeStatus(GlobalStatus.Committing);return true;}}return false;});if (shouldCommit) {boolean success = doGlobalCommit(globalSession, false);//If successful and all remaining branches can be committed asynchronously, do async commit.if (success && globalSession.hasBranch() && globalSession.canBeCommittedAsync()) {globalSession.asyncCommit();return GlobalStatus.Committed;} else {return globalSession.getStatus();}} else {return globalSession.getStatus() == GlobalStatus.AsyncCommitting ? GlobalStatus.Committed : globalSession.getStatus();}
}

 

@Override
public GlobalStatus rollback(String xid) throws TransactionException {//根据xid查询信息,如果开启主从,会有可能导致查询信息不完整GlobalSession globalSession = SessionHolder.findGlobalSession(xid);if (globalSession == null) {return GlobalStatus.Finished;}globalSession.addSessionLifecycleListener(SessionHolder.getRootSessionManager());// just lock changeStatusboolean shouldRollBack = SessionHolder.lockAndExecute(globalSession, () -> {globalSession.close(); // Highlight: Firstly, close the session, then no more branch can be registered.if (globalSession.getStatus() == GlobalStatus.Begin) {globalSession.changeStatus(GlobalStatus.Rollbacking);return true;}return false;});if (!shouldRollBack) {return globalSession.getStatus();}doGlobalRollback(globalSession, false);return globalSession.getStatus();
}

 

相信此问题会在支持Raft之后得到完美的解决

pr: https://github.com/seata/seata/pull/3086

有兴趣的朋友也可以尝试去review一下代码

 

部署-高可用

 

image.png

 

Seata和其他中间件的高可用部署方式差别不大,如图片所示,确保应用服务和TC访问相同的注册中心和配置中心,同时只需要启动多台TC,并将store.mode改为db模式即可完成高可用部署,并选择合适的注册中心和配置中心即可,目前支持的配置中心有

 

  • nacos
  • consul
  • etcd3
  • eureka
  • redis
  • sofa
  • zookeeper

 

可选的配置中心有

 

  • nacos
  • etcd3
  • consul
  • apollo
  • zk

 

部署-单节点多应用

 

image.png

 

当然也有更加灵活的部署方式,通过vgoup-mapping(事务集群),可以做到单节点多应用的隔离,比如A应用和B应用访问A-Group的两个TC,C应用和D应用访问B-Group的两个TC,E应用和F应用访问C-Group的两个TC。

 

部署-异地容灾

 

image.png

 

image.png

 

通过vgoup-mapping也可以做到异地容灾,当原有集群出现不可用时,可以通过变更配置立刻转移到备用的集群上。此处以Nacos作为注册中心举例,TC配置方式如下:

 

# 广州机房
registry {# file 、nacos 、eureka、redis、zk、consul、etcd3、sofatype = "nacos"loadBalance = "RandomLoadBalance"loadBalanceVirtualNodes = 10nacos {application = "seata-server"serverAddr = "127.0.0.1:8848"group = "SEATA_GROUP"namespace = ""cluster = "Guangzhou"username = ""password = ""}
}

 

# 上海机房
registry {# file 、nacos 、eureka、redis、zk、consul、etcd3、sofatype = "nacos"loadBalance = "RandomLoadBalance"loadBalanceVirtualNodes = 10nacos {application = "seata-server"serverAddr = "127.0.0.1:8848"group = "SEATA_GROUP"namespace = ""cluster = "Shanghai"username = ""password = ""}
}

原文链接

本文为阿里云原创内容,未经允许不得转载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/513810.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

“类云”的存储服务什么样?Pure Storage发布Pure Fusion等系列新品

一键部署自动化存储平台与云原生数据库即服务&#xff0c;无缝连接基础设施运营与应用程序。 编辑 | 宋慧 出品 | CSDN 云计算 近日&#xff0c;专为多云环境提供存储即服务的Pure Storage发布一系列现代化基础设施、运营及应用程序&#xff0c;这是Pure Storage迈向创新现代…

mac mongodb可视化工具_MongoDB从立地到成佛(介绍、安装、增删改查)

文章作者公众号bigsai&#xff0c;已收录在回车课堂,如有帮助还请不吝啬点个赞赞支持一下&#xff01;课程导学大家好我是bigsai&#xff0c;我们都学过数据库&#xff0c;但你可能更熟悉关系(型)数据库例如MySQL&#xff0c;SQL SERVER&#xff0c;ORACLE等&#xff0c;对于非…

阿里巴巴云原生 etcd 服务集群管控优化实践

简介&#xff1a; 这些年&#xff0c;阿里云原生 etcd 服务发生了翻天覆地的变化&#xff0c;这篇文章主要分享一下 etcd 服务在面对业务量大规模增长下遇到的问题以及我们是如何解决的&#xff0c;希望对读者了解 etcd 的使用和管控运维提供经验分享。 作者 | 陈星宇&#xff…

计算机组装与维护思考问题,计算机组装与维护中的常见问题及解决方法

郜庆国摘要&#xff1a;在如今的社会下&#xff0c;各个领域的很多行业在工作的时候都需要用到计算机来帮助工作的进行&#xff0c;因为计算机在很多情况下都能够很好地进行计算与帮助&#xff0c;所以我们在进行工作时&#xff0c;不仅提高了工作的效率&#xff0c;还解决了很…

淘票票首次公开小程序开发秘籍,踩过坑才知道怎么走!

简介&#xff1a; 在2019年&#xff0c;阿里巴巴文娱的淘票票几乎涉足了当时市面上所有的小程序。在不少平台上&#xff0c;淘票票是阿里“第一批吃螃蟹”的技术团队。回顾过往&#xff0c;阿里文娱做过很多尝试&#xff0c;也踩过很多坑。《小程序 大世界》总结了淘票票过去 2…

stm32f407 6个串口dma_stm32之DMA

一. 对于大容量的STM32芯片有2个DMA控制器&#xff0c;控制器1有7个通道&#xff0c;控制器2有5个通道每个通道都可以配置一些外设的地址。二. 通道的配置过程&#xff1a;1. 首先设置CPARx寄存器和CMARx寄存器。通过DMA控制器把一个地址的值复制到另外一个地址&#xff0c;通过…

立足当下,塑造未来

今天&#xff0c;以“5G与世界同行”为主题的2021全球移动宽带论坛&#xff08;Global MBB Forum&#xff09;在迪拜举行。期间&#xff0c;华为轮值董事长胡厚崑发表了题为“立足当下&#xff0c;塑造未来”的主题演讲。胡厚崑指出&#xff1a;“5G预商用五年以来&#xff0c;…

jfinal html5,Jfinal框架整合webSocket技术功能实现

技术难度&#xff1a;简单在这里我会用最简单的方法实现JFinal框架结合webSocket最基础的功能&#xff0c;以至于后续业务的拓展需要小伙伴们依据实际情况去实现相应的开发&#xff01;废话不多说&#xff0c;直接上代码&#xff01;1、编写webSocket类package morality.ws;imp…

行业实战 | 5G+边缘计算+“自由视角” 让体育赛事更畅快

简介&#xff1a; 世界本是多维的。进入5G时代&#xff0c;观众对多维度视觉体验的需求日益增长&#xff0c;5G MEC网络与边缘计算的结合&#xff0c;具备大带宽、低延迟特性&#xff0c;使视频多维视觉呈现成为现实。在第二十三届CUBA中国大学生篮球联赛期间&#xff0c;中国电…

华为汪涛:走向智能世界2030,无线网络未来十年十大产业趋势

2021全球移动宽带论坛&#xff08;Global MBB Forum&#xff09;期间&#xff0c;华为常务董事、ICT基础设施业务管理委员会主任汪涛发表了题为“走向智能世界2030&#xff0c;无线网络未来十年十大趋势”的主题演讲。汪涛表示&#xff1a;“未来十年&#xff0c;是走向智能世界…

python怎么输入一个数字并调用_Python3 实例(一)

原标题&#xff1a;Python3 实例&#xff08;一&#xff09; Python Hello World 实例 以下实例为学习Python的第一个实例&#xff0c;即如何输出"Hello World!"&#xff1a; 实例 # -*- coding: UTF-8 -*- # Filename : helloworld.py # author by : www.runoob.com…

未来教育计算机书,未来教育.全国计算机等级考试

1册图书1张光盘&#xff0c;轻松应对2018年一级计算机基础及MS Office应用考试 n 1.历年真题精选&#xff0c;全方位把握真考动向&#xff0c;具有练习价值 n (1)新大纲、新题型、新题库&#xff0c;全方位解读无纸化考试&#xff0c;帮助考生轻松过关。 n (2)精选2017年~2016年…

python制作简单网页_python 跑服务器,访问自己制作的简单页面

1 python 跑服务器&#xff0c;访问自己制作的简单页面2 # winb出现一个网址http:/0.0.1:5000/复制到浏览器查看 # http://127.0.0.1:5000/home做这个首先要安装好python的flask&#xff0c;前面有详细的安装步骤 具体代码如下# coding:utf-8 # Flask构造函数&#xff0c;从fla…

华为云专属月·行业深耕专项行动正式开启

10月15日&#xff0c;以“云上共创 互联网新价值”为主题的“华为云专属月行业深耕”线上发布会圆满举办。会上&#xff0c;华为云宣布“华为云专属月行业深耕专项行动”正式启动&#xff0c;本次专属月期间&#xff0c;华为云将面向包括电商、游戏、移动出行三大行业在内的互联…

计算机电子工程专业就业前景,就业前景好的4大类专业,电气电子类上榜,有你喜欢的吗?...

原标题&#xff1a;就业前景好的4大类专业&#xff0c;电气电子类上榜&#xff0c;有你喜欢的吗&#xff1f;行业的发展和社会的发展是紧密相连的&#xff0c;所以为了推动社会各个行业的发展&#xff0c;在大学中就会设立很多类型的专业。下面来介绍一下就业前景好的4大类专业…

Nacos 2.0 性能提升十倍,贡献者 80% 以上来自阿里之外

简介&#xff1a; 3 月 20 日&#xff0c;Nacos 2.0 正式发布。Nacos 是阿里巴巴在 2018 年开源的一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台&#xff0c;也可以理解为微服务的注册中心 配置中心。 来源 | 阿里巴巴云原生公众号 3 月 20 日&#xff0c…

以系统化视角反观产品运营,解读提升用户转化的“四部曲”

简介&#xff1a; 正常的活动运营通常会围绕公司经营目标&#xff0c;针对不同性质、不同类型的活动开展工作。这样的活动一般会分四个阶段&#xff1a;活动准备、活动策划、活动执行与活动复盘阶段。 作者&#xff1a;友盟数据大使 Suffering 如今&#xff0c;移动互联网的红…

移动云亮相 2021 IDC 年度盛典 共话变革与赋能

“过去一年&#xff0c;是考验各行业在面临多重挑战时保持数字弹性能力的一年。未来十年&#xff0c;是数字化转型成为企业主旋律、加速推进的十年。”10月15日&#xff0c;由IDC中国主办的“2021第六届IDC中国数字化转型年度盛典”在上海圆满落幕。盛典以“数字焕颜 科技有型”…

春色满园关不住,带你体验阿里云 Knative

简介&#xff1a; Knative 是基于 Kubernetes 的开源 Serverless 应用编排框架。阿里云 Knative 在社区Knative基础之上&#xff0c;与阿里云产品进行了深度的融合&#xff0c;给你带来最纯粹的容器化 Serverless 体验。 Knative 是基于 Kubernetes 的开源 Serverless 应用编排…

python选择排序算法图解_python基本算法之实现归并排序(Merge sort)

0、前言 评判一个算法的好坏的标准&#xff1a; 时间复杂度 空间复杂度 1、归并排序算法是什么&#xff1f; 冒泡排序(Bubble Sort)是一种建立在归并操作上面的一种有效的排序算法&#xff0c;由John von neumann于1945年发明。采用分治法&#xff08;Divide and Conquer&#…