当前位置: 首页 > 后端技术 > Java

因为BitMap,白白用了8台服务器,,

时间:2023-04-01 21:48:12 Java

最近因为加了一些风控措施,新人团单接口的QPS和TPS下降了5%~10%左右,这还不错!首先简单介绍一下【新人团】活动:业务介绍:新人团顾名思义,是由新用户发起的加入团。如果拼团成功,系统会自动奖励新用户一张平台优惠15.1元的off券。这相当于无门槛折扣。每个用户只有一次机会。新人团活动最大的目的主要是吸引新人。新用户判断标准:是否有支付成功的订单?不是新用户:新用户。目前问题:由于此类活动折扣力度大,容易被羊毛党、黑货盯上。为此,我们完善了订单风控体系,让黑货无处遁形!但是由于需要同步调用风控系统,导致整个下单接口的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'StopWatchfornewcomergrouporderorder':runningtime=1195896800ns--------------------------------------------ns%任务名称--------------------------------------------014385000021%调用风控系统接口010481800010%获取入团活动信息013989200015%获取用户基本信息028314600030%判断是否为新用户028726200024%整个界面在测试环境下执行时间1.2s左右生成订单入库.最耗时的步骤是【判断是否为新用户】的逻辑。这是我们重点优化的地方(其实我们只能针对这一点进行优化,因为其他步骤逻辑基本没有优化空间)。确定解决方案在该界面中,【判断是否为新用户】的判断标准是用户是否有订单支付成功。因此,开发者想当然地根据用户ID去订单数据库中查询。我们订单主库的配置如下:这个配置还是比较豪华的。但随着业务的积累,订单主库中的数据已经超过千万级。虽然数据会定期迁移,但是订单量突破千万的周期越来越短了……(分库分表方案是时候提上日程了,这个场景分库分表的内容暂且不讨论)而且用户ID虽然是索引,但毕竟不是唯一索引。因此,查询效率比其他逻辑更耗时。通过简单的分析我们可以知道,其实我们只需要知道用户有没有支付成功的订单即可,而不关心支付成功的订单有多少。所以,这种场景显然适合使用Redis的BitMap数据结构来解决。在支付成功的逻辑中,我们简单的添加一行代码设置BitMap://描述:key表示用户是否有支付成功的订单标识//userId为long类型Stringkey="order:f:paysucc";redis模板。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%生成订单存入测试环境下单时间有变成0.82s,主要性能损失在生成订单和入库这一步,涉及到事务和数据库插入数据,所以比较合理。接口响应时间减少31%!比起制作环境的表演效果,表演效果更明显……还有跳舞!这次的晴天霹雳优化效果很明显,心想CTO应该给我点业绩,不然扣我工资~一边这么想着,一边准备生产环境的灰度发布。发布完版本,我准备来找葛优躺下好好休息,等测试妹子验证通过就下班了。然而,我躺下不到一分钟,测试妹子就走过来,紧张地对我说:“界面报错,请看一下!”什么?打开日志,我顿时傻眼了。报错日志如下:io.lettuce.core.RedisCommandExecutionException:ERRbitoffsetisnotanintegeroroutofrangeatio.lettuce.core.ExceptionFactory.createExecutionException(ExceptionFactory.java:135)~[lettuce-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]在io.lettuce.core.ExceptionFactory.createExecutionException(ExceptionFactory.java:108)~[lettuce-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]在io.lettuce.core.protocol.AsyncCommand.completeResult(AsyncCommand.java:120)~[lettuce-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]在io.lettuce.core.protocol.AsyncCommand.complete(AsyncCommand.java:111)~[lettuce-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]在io.lettuce.core.protocol.CommandHandler.complete(CommandHandler.java:654)~[lettuce-core-5.2.1.RELEASE.jar:5.2.1.RELEASE]在io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:614)~[lettuce-core-5.2.1.RELEASE.jar:5.2.1。RELEASE]…………bitoffsetisnotanintegeroroutofrange。这个错误提示已经很明显地表明:我们的偏移量超出范围。为什么会这样?不禁想: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台服务器。这很痛,也是值得的。然后放音乐,然后跳舞~~~来源:r6a.cn/dNTk