一.前言
在某双十一期间,带着一个临时组件的团队启动了一个商品促销活动的项目。然而,上线并不顺利,同一天出现了性能问题,界面超时,用户无法打开网页,最终无法暂时脱机。重建后台的核心代码花了三天两夜的时间,以保持活动的进行。
回顾时间记录,从5月31日晚上08:25开始,开始为上线做准备,发现异常现象,分析原因,重构代码,并于6月2日23:54离开公司。持续作战51小时29分钟,中间睡眠时间不到5小时。
这一波刚过去了,一波未平另一波又起,由于活动的奖励丰厚,大批羊毛党闻风而至,甚至有人做脚本,并公开在某宝上出售,严重影响了正常用户薅羊毛。
一位顾客的反馈说:我们别谈收集羊毛了,现在整只羊都被他们用脚本抢走了!
在接下来的几天里,我们将不得不与脚本以及写脚本的幕后高人斗智斗勇,直到活动结束。
这篇文章对此斗法做了深入的回顾,在未来的项目中能够快活点点。
二、该项目看似完美的总结
当我们回顾项目过程时,我们可以发现许多问题点,例如:
人力短缺,需求过于复杂,开发和测试工作量大。
前端和后端开发和测试从其他团队中临时借调,并且不熟悉当前项目的业务和技术。
跨团队临时团队,职责界定不明确,项目控制不严格.
开发人员不熟悉项目中使用的技术,不需要对原始项目成员进行CodeReview。
试验太仓促,压力测量方案的设计不合理。
列出问题后,你很快就能逐一写出改进点。
从企业层面加强项目总体安排,避免重复项目,并将资源投入多项重点活动。
加强团队的能力培养,总结文件,为新人学习。
当您遇到CodeReview的问题时,对于核心代码,项目经理协调高级开发以帮助解决这些问题。
临时小组的职责将明确界定,小组负责人将明确沟通。
严格控制测试质量,测试具有在线否决权。
这些结论似乎一点问题都没有,列出了问题,列出了改进点,甚至可以作为模板。这就是我们最终的结果吗?
当然,说把问题的前提看作是问题的原因并不是错误的。
让我们看两个表达式。
下一次我们将成立一个经验丰富的项目团队,以避免质量问题。
下一次我们面对一个临时的、缺乏经验的项目团队时,如何避免质量问题。
这两种表达方式有什么区别?
前一种说法是由于我们"Team"的原因,导致了这个质量问题,所以我们必须解决"Team"问题。
后一种说法是由于我们的团队是临时形成的,我们的开发、测试人员不熟悉新项目的业务和技术,在这个前提下,会出现质量问题,那么在这个前提下,如何避免质量问题呢?
临时拼凑的团队,缺乏经验不是问题的根源,它们是问题的前提,这是客观存在的。
就像我们说,当我们解决一个问题时,最快的办法是,如果我们不解决问题,我们就必须解决问题。
在这里,如果我们不解决问题,解决问题的团队就会解决问题。
正是由于这种误解,一旦我们出现项目质量问题,我们就会多次向我们的团队投入协作,或者我们的项目时间紧迫,那么下一次改进就会结束。
这样一个普遍的答案,似乎是完全正确的,往往是不可能站住脚的。
显然项目时间紧迫,新团队合作经验不足已经客观存在,没有它就没有问题,如何能作为问题本身来解决。
1.质量问题的主要原因
带着这个前提,我们再回头看前面的总结,其实就能过滤出真正有价值的点了。我们也可以问,这个问题是不可避免的,但为什么我们的性能问题在项目过程中没有暴露出来呢?
三个角度:
从项目的角度来看,没有严格遵守项目过程,特别是最后的测试任务很紧,当错误越多,测试报告就会很快给出。
从开发角度,没有找熟悉业务和技术的同学做CodeReview。
从试验的角度看,压力测量方案的设计不合理,不符合实际情况。
逐个分析。
前面提到事故是后台的性能问题,从项目角度,就算流程严谨也没法暴露出性能问题,特别是在项目过程中,已暴露的风险是前端人力不足,中间加了人手,从功能的角度,后端进度完全正常。
从发展的角度来看,我没有提到缺乏开发经验,也没有推卸责任,这和我们作为一个临时团队在企业中缺乏经验一样,也是存在的客观前提。当你接触到新项目,使用新技术时,经验缺乏的问题就一定会存在。
问题是在自身经验不足时,如何去完成任务,那么和熟悉业务和技术的同学做CodeReview是主要的手段。
从测试的角度来看,功能测试并不是一个问题,但是性能相关的压力测量方案是有问题的,并且没有导致一开始就面临问题。最初的压测方案是开发只出接口和参数文档,直接丢给测试去压,现在看来,这是错误的。
因此,这个质量问题的要点概括如下。
下一次,当我们面对一个临时的、缺乏经验的项目团队时,开发人员需要注意高流量的业务需求:
让熟悉业务和技术的同学帮忙做CodeReview。
设计出符合业务场景的压测方案。
这两点就可以落地了,这也不是说项目管理上没有改进的,而是优先保证这两点,能更有效的降低风险。
CodeReview的技巧在这里没什么可说的,让我们谈谈我们在压力测量计划中所做的几次改进吧。
2.三次压测改进
单用户,单接口,双机压测
随机用户,多接口,全量压测
随机用户,功能分组接口,全量压测
最开始压测方案是用一个用户,两台服务器,一个缓存分片做压测,然后简单的用服务器QPS的均值乘以线上部署机器数量当作压测结果。
如果此场景位于下图的左侧,自然可以在链接上调用服务器,以便同时灵活地扩展。
但是,如果右边的场景中的呼叫链接存在瓶颈,例如数据库是一个节点,不能扩展,那就是一个问题。
类似地,这个项目的问题是Redis已经成为一个单节点瓶颈。此外,由于用户id是固定的,缓存可能会被重用,因此很难测试频繁创建缓存的场景。
对系统重构后的压缩测量方案进行了改进,通过随机用户ID、批轮询接口和测试环境的弹性扩展完全模拟了在线部署环境。
通过增加降级开关,暂时关闭输入方式、风险控制、及时性检查等,从而使压力测量的要求贯穿整个主要过程。
然后,在此方案的基础上,通过接口分组和对适当数据的伪造,编写一个接近真实调用行为的脚本,并再次进行压缩。
在执行过程中,还经历了从开发到提供数据,测试全部责任;测试领导,开发参与,再到开发领导,测试辅助过程。
因此,压力测量方案越来越接近真实场景,压力测量的结论自然更加可信。
3.高并发情况下的设计
以往对系统设计不合理的讨论导致了这一性能问题,分析了产生这一问题的根本原因。
首先要理解的是,Redis集群是由多个片段组成的,而将数据块写入哪个片段是由键的哈希值离散化的。
例如,我们需要在Redis中缓存一批用户信息,并可以通过ID访问它。
如果您使用Redis附带的Hash表结构,如下所示:
Save:redis.hset("userMap",ID,userInfo)
阅读:redis.hget("userMap",ID)
好吧,因为键是一个固定的userMap,这意味着所有的用户信息都写在一个片段中。
对于通常的分布式系统的设计,最基本的原则之一是使流量通过集群机器尽可能均匀地分布。
固定密钥无法利用分布式,如果并发性很高,则允许碎片抵抗所有通信量,如果用户数为数十万,一次读取所有数据的操作将成为一场灾难。
实际设计时,直接把整个Redis集群当作一个Hash表的方式更加高效。
存:redis.set("userMap"+ID,userInfo)
读:redis.get("userMap"+ID)
这里的key="userMap"+ID,ID不同key就被离散了,请求会集群平摊,从而充分发挥分布式系统的性能。
三、黑产和羊毛党的问题
在项目上线后另一个没重视的问题出现了,那就是大量的黑产和羊毛党出现,活动奖励全被这些用脚本的人占据了。
对黑产的事前考虑太少了,仅做了简单的风控校验,根本检测不足异常用户,导致黑产可以通过脚本大量刷接口。
这里的经验有两点:
对包含现金、现金等价物或高价值奖励的活动,要有面对黑产的心理预期。
在大公司,专业的事情找专业的人做,基于业务场景,提前跟风控团队沟通好。
对于第一点,基本上只要值点钱的活动,黑产肯定跑不了,空手套白狼,抢到就是赚到,不妨想想如果你是黑产,结合下业务场景,你会怎么来刷自己的系统。
基于第一点,公司没有风控团队那就只能自己做了,而一般上点规模的公司都有自己的风控团队,利用好现成资源。
风控主要考虑两方面:
有风控团队的,接入他们的通用风控模型。
针对项目的业务场景,定制化一些风控模型。
通用风控模型基本是通过新老账号、异地登录、人机识别等等用户行为建立的用户画像,通过离线计算和实时校验来处理。
定制化模型视情况而定,比如拉一个单独的小黑户,放进去的用户不能参与这个活动等等。
被拦截的用户一般是走验证码或直接拉黑,对于后者,别忘了和客服的妹子们打好招呼,准备下话术应对客诉。
四、结语
最后总结下项目的经验。
首先是前提:
当你的面前的是一个临时组建,对现在项目经验不足的项目团队时。
当你面临一个大流量,包含现金或等价物的活动时。
请务必做好这三点:
找熟悉本项目的业务和技术的开发参与方案的设计和CodeReview。
请开发主动参与压测任务,设计压测方案,注意尽可能模拟真实场景。
做好应对黑产的心理准备,直到大促活动结束。
来自于,一个连续加班51小时29分,被用户吐槽整只羊都被人家牵走了的开发。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.