“砍一刀”算法怎么防止被刷破产?90% 的人都挂在了“最后 0.01 元”
写在开头
今天我们聊聊疯狂的“病毒营销引擎”——拼多多的“砍一刀”。
有个粉丝去面拼多多(Temu 部门),二面被问了一个非常硬核的场景题:
面试官: “我们要上线一个送手机的‘砍一刀’活动。手机成本 2000 块。 面对海量并发和黑产刷单,你怎么设计算法和架构,保证用户能领到手机,但公司绝不会亏本?”
粉丝答:“用 Redis 原子减,每次随机砍个金额……” 面试官打断:“随机?万一前 10 个人每人砍 200,手机不就白送了?或者黑产搞 1000 个机器号来砍,我们岂不是要破产?”
这道题考的不是简单的减法,而是“概率论控制成本”与“人性博弈”。 今天 Fox 就带你拆解这套让全中国人民“又爱又恨”的系统。
一、 核心算法:不是“随机”,是“看人下菜碟”
首先要明确:“砍一刀”绝不是真随机!它是一个基于“用户价值(User Value)”的动态定价游戏。
如果手机成本 2000 元,平台愿意付出的获客成本(CAC)是 200 元。
算法的核心目标: 必须让你拉够价值 200 元的“人头”,才能给你手机。
算法策略:价值系数 + 芝诺悖论
1. 每一刀砍多少,取决于“谁在砍”
你以为金额是随机的?错!算法会根据用户画像计算权重:
新用户(贵客): 权重 10.0。一刀下去砍 20 元。(激励你继续拉新) 老用户(熟客): 权重 0.1。一刀下去砍 0.2 元。(因为你已经没啥拉新价值了) 黑产号(垃圾): 权重 0.001。一刀下去砍 0.01 元。
2. 永远砍不完的“最后一刀”
当进度条达到 99% 时,算法进入“收敛模式”。 剩余金额不再是“钱”,而是一个数学极限。 假设还差 1 元,算法会把这 1 元切分成无限份:0.5 + 0.2 + 0.1 + ... 无论你拉多少人,金额永远无限逼近 0,却很难等于 0。这就是数学上的“芝诺悖论”。
// 伪代码:基于权重的动态砍价
public BigDecimal cut(User helper){
// 1. 获取帮砍人的画像权重 (0.01 ~ 10.0)
BigDecimal weight = UserProfileService.getWeight(helper.getId());
// 2. 计算基准金额 (剩余金额 * 衰减系数)
BigDecimal base = remainAmount.multiply(DECAY_RATE);
// 3. 最终金额 = 基准 * 权重
// 4. 兜底逻辑:最小不能少于 1 分钱(对应存储的 10000 微)
return calculate(base, weight);
}
二、 架构设计:高并发下的“防刷墙”
算法保证了不亏,架构要保证“不崩”。
1. 金额存储:Integer 优于 BigDecimal
坑点: 在 Redis 里存浮点数(String)进行计算,慢且有精度风险。
解法:“去小数点化”。将金额单位转为 “微”(1 元 = 1,000,000 微)。 Redis 里存的是 Long 类型的整数,Lua 脚本执行 DECRBY 是纳秒级的,且绝对精准。
2. 解决“大 V 热 Key”问题
如果某个大 V 分享了链接,瞬间涌入 100 万人帮砍,Redis 单个 Key 会被打爆。
解法:“本地缓存(LocalCache)前置”。
对于超级热单,在应用服务器(JVM)层做一层缓存,拦截 90% 的无效查询。 写入时利用 MQ 削峰填谷,串行处理砍价请求,前端只显示“砍价中...”。
三、 核心风控:与黑产的“图灵博弈”
这是面试官最想听的部分。如果 1000 个机器人瞬间把 2000 元砍完了,算法再好也没用。
1. 设备指纹 + 行为生物学
不要只看 IP。要看陀螺仪、按压面积。真人在砍价时,手指按压屏幕的面积是变化的,手机会有轻微物理抖动;而脚本的数据是死板的。 策略: 识别为机器人的,执行“风控降权”——前端提示砍价成功,后台实际扣减金额为 0。
2. 终极杀招:强制图灵测试
现在的高级云控脚本已经能模拟陀螺仪了。怎么办?
解法: 在砍价的关键节点(如最后 1 元、最后 0.01 元),强制弹出“滑动拼图”或“选字验证码”。 这一招能直接废掉 99% 的自动化脚本,因为打码平台的响应速度赶不上用户的并发速度。
四、 致命争议:为什么最后变成了“金币”?
很多人骂拼多多:“明明显示还差 0.01 元,我拉了人,怎么变成了送金币?” 这其实是技术与法务的“保命策略”。
技术真相: 数据库里的金额已经拆分到 1 微(0.000001 元) 了,但前端只能显示 2 位小数。继续砍金额,用户会发现数字“不动了”(看起来像 Bug)。
产品黑魔术——“单位置换(Unit Switch)”:为了让游戏继续,系统会把“金额模式” 切换为 “道具模式”。
还差 0.01 元 -> 抽到“10 个金币” -> 100 金币换 0.01 元。 还差 1 金币 -> 抽到“1 个碎片” -> 10 个碎片换 1 金币。 本质: 通过不断贬值货币单位,把进度条无限拉长,让你永远觉得“只差一点点”。
五、 面试标准回答模板
下次面试被问到“设计砍一刀”,请按这个套路输出:
“面试官,这个系统的核心不是随机算法,而是 ‘基于 CAC(获客成本)的动态定价模型’。
算法层: 利用 ‘用户价值权重’ 和 ‘芝诺悖论’,确保金额无限收敛但不为零。 存储层: Redis 中使用 ‘Long 类型(单位:微)’ 存储金额,避免浮点数精度问题,提升 Lua 执行效率。 风控层: 组合使用 ‘生物探针’(陀螺仪)和 ‘关键节点验证码’(图灵测试)拦截脚本。 产品策略: 在精度耗尽时,启用 ‘单位置换’(切金币/碎片),既规避了技术尴尬,又合法地延长了用户留存。”
写在最后
“砍一刀”的本质,不是技术,而是对人性的精准计算。 技术保证了“平台不亏”,而心理学保证了用户“觉得能赢”。 当你理解了这一点,你写的代码就不再是冷冰冰的逻辑,而是商业的引擎。