备案 控制台
开发者社区 开发与运维 文章 正文

简单高效的代码优化-事务后异步处理

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 基于skywalking监控分析,简单高效的优化代码,提升90%+效率,设计事务后异步处理,EventListener,Mysql隔离级别等知识点

背景

以电商平台为例,对于用户而言订单签收是订单正向流程的最后一环,也是用户高频使用的场景之一。

最近接触的一个项目已存在多年,现阶段已有的订单签收逻辑存在较为严重的性能问题,线上监控显示订单的签收接口耗时达到了1s-5s甚至以上,对于用户而言签收会产生明显的页面卡顿。并且随着需要签收的一单内含有的商品越多,签收耗时越长,这显然是无法接受的。基于此,签收的重构便提上了日程。

现状

如下是Skywalking中,对旧签收接口的监控

订单含单个商品时,签收耗时1-1.8s左右:

order-sk-1.png

订单含赠品/或为预售订单时,签收耗时超3s

order-sk-2.png

订单为套装组合时,签收耗时超5s

order-sk-3.png

由于签收服务中包含签收核心逻辑,其余多种签收方式均会走到服务提供的接口

涉及多个服务调用,包含后台签收、用户自己签收、定时任务签收、基于轨迹的签收

核心接口慢,导致该服务在诸多调用情况下TP99居高不下,同时使得常用的批量签收有着惊人的超50s的阻塞式耗时,签收相关接口长期处在监控耗时Top5

order-sk-4.png

order-sk-5.png

可以显而易见地观察到批量任务中50s中有30s都在跑签收

其次,代码上还存在如下典型问题

  • 签收代码逻辑混乱,主流程和次要流程交替出现在代码中,校验混合在各种方法内,次要流程异步化不完善
  • 没有事务控制,无法保证核心流程的幂等性,如果发生意外还需要手动修复数据
  • 潜在的永不过期锁
  • try catch,异常控制粒度过粗
  • 泛型的折叠使用,存在潜在的泛型擦涂问题
  • 单一方法职责不清晰,代码过长导致阅读困难

Service层

order-sk-6.png

order-sk-7.png

Manager层

order-sk-8.png

order-sk-9.png

order-sk-10.png

优化方案

有了问题分析,优化方案就是逐个解决上述问题即可

对于Service层

  1. 将业务校验统一在Service层,结合全局异常,很容易写出整洁的代码
  2. 所有的RemoteResult都必须加上泛型推断,一是需要通过代码检测插件,二是避免泛型擦涂问题,在编译期提前发现问题。关于泛型擦涂问题,这里不做展开涉及。
  3. 新增Redission组件,替换原本的基于lettuce手动编写的加解锁,将加锁代码写入try catch中,避免指令已发送到机器加上了锁,但加锁返回结果超时未被异常捕获,无法解锁,造成永不过期的锁。Redission作为Redis官方指定的分布式Redis组件,无需担心许多分布式场景下的加解锁、续期、释放了非加锁线程的锁等问题,方案拉满,已经非常成熟。这里不做展开涉及。
  4. 细粒度的异常分类,不同异常做出不同处理

优化后的代码为

order-sk-11.png

order-sk-12.png

需要注意的是,这里Service层不需要加入@Transactional事务控制,这样会造成大事务,校验过程有的时候是复杂且耗时的,数据库连接是宝贵的,当事务开启时数据库的连接就会被占用,避免其余线程拿不到连接的情况。

对于Manager层

需要做的是复用签收这个动作会产生的所有数据库/中间件影响,不应该在Manager层存在业务校验

  1. 理清核心流程与分支流程,分支流程全异步化,只在核心流程落库成功后执行
  2. 开启事务控制,所有方法在同一个事务中,要么一起成功,要么一起失败,保证异常情况下的数据幂等性
  3. 分支流程只在事务提交成功后,才开始处理,避免明明数据落库失败了,但下游却收到签收成功了的消息

order-sk-13.png

上图中的事务1、2、3、4的代码结构基本上和下图相同
order-sk-14.png

每一个需要受到事务控制的Manager中的方法都需要加上@Transactionnal的注解,并指定rollbackFortransactionManager,同时在捕获异常后将异常直接抛出,以使得外围事务感知到内部事务异常,使事务回滚

