需求
我每天执行一个定时任务,把一个具有千万条数据的日志分割成一些小的日志文件;
然后启动多个PHP进程,分别对这些小日志文件中的每行数据进行处理,处理过程是这样的:每个PHP进程从各自负责的日志文件中逐行读取,然后根据该行中的一个标识去数据库中查找是否有了该记录,如果没有则插入该行,如果已经存在了就更新(该行的数据与数据库中的数据进行合并);
引发的问题
这样就出现了一个问题,就是可能存在多个PHP进程对同一条数据都在做处理,如果用Mysql锁机制可以避免这个问题,但是这样的话,就出现另一个问题,就是MySQL的高并发瓶颈,因为我是多个PHP进程同时在进行,一个进程要等待另一个进程解锁后,才能操作数据库。
这样我前面所做的分割日志、启动多个进程这些操作就失去意义了,因为我在前面做日志分析时,启的进程再多,运行的再快,到最后全都会卡在高并发操作数据库这个瓶颈上,不知道大家对这类问题是怎么处理的?
版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。
锁之所以变成瓶颈,根因不是 MySQL 慢,而是"多个进程可能同时写同一行"这件事本身。那就从源头掐掉它:拿行内标识做一次 hash(crc32(id) % N,或者对标识取 MD5 再取前几位),把日志文件切成 N 份,起 N 个进程,一个进程只吃一个分片。
同一标识永远落到同一进程,进程之间就是逻辑互斥的——不是靠锁互斥,而是靠路由天然隔离。行锁、SELECT ... FOR UPDATE、GET_LOCK 全都不需要了,数据库从"协调者"退回成"干活的",这才是多进程该有的样子。
分片有两个细节:切分时就按标识排序后再轮询落盘,比按时间戳切更均匀;N 别拍脑袋定,看 MySQL 的并发写入能力和磁盘 IO 能扛多少再定。
即便分片干净了,一行一次 INSERT 也是浪费。日志统计天然是批量语义,攒 500~2000 行发一条:
INSERT INTO log_stat (biz_id, cnt, ts)
VALUES (?,?,?),(?,?,?) /* ... */
ON DUPLICATE KEY UPDATE
cnt = cnt + VALUES(cnt),
ts = VALUES(ts);
一次网络往返、一次解析、一次提交解决几百行,配合 rewriteBatchedStatements=true 和调大 innodb_buffer_pool_size,写吞吐通常能上一个数量级。批大小不是越大越好,太大容易撞 max_allowed_packet,单事务持锁时间也拉长,边压边调,找拐点。
如果写入在时间上扎堆(定时任务集中跑的典型症状),中间垫一层 Redis 或内存队列:
HINCRBY 进 Redis 的 Hash,或推进队列;业务能接受秒级延迟就够用了,这点延迟换的是数据库看到的流量从"瞬时尖峰"变成"平缓水流"。用 Redis 计数时务必用服务端累加(INCRBY/HINCRBY),别读出来在客户端加,否则又绕回读-改-写。
ON DUPLICATE KEY UPDATE 靠它判重;没有唯一键,这条语句会退化成纯插入,数据直接翻倍。cnt = cnt + VALUES(cnt)、ts = GREATEST(ts, VALUES(ts)),累加和取最大值都在存储引擎里原子完成,省掉"查出来—算一遍—写回去"三步,也省掉中间那次行锁等待窗口。总有些场景按标识切不干净,比如多来源写入、要跨分片汇总。这时再用锁,守两条底线:
GET_LOCK('biz_id:12345', 2) 这种按业务键命名锁,而不是锁表;顺带提醒,GET_LOCK 绑定的是连接而不是事务,连接池复用时容易踩坑,记得配超时参数并在用完后 RELEASE_LOCK。
推进顺序上,先分片消竞争,再批量化降往返,然后异步削峰,最后才轮到表结构和锁参数的微调。前几步做扎实了,后面那点锁的优化往往就没那么重要了。
以下是处理PHP多进程与MySQL高并发瓶颈的一些常见方法:
数据库层面
连接池:使用连接池技术管理数据库连接,避免频繁创建和销毁连接,提高连接的复用率,减少连接建立的开销。
优化查询:分析查询语句,使用索引优化查询性能,避免全表扫描。还可通过缓存查询结果,减少重复查询数据库的次数。
数据库集群:采用主从复制集群,主库负责写操作,从库负责读操作,实现读写分离,分担读压力。也可使用分布式数据库架构,如MySQL Cluster,将数据分散存储在多个节点上,提高并发处理能力。
PHP多进程层面
进程管理:合理控制多进程的数量,避免进程过多导致系统资源耗尽。可使用进程池来管理进程,根据系统负载动态调整进程数量。
共享内存:利用共享内存来存储一些公共数据,避免多个进程重复读取相同的数据,提高数据访问效率。
消息队列:引入消息队列,如RabbitMQ、Kafka等,将一些非实时的任务放入消息队列中异步处理,减轻数据库的并发压力。
系统层面
优化服务器配置:合理配置服务器的CPU、内存、磁盘等资源,确保系统能够支持高并发处理。如增加内存、使用SSD硬盘等。
网络优化:优化网络设置,减少网络延迟和丢包率。可通过升级网络设备、优化网络拓扑结构等方式来实现。
针对PHP多进程与MySQL的高并发瓶颈,可以通过优化SQL查询、增加索引、使用连接池和读写分离等方法来提升性能,同时考虑使用缓存技术减少数据库压力。如果有帮助帮忙采纳一下。谢谢
写过类似的东西,像这种需要多次更新同一账号数据的时候,我们的做法是处理完数据后不立即存入mysql,
而是放在了redis中,后面的数据更新都在redis修改,
在redis积累了数千条数据后在异步的写一次数据到mysql,效果不错
卤煮的场景多进程处理建议只负责数据处理,处理完的放redis,在单独用一个脚本定时检测redis
达到条件的时候(一定条数或时间)存一次数据到mysql