PHP读取CSV发放随机码出现重复发放的原因及解决方法
重复发码的核心缺陷
当前代码出现重复发码完全是并发竞态+无原子操作保障导致的,具体问题点:
- 整个「读全量CSV→取第一个码→移除首行→全量重写CSV」的流程没有任何互斥机制,不是原子操作。高并发场景下,多个请求会在同一时间窗口内读到完全相同的CSV内容,拿到同一个首行随机码,之后各自执行移除首行、重写文件的操作,最终多个请求会向不同用户发放同一个码。举个实际场景:请求A、B同时启动流程,都读到码
abc123在CSV第一行,A取走abc123后把移除了首行的内容写回文件;B根本感知不到A的操作,还是用自己内存里存的、首行是abc123的数据集重写文件,最终两个请求都把abc123发给了用户,同时A发完之后的文件状态还被B的写操作覆盖,还会额外丢码。 - 写文件时直接用
w模式打开,会瞬间清空原文件内容,如果此时有其他请求刚好执行读文件操作,会读到空文件或者半写入的损坏内容,进一步引发空码、丢码问题。
关于文件重写逻辑的说明
是的,当前代码在取走第一个码之后,会遍历所有剩余的随机码逐行重新写入CSV文件。如果CSV里存了几万、几十万条随机码,每次发1个码就要重写几万几十万行内容,磁盘IO开销极高,码量越大、并发越高,流程执行时间越长,竞态问题出现的概率就越高。
防重复&性能优化方案
临时兼容CSV存储的修复方案
如果暂时无法更换存储介质,必须先给整个文件操作流程加排他锁,保证同一时间只有一个请求能执行完整的读改写流程,核心调整点:
- 放弃「先读全量文件、再用w模式打开清空重写」的逻辑,改用
c模式打开文件(打开时不自动清空内容),通过flock加非阻塞独占锁,只有拿到锁的请求才能操作文件,拿不到锁的请求做短延迟重试,避免请求堆积。 - 拿到锁之后再读取全量内容、取码、截断清空文件、回写剩余内容,写完立刻释放锁,不要把后续的表单校验、用户通知这类业务逻辑放在锁区间内,尽可能缩短锁的持有时间。
参考实现代码:
$file = fopen($csv_relative_path, 'c'); // 尝试加非阻塞独占锁 if (!flock($file, LOCK_EX | LOCK_NB)) { fclose($file); // 这里可加10~50ms的短延迟后重试,不要直接返回错误 exit("系统繁忙,请稍后重试"); } $data = []; // 读取全量CSV内容 while (($line = fgetcsv($file)) !== FALSE) { $data[] = $line; } $code = ''; if (!empty($data)) { $code = array_shift($data)[0]; // 截断文件清空旧内容 ftruncate($file, 0); // 文件指针回到开头 rewind($file); // 回写剩余码 foreach ($data as $fields) { fputcsv($file, $fields); } } // 释放锁、关闭文件 flock($file, LOCK_UN); fclose($file); // 锁释放后再执行后续发码业务逻辑
注意:CSV加锁的方案性能上限极低,只适合低并发场景临时救急,高并发下不推荐使用。
高并发场景长期方案
要彻底解决性能和重复发码问题,必须替换CSV存储,选择天然支持原子操作的存储介质:
- 方案1:数据库存储
建单独的随机码表,包含主键ID、码值、发放状态(0未发放/1已发放)、发放时间、用户ID字段,给码值加唯一索引,给发放状态加索引。发码时直接用数据库原子更新语句拿码,比如MySQL 8.0+可执行:
数据库本身会保证这条更新语句的原子性,不会出现两个请求同时拿到同一条记录的情况,性能远高于文件操作,同时可以很方便的统计发码进度、查询码的发放记录。UPDATE random_codes SET status = 1, user_id = ?, grant_time = NOW() WHERE status = 0 LIMIT 1 RETURNING code; - 方案2:Redis队列存储
提前把所有随机码批量推入Redis的List结构,发码时直接执行LPOP操作弹出队首的码即可。LPOP是Redis原生原子命令,哪怕十万级并发也不会返回重复值,性能比数据库更高,非常适合大流量高并发场景。注意开启Redis的RDB+AOF持久化避免服务重启丢码,同时配置队列长度告警,库存不足时及时补码。 - 兜底方案:不管用哪种存储,都要在用户发码记录表上对「用户ID+活动ID」加唯一索引,避免同一个用户重复领码;如果是码只能用一次的场景,对「码值」加唯一索引,从数据库层面挡住重复写入,做最后一层容错。
内容的提问来源于stack exchange,提问作者user1190132
相关产品推荐
相关产品推荐