根据@Transactional的默认传播级别Propagation.REQUIRED,事务1、2、3、4都将加入到外层事务中,其中任意一个事务异常,均会使得1、2、3、4回滚,同时后续的分支流程不会执行

需要注意的是,如果你使用@Transactional注解,你应该熟悉该注解的各种失效场景及多种传播机制,避免发生以为有回滚,其实不会回滚的情况。

事务4是操作mongoDb的事务,同样可以用@Transactional注解控制

分支流程

分支流程需要在核心流程数据落库之后才开始处理

如果你熟悉GoogleEventBus或者Spring@EventListener,你可以很快速的迁移知识到Spring @TransactionalEventListener

帮助解耦代码,实现事务提交后异步执行分支流程,@TransactionalEventListener@EventListener的子类,用于支持事务上的Event总线。

Spring中我们可以很方便的使用TransactionSynchronizationManager.registerSynchronization执行事务方法的回调,并实现TransactionSynchronizationAdapter其中的接口即可

如果你研究过@Transactional的原理,那么对事务管理器的处理就并不陌生

order-sk-15.png

在本文中,我们只需要实现afterCommit方法即可,如果在@Transactional的代码中存在事务同步器扩展点,则上文事务执行后,依次会执行扩展点后的方法

Q:TransactionSynchronizationManager.registerSynchronization是必须的吗

A:不是,只有你的Event事件实体(本文的OrderSignEvent)构建依赖于上文事务的结果时,你才需要使用该方法,否则直接采用applicationEventPublisher.pushEvent即可,register只是提供了除注解外手动事务的实现,用于更细微的代码控制

pushEvent之后,我们可以编写对应的监听者

以如下分支流程为例,InsertItemOpen用于签收成功后,通过计算往反向表中插入数据,用于后续的撤单、退货等

order-sk-18.png

直接采用@TransactionalEventListener默认采用单一线程的线程池,同时也不是异步线程,因此需要手动创建出线程池,并加以@Async指定

我们可以指定该方法的执行阶段,这里为TransactionPhase.AFTER_COMMIT,即事务提交之后,监听的classOrderSignEvent.class

TransactionSynchronizationAdapter类似,TransactionPhase枚举分为如下4个阶段,用于@TransactionalEventListener注解上

order-sk-19.png

同时需要注意,如果事务提交后/完成后的event内有执行数据库新增操作,那么他的传播级别就不能是@Transactional的默认传播级别,需要至少修改为Propagation.REQUIRES_NEW,新开一个事务

这样做的原因是因为,此时如果为默认的传播机制,则会加入到上文事务中,但上文事务已经提交了,这时候insert插入数据库实际上是空执行了一次,因为本次执行不会再提交。

线程池配置

order-sk-20.png

线程池避免全局使用同一个线程池,避免某一任务激增导致其余使用该线程池的任务,无法获取线程的问题,同时执行不同种任务的线程池,应该设定线程前缀名,方便链路跟踪

在社区中,阿里开发手册具有类似建议

order-sk-21.png

签收的分支流程分为如下几个大类,重构时可按照不同类型分类监听

tip: 由于监听者是异步线程,所以监听者内部抛出的异常是不能够被全局异常捕获的,我们可以像上文insertItemOpen方法一样,catch住异常再选择是抛出还是打印日志

不同于EventBus,在idea中,天然的支持了Spring Event的跟踪,点击事件发布者左侧绿标,便可以找到对应的事件监听者

order-sk-22.png

点击监听者旁的绿标同样可以回到事件发布者,非常的便捷

order-sk-23.png

优化效果

本文的性能问题不体现在慢SQL上,所以优化方案中并不包含SQL优化处理

同时由于分支流程下游方法的幂等未知性,重构时没有加入分支流程的重试机制,这些方法在重构时都是可以考虑的点。

改造后TP99监控,曲线更加平稳,除异步导出外多数接口在600ms内返回

order-sk-24.png

用户自行签收,从1.8s0.037s效率提升97.94%

Top30内不再看到签收接口的上榜

order-sk-25.png

