前言

最近,因为增加了一些风控措施,导致新人拼团订单接口的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去订单数据库中查询。我们的订单主库的配置如下:

这配置还算豪华吧。然而随着业务的积累,订单主库的数据早就突破了千万级别了,虽然会定时迁移数据,然而订单量突破千万大关的周期越来越短……(分库分表方案是时候提上议程了,此次场景暂不讨论分库分表的内容)而用户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 range
at 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台服务器。痛并值得。接着奏乐,接着舞~~~

来源:https://juejin.im/post/6854573218322513933

关注微信公众号:互联网架构师,在后台回复:2T,可以获取我整理的教程,都是干货。

猜你喜欢

1、GitHub 标星 3.2w!史上最全技术人员面试手册!FackBoo发起和总结

2、如何才能成为优秀的架构师?

3、从零开始搭建创业公司后台技术栈

4、程序员一般可以从什么平台接私活?

5、37岁程序员被裁,120天没找到工作,无奈去小公司,结果懵了...

6、滴滴业务中台构建实践,首次曝光

7、不认命,从10年流水线工人,到谷歌上班的程序媛,一位湖南妹子的励志故事

8、15张图看懂瞎忙和高效的区别

9、2T架构师学习资料干货分享

这月绩效差点没了!搞了一个“新人拼团”活动遇到黑产,搭进去了8台服务器......相关推荐

  1. mumu血压计母亲节“拼团”盛大开幕,百度和厂家创造一个双赢的局面

    在BAT的互联网时代.似乎不论什么新兴产品和服务都不能脱离BAT的支持,作为中国互联网体量最庞大的三家企业.BAT代表的是资源优势.用户优势.品牌优势.因此,一旦脱离BAT的支持,想迅速做大无异于天方 ...

  2. 拼团小程序源码_纯小白如何做一个摄影电商拼团小程序?

    过去,摄影行业都是线下实体店为主,宣传手段单一.推广效率差,客户复购率更是不怎样.随着微信小程序的出现,各个摄影行业商家开始通过小程序来进行推广,连接线上线下场景,这样便能触达更多消费者. 小程序拥有 ...

  3. 第一份工作,我差点没转正

    大学毕业,我的第一份工作,在百度. 这第一份工作,我差点没能转正. 绝大部分baiduer可能都不知道,百度在中关村软件园的信威大厦还秘密办公过几年,信威大厦里蛰伏着有啊与百度hi两个关系紧密的&qu ...

  4. 搞了一个星期的扫码收款, 总结感悟

    正好明天周末, 搞了一个星期的支付宝和微信的付款码支付, 今天终于有了模样 支付宝:   支付宝目前符合公司这种代替商家收款同时款项要打到商家自己的账户上需要使用第三方授权的形式, 找到了文档和SDK ...

  5. 4天4夜渡劫成功,解决10月1项目上线遇到的一个Mysql大坑,导致项目无法正常访问

    经历4天4夜解决10月1项目上线遇到的一个Mysql大坑,导致项目无法正常访问 一.问题重现 二.排查问题 三.解决问题 四.关于Mysql这两个参数的作用以及解释 五.总结 标题是不是惊讶到你了,但 ...

  6. 进公司两个月了还没上手项目_27个“经验证且易于上手”的赚钱在线业务创意

    进公司两个月了还没上手项目 Are you looking for online business ideas to make extra income on the side? The intern ...

  7. 入职P8几个月,一直没啥事,阿里花大价钱招我干嘛的?

    有网友在某互联网论坛上吐槽:入职P8几个月,一直没啥事,阿里花大价钱招我干嘛的! 有网友调侃道:p8是要自己找事情的,兄弟3.25在向你招手.所以没p7带你吗?还有前阿里网友说:p8必须要无中生有了, ...

  8. 百度搞了一个“开发者搜索”

    (给程序员的那些事加星标) 说起百度搜索,最受网民诟病的是其医疗广告部分.(此处省略一千字) 最近听说百度搞了一个完全无广告的搜索引擎:开发者搜索. 打开瞧一瞧,首页并无百度 Logo,如果隐藏网址信 ...

  9. java开局降落伞落地拿枪打_绝地求生:1月最新的跳伞技巧,一个开局捡空投,一个落地快4秒...

    原标题:绝地求生:1月最新的跳伞技巧,一个开局捡空投,一个落地快4秒 大家好我是毅哥讲游戏,雪地地图上线后很多玩家回归了游戏,但身边不少朋友问我:怎么你以前的波浪跳伞攻略不管用了?嘿嘿~其实蓝洞已经对 ...

  10. 万万没想到,一个技术方案帮实习生追到了运营妹子!

    上回说到,公司的新业务增长速度放缓,运营部门提出要发展短视频来促进更快的业务增长,而我也因为提前准备好了技术预案再一次得到老板的赞赏(了解详情请看上集:一个技术预案,让老板当场喊出了奥利给 ). 既然 ...

最新文章

  1. 指定位数%-m.nf与%m.nf的异同
  2. android 软键盘显示和隐藏造成页面跳闪问题的解决方案
  3. sqlite3数据库最大可以是多大?可以存放多少数据?读写性能怎么样?详述
  4. 点击显示隐藏盒子函数
  5. WPF之DataGrid
  6. Java英雄:丹·艾伦
  7. 计算机科学与技术 天涯,计算机科学与技术专业
  8. R 语言 Windows 环境 安装与Windows下制作R的package--Rtools
  9. 如何在Linux上运行Windows软件?
  10. 说说大型高并发高负载网站的系统架构 (转)
  11. cocos2d-x的CCSequence与CCRepeatForever的混用
  12. Atitit.atijson 类库的新特性设计与实现 v3 q31
  13. 激光雷达+imu_大疆览沃浩界(Livox Horizon)激光雷达测评
  14. oa项目经验描述_项目执行简历中的项目经验怎么写
  15. 时隔两年之后,证监会官网再次更新瑞丰银行的IPO进度。
  16. C#与西门子1500通讯案例——基于S7.net+
  17. HC-05蓝牙配对AT指令
  18. 什么叫系统后门?后门与漏洞有什么区别?
  19. LArea 微信端 地址选择
  20. 录屏工具Gif软件 附带源码

热门文章

  1. 音乐艺考生如何提高视唱练耳技巧
  2. Linux命令:修改文件权限命令chmod、chgrp、chown详解
  3. VR AR体验或成2017圣丹斯电影节“新主角”
  4. 1.3_对于Servlet规范的一点理解
  5. django-spaghetti-and-meatballs 0.2.0 : Python Package Index
  6. asp.net mvc 如何调用微信jssdk接口:分享到微信朋友(圈)| 分享到qq空间
  7. 「leetcode」47.全排列 II【回溯算法】详细图解!
  8. poj 1422 Air Raid 最小二分匹配 基础
  9. 如何裁剪、合并视频?
  10. 机器视觉的发展现状和前景分析