当前位置:首页 > > 架构师社区
[导读]最近,因为增加了一些风控措施,导致新人拼团订单接口的QPS、TPS下降了约5%~10%,这还了得!

作者:浪漫先生

来源:uejin.im/post/6854573218322513933



# 前言


最近,因为增加了一些风控措施,导致新人拼团订单接口的QPS、TPS下降了约5%~10%,这还了得!


首先,快速解释一下【新人拼团】活动:


业务简介:顾名思义,新人拼团是由新用户发起的拼团,如果拼团成功,系统会自动奖励新用户一张满15.1元减15的平台优惠券。这相当于是无门槛优惠了。每个用户仅有一次机会。新人拼团活动的最大目的主要是为了拉新。


新用户判断标准:是否有支付成功的订单 ? 不是新用户 : 是新用户。


当前问题:由于像这种优惠力度较大的活动很容易被羊毛党、黑产盯上。因此,我们完善了订单风控系统,让黑产无处遁形!然而由于需要同步调用风控系统,导致整个下单接口的的QPS、TPS的指标皆有下降,从性能的角度来看,【新人拼团下单接口】无法满足性能指标要求。因此CTO指名点姓让我带头冲锋……冲啊!


# 问题分析


风控系统的判断一般分为两种:在线同步分析和离线异步分析。在实际业务中,这两者都是必要的。在线同步分析可以在下单入口处就拦截掉风险,而离线异步分析可以提供更加全面的风险判断基础数据和风险监控能力。


最近我们对在线同步这块的风控规则进行了加强和优化,导致整个新人拼团下单接口的执行链路更长,从而导致TPS和QPS这两个关键指标下降。


# 解决思路


要提升性能,最简单粗暴的方法是加服务器!然而,无脑加服务器无法展示出一个出色的程序员的能力。CTO说了,要加服务器可以,买服务器的钱从我工资里面扣……


在测试环境中,我们简单的通过使用StopWatch来简单分析,伪代码如下:

@Transactional(rollbackFor = Exception.class)public CollageOrderResponseVO colleageOrder(CollageOrderRequestVO request) { StopWatch stopWatch = new StopWatch();  stopWatch.start("调用风控系统接口"); // 调用风控系统接口, http调用方式 stopWatch.stop();  stopWatch.start("获取拼团活动信息"); //  // 获取拼团活动基本信息. 查询缓存 stopWatch.stop();  stopWatch.start("获取用户基本信息"); // 获取用户基本信息。http调用用户服务 stopWatch.stop();  stopWatch.start("判断是否是新用户"); // 判断是否是新用户。查询订单数据库 stopWatch.stop();  stopWatch.start("生成订单并入库"); // 生成订单并入库 stopWatch.stop();  // 打印task报告 stopWatch.prettyPrint();  // 发布订单创建成功事件并构建响应数据 return new CollageOrderResponseVO();}


执行结果如下:

StopWatch '新人拼团订单StopWatch': running time = 1195896800 ns---------------------------------------------ns % Task name---------------------------------------------014385000 021% 调用风控系统接口010481800 010% 获取拼团活动信息013989200 015% 获取用户基本信息028314600 030% 判断是否是新用户028726200 024% 生成订单并入库


在测试环境整个接口的执行时间在1.2s左右。其中最耗时的步骤是【判断是否是新用户】逻辑。这是我们重点优化的地方(实际上,也只能针对这点进行优化,因为其他步骤逻辑基本上无优化空间了)。


# 确定方案


在这个接口中,【判断是否是新用户】的标准是是用户是否有支付成功的订单。因此开发人员想当然的根据用户ID去订单数据库中查询。我们的订单主库的配置如下:

拼团活动遇黑产?搭进去了8台服务器...


这配置还算豪华吧。然而随着业务的积累,订单主库的数据早就突破了千万级别了,虽然会定时迁移数据,然而订单量突破千万大关的周期越来越短……(分库分表方案是时候提上议程了,此次场景暂不讨论分库分表的内容)而用户ID虽然是索引,但毕竟不是唯一索引。因此查询效率相比于其他逻辑要更耗时。


通过简单分析可以知道,其实只需要知道这个用户是否有支付成功的订单,至于支付成功了几单我们并不关心。因此此场景显然适合使用redis的bitmap数据结构来解决。在支付成功方法的逻辑中,我们简单加一行代码来设置bitmap:

// 说明:key表示用户是否存在支付成功的订单标记// userId是long类型String key = "order:f:paysucc"; redisTemplate.opsForValue().setBit(key, userId, true);

通过这一番改造,在下单时【判断是否是新用户】的核心代码就不需要查库了,而是改为:

Boolean paySuccFlag = redisTemplate.opsForValue().getBit(key, userId);if (paySuccFlag != null && paySuccFlag) { // 不是新用户,业务异常}


修改之后,在测试环境的测试结果如下:

StopWatch '新人拼团订单StopWatch': running time = 82207200 ns---------------------------------------------ns % Task name---------------------------------------------014113100 017% 调用风控系统接口010193800 012% 获取拼团活动信息013965900 017% 获取用户基本信息014532800 018% 判断是否是新用户029401600  036%  生成订单并入库


测试环境下单时间变成了0.82s,主要性能损耗在生成订单入库步骤,这里涉及到事务和数据库插入数据,因此是合理的。接口响应时长缩短了31%!相比生产环境的性能效果更明显……接着舞!


# 晴天霹雳


这次的优化效果十分明显,想着CTO该给我加点绩效了吧,不然我工资要被扣完了呀~


一边这样想着,一边准备生产环境灰度发布。发完版之后,准备来个葛优躺好好休息一下,等着测试妹子验证完就下班走人。然而在我躺下不到1分钟的时间,测试妹子过来紧张的跟我说:“接口报错了,你快看看!”What?


当我打开日志一看,立马傻眼了。报错日志如下:

io.lettuce.core.RedisCommandExecutionException: ERR bit offset is not an integer or out of rangeat io.lettuce.core.ExceptionFactory.createExecutionException(ExceptionFactory.java:135) ~[lettuce-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]at io.lettuce.core.ExceptionFactory.createExecutionException(ExceptionFactory.java:108) ~[lettuce-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]at io.lettuce.core.protocol.AsyncCommand.completeResult(AsyncCommand.java:120) ~[lettuce-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]at io.lettuce.core.protocol.AsyncCommand.complete(AsyncCommand.java:111) ~[lettuce-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]at io.lettuce.core.protocol.CommandHandler.complete(CommandHandler.java:654) ~[lettuce-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]at io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:614) ~[lettuce-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]…………


bit offset is not an integer or out of range。这个错误提示已经很明显:我们的offset参数out of range。为什么会这样呢?我不禁开始思索起来:redis bitmap的底层数据结构实际上是string类型,redis对于string类型有最大值限制不得超过512M,即2^32次方byte…………我靠!!!


# 恍然大悟


由于测试环境历史原因,userId的长度都是8位的,最大值99999999,假设offset就取这个最大值。那么在bitmap中,bitarray=999999999=2^29byte。因此setbit没有报错。


而生产环境的userId,经过排查发现用户中心生成ID的规则变了,导致以前很老的用户的id长度是8位的,新注册的用户id都是18位的。以测试妹子的账号id为例:652024209997893632 = 2^59byte,这显然超出了redis的最大值要求。不报错才怪!


紧急回退版本,灰度发布失败~还好,CTO念我不知道以前的这些业务规则,放了我一马~该死,还想着加绩效,没有扣绩效就是万幸的了!


本次事件暴露出几个非常值得注意的问题,值得反思:


  • 懂技术体系,还要懂业务体系


    对于bitmap的使用,我们是非常熟悉的,对于多数高级开发人员而言,他们的技术水平也不差,但是因为不同业务体系的变迁而无法评估出精准的影响范围,导致无形的安全隐患。本次事件就是因为没有了解到用户中心的ID规则变化以及为什么要变化从而导致问题发生。

  • 预生产环境的必要性和重要性


    导致本次问题的另一个原因,就是因为没有预生产环境,导致无法真正模拟生产环境的真实场景,如果能有预生产环境,那么至少可以拥有生产环境的基础数据:用户数据、活动数据等。很大程度上能够提前暴露问题并解决。从而提升正式环境发版的效率和质量。

  • 敬畏心


    要知道,对于一个大型的项目而言,任何一行代码其背后都有其存在的价值:正所谓存在即合理。别人不会无缘无故这样写。如果你觉得不合理,那么需要通过充分的调研和了解,确定每一个参数背后的意义和设计变更等。以尽可能降低犯错的几率。


# 后记


通过此次事件,本来想着优化能够提升接口效率,从而不需要加服务器。这下好了,不仅生产环境要加1台服务器以临时解决性能指标不达标的问题,还要另外加7台服务器用于预生产环境的搭建!因为bitmap,搭进去了8台服务器。痛并值得。接着奏乐,接着舞~~~


免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