批量签收返回耗时缩小一个量级,从5w5k效率提升90%

order-sk-26.png

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
benym
目录
相关文章
人不走空
|
3月前
|
算法 安全 编译器
并发的三大特性
并发的三大特性
人不走空
38 1
前端充电宝
|
3月前
|
存储 前端开发 JavaScript
如何解决前端常见的竞态问题?
如何解决前端常见的竞态问题?
前端充电宝
48 0
猫头虎
|
6月前
|
消息中间件 缓存 NoSQL
高并发幂等计数器的设计与实现
高并发幂等计数器的设计与实现
猫头虎
110 0
高并发幂等计数器的设计与实现
Go先锋
|
6月前
|
Java
并发三大特性
并发三大特性
Go先锋
20 0
游客z4yknzc4u3d6i
|
9月前
|
设计模式 算法 安全
并发 并行 同步 异步 你分清了吗
并发 并行 同步 异步 你分清了吗
游客z4yknzc4u3d6i
91 0
javaedge
|
11月前
|
SQL 监控 Kubernetes
业务逻辑复杂如何解决性能问题
上节针对生成订单信息这个接口做了三个阶段的分析定位和优化动作,让TPS变得正常。不过,系统资源并没有完全用起来,这个接口显然还有优化空间。性能优化的过程中,要把资源都用起来。
javaedge
128 0
愿天堂没有BUG(公众号同名)
|
缓存 安全 算法
高性能无锁并发框架Disruptor,太强了
Disruptor是一个开源框架,研发的初衷是为了解决高并发下队列锁的问题,最早由LMAX提出并使用,能够在无锁的情况下实现队列的并发操作,并号称能够在一个线程里每秒处理6百万笔订单
愿天堂没有BUG(公众号同名)
243 0
1224480819038671
|
缓存 安全 算法
高性能无锁并发框架Disruptor,太强了!
Disruptor是一个开源框架,研发的初衷是为了解决高并发下队列锁的问题,最早由LMAX提出并使用,能够在无锁的情况下实现队列的并发操作,并号称能够在一个线程里每秒处理6百万笔订单官网:lmax-exchange.github.io/disruptor/目前,包括Apache Storm、Camel、Log4j2在内的很多知名项目都应用了Disruptor以获取高性能为什么会产生Disruptor框架「目前Java内置队列保证线程安全的方式:」ArrayBlockingQueue:基于数组形式的队列,通过加锁的方式,来保证多线程情况下数据的安全;LinkedBlockingQue基于链表形式
1224480819038671
657 0
阿里云云原生
|
Kubernetes 负载均衡 算法
异步任务处理系统,如何解决业务长耗时、高并发难题?
阿里云函数计算 FC 为用户提供了开箱即用的,接近于Level ß3能力的异步任务处理服务。用户只需要创建任务处理函数,通过控制台,命令行工具,API/SDK,事件触发等多种方式提交任务,就可以弹性、可靠、可观测完备的方式处理任务。
阿里云云原生
956 1
异步任务处理系统,如何解决业务长耗时、高并发难题?
平凡人笔记
|
存储 移动开发 NoSQL
Redis命令性能优化及事务使用过程(上)
Redis命令性能优化及事务使用过程(上)
平凡人笔记
190 0
Redis命令性能优化及事务使用过程(上)

热门文章

