最近因为加了一些风控措施,新人团单接口的QPS和TPS下降了5%~10%左右,这还不错!来自Pexels首先简单介绍一下【新人加群】活动:业务介绍:顾名思义,新人加群是由新用户发起的加群。加入成功,系统将自动奖励新用户一张15.1元减15的平台券。这相当于无门槛折扣。每个用户只有一次机会。新人团活动最大的目的主要是吸引新人。新用户判断标准:是否有支付成功的订单?不是新用户:新用户。目前问题:由于此类活动折扣力度大,容易被羊毛党、黑货盯上。为此,我们完善了订单风控系统,让黑货无处遁形!但由于需要同步调用风控系统,导致整个订单接口的QPS和TPS指标有所下降。从性能上看,【新人入团下单接口】无法满足性能指标要求。所以,CTO让我带头冲锋,姓氏……冲锋!问题分析风控系统的判断一般分为在线同步分析和离线异步分析两种。在实际业务中,两者都是必需的。在线同步分析可以在订单入口处拦截风险,离线异步分析可以提供更全面的风险判断基础数据和风险监控能力。近期,我们加强优化了线上同步的风控规则,导致整个新人团单界面的执行链路变长,导致TPS和QPS这两个关键指标有所下降。解决思路要提高性能,最简单粗暴的方法就是增加服务器!但是,无脑添加一个服务器,并不能体现一个优秀程序员的能力。CTO说加个服务器就可以了,买服务器的钱会从我的工资里扣。伪代码如下:@Transactional(rollbackFor=Exception.class)publicCollageOrderResponseVOcolleageOrder(CollageOrderRequestVOrequest){StopWatchstopWatch=newStopWatch();stopWatch.start("调用风控系统接口");//调用风控系统接口,http调用方法stopWatch.stop();stopWatch.start("获取入团活动信息");////获取入团活动基本信息。查询缓存stopWatch.stop();stopWatch.start("获取用户基本信息");//获取用户基本信息。http调用用户服务stopWatch.stop();stopWatch.start("判断是否为新用户");//判断是否为新用户。查询订单数据库stopWatch.stop();stopWatch.start("生成订单入库");//生成订单入库stopWatch.stop();//打印任务报告stopWatch.prettyPrint();//发布订单创建成功事件并构造响应数据returnnewCollageOrderResponseVO();}执行结果如下:StopWatch'StopWatch':runningtime=1195896800ns----------------------------------------ns%Taskname----------------------------------------------014385000021%调用风控系统接口010481800010%获取入团活动信息013989200015%获取用户基本信息028314600030%判断是否为新用户028726200024%测试环境下整个接口生成订单入库的执行时间约为1.2s。最耗时的步骤是【判断是否为新用户】的逻辑。这是我们重点优化的地方(其实我们只能优化这一点,因为其他步骤逻辑基本没有优化空间)。确定解决方案在该界面中,【判断是否为新用户】的判断标准是用户是否有订单支付成功。因此,开发者想当然地根据用户ID去订单数据库中查询。我们订单主库的配置如下:这个配置还是比较豪华的。但是,随着业务的积累,主订单库中的数据已经超过千万级。虽然数据会定期迁移,但是订单量突破千万的周期越来越短了……(分库分表方案是时候提上日程了,这个场景分库分表的内容暂且不讨论)而且用户ID虽然是索引,但毕竟不是唯一索引。因此,查询效率比其他逻辑更耗时。通过简单的分析我们可以知道,其实我们只需要知道用户有没有支付成功的订单即可,而不关心支付成功的订单有多少。所以,这种场景显然适合使用Redis的BitMap数据结构来解决。在支付成功的逻辑中,我们简单的添加一行代码设置BitMap://说明:key表示用户是否有支付成功的订单标识//userId为long类型Stringkey="order:f:paysucc";redisTemplate.opsForValue().setBit(key,userId,true);通过这次改造,【判断是否为新用户】的核心代码在下单时不需要去查数据库,而是改为:BooleanpaySuccFlag=redisTemplate.opsForValue().getBit(key,userId);if(paySuccFlag!=null&&paySuccFlag){//不是新用户,业务异常}修改后,在测试环境中测试结果如下:StopWatch'NewcomerGroupOrderStopWatch':runningtime=82207200ns----------------------------------------------ns%任务名称-----------------------------------------------014113100017%调用风控系统接口010193800012%获取入团活动信息013965900017%获取用户基本信息014532800018%判断是否为新用户029401600036%生成订单并存入测试环境。并且数据库插入数据,所以是合理的。界面响应时间缩短31%!与生产环境相比,表演效果更加明显……继续跳舞!Thunderbolt这次的优化效果很明显,觉得CTO应该给我点业绩,不然扣我工资。啊~一边这么想,一边准备着生产环境的灰度发布。发布完版本,我准备来找葛优躺下好好休息,等测试妹子验证通过就下班了。然而,我躺下不到一分钟,测试妹子就走过来,紧张地对我说:“界面报错,请看一下!”什么?打开日志一看,顿时傻眼了。报错日志如下:io.lettuce.core.RedisCommandExecutionException:ERRbitoffsetisnotanintegeroroutofrangeatio.lettuce.core.ExceptionFactory.createExecutionException(ExceptionFactory.java:135)~[lettuce-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]atio.lettuce.core.ExceptionFactory.createExecutionException(ExceptionFactory.java:108)~[lettuce-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]atio.lettuce.core.protocol.AsyncCommand.completeResult(AsyncCommand.java:120)~[lettuce-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]atio.lettuce.core.protocol.AsyncCommand.complete(AsyncCommand.java:111)~[lettuce-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]atio.lettuce.core.protocol.CommandHandler.complete(CommandHandler.java:654)~[lettuce-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]atio.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:614)~[lettuce-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]…………位偏移不是整数或超出范围。这个错误提示已经很明显:我们的offset参数超出范围。为什么会这样?不禁开始思考:RedisBitMap的底层数据结构其实是String类型,而Redis对String类型最大限制为512M,也就是2^32次方字节……我靠!!!突然意识到,由于测试环境的历史原因,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台服务器。这很痛,也是值得的。然后放音乐,然后跳舞~~~作者:浪漫先生编辑:陶佳龙来源:https://juejin.im/post/6854573218322513933
