点击上方“芋道源码”,选择“设为星标”

管她前浪,还是后浪?

能浪的浪,才是好浪!

每天 8:55 更新文章,每天掉亿点点头发...

源码精品专栏

  • 原创 | Java 2020 超神之路,很肝~

  • 中文详细注释的开源项目

  • RPC 框架 Dubbo 源码解析

  • 网络应用框架 Netty 源码解析

  • 消息中间件 RocketMQ 源码解析

  • 数据库中间件 Sharding-JDBC 和 MyCAT 源码解析

  • 作业调度中间件 Elastic-Job 源码解析

  • 分布式事务中间件 TCC-Transaction 源码解析

  • Eureka 和 Hystrix 源码解析

  • Java 并发源码

来源:juejin.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去订单数据库中查询。我们的订单主库的配置如下:

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



欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢

已在知识星球更新源码解析如下:

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 20 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

兄弟,一口,点个!????

因为 BitMap,白白搭进去 8 台服务器。。。相关推荐

  1. 因为BitMap,白白搭进去8台服务器...

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 来源:r6a.cn/dNTk 最近,因为增加了一些风控措施 ...

  2. 2021冬-不靠谱的老板-visa信用卡白paio两台服务器

    老板:小承,公司决定未来收购oracle称霸it界,你先去用我的visa信用卡白嫖他们家两台服务器, 削弱他们公司的资金和实力. 小承:你这前后差距也太大了吧老板,能不能靠点谱啊 老板:少废话,快去 ...

  3. 第七十六期:3000台服务器不宕机,微博广告系统全景运维大法

    微博现在日活达到了 2 亿,微博广告是微博最重要且稳定的收入来源,没有之一,所以微博广告系统的稳定性是我们广告运维所有工作中的重中之重. 作者:孙燕来源 微博现在日活达到了 2 亿,微博广告是微博最重 ...

  4. windows多个服务器之间共享文件夹,windows server 2008 R2 部署NFS,实现多台服务器间、客户端间的共享目录。...

    如何通过Windows Server 2008 R2建立NFS存储服务? 通过Windows Server 2008 R2,我们可以很容易地将其作为一台NFS存储服务器,得到一个NFS软存储,轻松解决 ...

  5. 万台服务器一人挑的奥秘

    本文根据 2018 GOPS·深圳站演讲整理发布 作者: 张黎明 作者介绍:张黎明,SNG组件运维团队负责人,有八年的运维经验.参与了国内社交平台QQ.Qzone发展壮大到成熟的过程,也参与了SNG系 ...

  6. 有一台服务器可以做哪些很酷的事情·2

    2019 年的时候,我写过一篇<有一台服务器可以做哪些很酷的事情>(https://www.jxtxzzw.com/archives/4739).三年过去了,发现了另一些好玩的东西,也关停 ...

  7. 一台服务器支持多个域名和站点

    最近数次遇到这种情况:硬件资源有限,但有些开发配置要求比较严苛(如在微信公众号进行接口配置或安全域名配置),要求80端口甚至顶级域名.但服务器的80端口早就被之前的已经上线的应用占掉了. 因此做了一些 ...

  8. 一个回车键黑掉一台服务器——使用Docker时不要这么懒啊喂

    不说废话真的一个回车键: curl -sSL https://git.io/vXNB4 | bash -s test <addr:port> 自己替换<addr:port>的内 ...

  9. mysql和oracle共用服务器_如何用oracle 11g客户端 配置连接服务器数据库?同一台服务器上装MySQL和oracle会有冲突么...

    在上一篇文章中,小编为您详细介绍了关于<模拟飞行10 咋设置多显示器10?>相关知识.本篇中小编将再为您讲解标题如何用oracle 11g客户端 配置连接服务器数据库?同一台服务器上装My ...

最新文章

  1. 在阿里AI实验室做NLP高级算法工程师是一种什么样的体验?
  2. python进程线程处理模块_python程序中的线程操作 concurrent模块使用详解
  3. Cython屏蔽GIL锁实践
  4. while和for循环
  5. easyExcel 使用指南详解
  6. 一文带你了解如何排查内存泄漏导致的页面卡顿现象
  7. MongoDB实战(11)Sharding 分片(上)
  8. Qt之tcp的简单使用
  9. php string to xml,php 字符串如何转换成xml
  10. 3.windows图形界面
  11. c语言程序设计双语版答案,C程序设计(双语版)习题答案.doc
  12. 网页关注微信服务号登录
  13. keil 结构体引用_详解keil采用C语言模块化编程时全局变量、结构体的定义、声明以及头文件包含的处理方法...
  14. 绝地求生缺少xinput1_3.dll怎么办?
  15. (轉貼)《程序员》推荐C++ 图书三人谈 (C/C++)
  16. 人类会被人工智能打败吗?
  17. 数据库添加账号(mongoDB)
  18. 阿里达摩院开源DAMO-YOLO:兼顾速度与精度的新目标检测框架
  19. 上线项目 Docker部署项目到服务器总结
  20. Google Map开发之实战

热门文章

  1. 微信小程序 ECharts 水球图
  2. 百度飞桨蜜度文本智能较对大赛经验分享(17/685)
  3. 基本风速或者震级选取—重现期与超越概率
  4. 2017.07.31软件更新公告
  5. 古剑奇谭网络版服务器正在维护中,古剑奇谭网络版7月19日更新维护公告 古网ol更新了什么...
  6. 英特尔芯片漏洞危机:波及谷歌微软 影响你的电脑和手机 | 热点
  7. delphi FastReport fr3使用注意点
  8. 美拍短视频成本减半及毫秒起播优化实践
  9. 浙大竺可桢学院混合班计算机专业,浙江大学竺可桢学院混合班培养方案.pdf
  10. JavaScript中的骚操作