最新文章

  • 1
    ubuntu安装KVM虚拟机管理virt-manager
  • 2
    大咖云集,技术宅开趴倒计时 —— 2017 Kubernetes Meetup | 成都站
  • 3
    Linux查看进程的内存占用情况
  • 4
    一分钟了解阿里云产品:解析不生效的原因诊断
  • 5
    实现.net下的动态代理
  • 6
    2017-5-29学习记录——WebApi(1)
  • 7
    一步步手动实现热修复(二)-类的加载机制简要介绍
  • 8
    【MySQL】replace into 浅析之二
  • 9
    基于多串口ETH005设备的Socket网络编程
  • 10
    通讯录代码
  • 1
    Serverless 应用引擎产品使用之在函数计算中,数据库访问失败如何解决
    14
  • 2
    Serverless 应用引擎产品使用之在阿里云函数计算中发现没有NAC(Native Application Component)选项,且无法自己上传MOD(模块)如何解决
    17
  • 3
    Serverless 应用引擎操作报错合集之在阿里云函数计算中,调用了FC函数但是没有执行或者报错,并且在FC函数后台也看不到调用记录日志如何解决
    16
  • 4
    揭秘深度学习在图像识别中的奥秘
    13
  • 5
    构建高效的Android应用:从内存优化到电池寿命
    17
  • 6
    深入理解与应用软件自动化测试框架
    17
  • 7
    移动应用开发的未来:跨平台框架与原生系统的挑战
    15
  • 8
    Serverless 应用引擎操作报错合集之在阿里函数计算中,sd部署启动报错CAExited 报错信息“operation not permitted”如何解决
    15
  • 9
    构建高效云原生应用:以Kubernetes为核心
    16
  • 10
    深入理解软件测试中的持续集成与持续部署
    12
  • 相关电子书

    更多
  • 如何做小程序性能优化
  • 分布式高并发缓存6.0
  • Redis多线程性能优化
  • 下一篇
    2024年阿里云免费云服务器及学生云服务器申请教程参考

    深圳SEO优化公司白银建网站推荐清远外贸网站制作多少钱包头外贸网站制作报价景德镇seo哪家好九江网站建设设计肇庆百度网站优化排名公司张家口网站优化排名公司淄博网站设计模板价格福永百度关键词包年推广公司黔东南seo网站推广哪家好济南关键词按天扣费公司常州百度关键词包年推广鹤壁模板网站建设石岩百度竞价包年推广多少钱开封优秀网站设计邯郸营销网站多少钱北京至尊标王公司雅安百度竞价包年推广多少钱驻马店模板网站建设哪家好保定网站优化排名公司烟台网站优化排名坪地百姓网标王报价阜阳网站设计模板报价黔西南网站设计推荐永新模板网站建设哪家好延安设计网站哪家好黄石关键词按天收费公司潮州网站搜索优化推荐鹤岗模板推广报价西宁seo网站推广价格歼20紧急升空逼退外机英媒称团队夜以继日筹划王妃复出草木蔓发 春山在望成都发生巨响 当地回应60岁老人炒菠菜未焯水致肾病恶化男子涉嫌走私被判11年却一天牢没坐劳斯莱斯右转逼停直行车网传落水者说“没让你救”系谣言广东通报13岁男孩性侵女童不予立案贵州小伙回应在美国卖三蹦子火了淀粉肠小王子日销售额涨超10倍有个姐真把千机伞做出来了近3万元金手镯仅含足金十克呼北高速交通事故已致14人死亡杨洋拄拐现身医院国产伟哥去年销售近13亿男子给前妻转账 现任妻子起诉要回新基金只募集到26元还是员工自购男孩疑遭霸凌 家长讨说法被踢出群充个话费竟沦为间接洗钱工具新的一天从800个哈欠开始单亲妈妈陷入热恋 14岁儿子报警#春分立蛋大挑战#中国投资客涌入日本东京买房两大学生合买彩票中奖一人不认账新加坡主帅:唯一目标击败中国队月嫂回应掌掴婴儿是在赶虫子19岁小伙救下5人后溺亡 多方发声清明节放假3天调休1天张家界的山上“长”满了韩国人?开封王婆为何火了主播靠辱骂母亲走红被批捕封号代拍被何赛飞拿着魔杖追着打阿根廷将发行1万与2万面值的纸币库克现身上海为江西彩礼“减负”的“试婚人”因自嘲式简历走红的教授更新简介殡仪馆花卉高于市场价3倍还重复用网友称在豆瓣酱里吃出老鼠头315晚会后胖东来又人满为患了网友建议重庆地铁不准乘客携带菜筐特朗普谈“凯特王妃P图照”罗斯否认插足凯特王妃婚姻青海通报栏杆断裂小学生跌落住进ICU恒大被罚41.75亿到底怎么缴湖南一县政协主席疑涉刑案被控制茶百道就改标签日期致歉王树国3次鞠躬告别西交大师生张立群任西安交通大学校长杨倩无缘巴黎奥运

    深圳SEO优化公司 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化