内容简介:目前,全新的异步任务服务每天高效稳定的为唱吧提供数亿次的调用。服务器团队用全新的方式重新定义了异步任务实现方式,以为云计算而生的NSQ、成熟的PHP执行者PHP-FPM、自主开发的中间件NSQProxy以及admin管理后台共同组成了异步任务的队列服务。打开唱吧服务器代码,一股历史的厚重感扑面而来,“这块代码是历史原因”成了同学们的口头禅。为了提高响应,减少用户等待,和线上用户无直接关系的代码挪到异步后台执行,这样可以让前台业务逻辑代码更加简洁,执行速度更快。比如:用户购买礼物时,余额检查、付款、礼物进入
目前,全新的异步任务服务每天高效稳定的为唱吧提供数亿次的调用。服务器团队用全新的方式重新定义了异步任务实现方式,以为云计算而生的NSQ、成熟的 PHP 执行者PHP-FPM、自主开发的中间件NSQProxy以及admin管理后台共同组成了异步任务的队列服务。
唱吧异步任务的前世
打开唱吧服务器代码,一股历史的厚重感扑面而来,“这块代码是历史原因”成了同学们的口头禅。
为什么要用异步任务
为了提高响应,减少用户等待,和线上用户无直接关系的代码挪到异步后台执行,这样可以让前台业务逻辑代码更加简洁,执行速度更快。比如:用户购买礼物时,余额检查、付款、礼物进入用户背包等逻辑放在同步执行,统计等放在异步执行。
流程
-
同步执行的代码将数据插入队列(MemcacheQ)
-
以crontab的方式在后台启进程,进程开头代码就是while(true)
-
从队列中去取出数据消费,然后sleep,一直死循环下去。
弊端
-
MemcacheQ远不如Nginx等足够成熟稳定,偶尔会莫名其妙的卡主自己好几秒。
-
消费者自由散漫,分散在各个机器上,开几个进程也都是随心所欲。
-
不支持订阅发布(推送),只能死循环里去get,get不到就sleep。出现了1次set对应136次get(135次get到空),白白浪费服务器资源。
-
消费者以死循环的方式常住内存,导致代码更新不及时,生成端的数据消费端不认识,必须上服务器手动kill进程,这期间会造成消费失败的数据丢失。
-
每一个环节都是单点,无法避免单点故障,坏一个洞全船都要沉。
鸟哥: 不要拿PHP做常驻内存的事情,因为我没给PHP写优雅的GC
唱吧异步任务的今身
NSQ替换MemcacheQ
Golang编写,云计算时代的产物,MQ领域的新星,为分布式消息队列而生,性能强劲,部署及其方便,二次开发难度低,有 Go 、 Python 、PHP等多语言客户端。NSQ相关不是重点,不在赘述。
NSQ优势
-
消息可靠性高:有ack/requeue机制。
-
磁盘落地:积压的消息可以磁盘落地。
-
扩展性:优势明显,新增节点极其方便。
-
易用性:部署简单,甚至安装不需要编译。
-
分布式:为分布式而生,去中心化,官方推荐的拓扑结构没有单点故障。
-
延迟投递。
-
支持订阅发布。
-
bitly、有赞、 docker 、digg等大规模部署,并且有赞开源了二次开发版本。
唱吧村特殊村情及主要矛盾
历史上,我们的异步任务入队的数据是:PHP的类名(string)、PHP方法名和该方法的参数拼接成一个字符串。消费者需要new一个类,然后以执行字符串的方式(eval())来执行,由于方法和参数拼接成字符串,带来转义风险,最主要的是,这样就决定了我们的最终消费者,必须是PHP。
全新的架构
-
该拉取为推送。有数据就执行,没数据就阻塞,避免空轮询。
-
引入PHP-FPM。PHP-FPM作为非常成熟的PHP执行者,有完善的进程管理、垃圾回收、性能优化,并且常驻内存,非常适合作为最终消费者。并且PHP-FPM常驻内存并监听9000端口,也非常适合承担订阅者的角色,代码实时更新。
-
将上述两点融合,开发一个中间件,从NSQ订阅数据,再以FAST-CGI协议推送给PHP-FPM。
-
开发一个管理页面,可以方便的配置和管理。
-
旧版是线上请求直接写入单点的MemcacheQ机器上,先在是以HTTP写入Nginx,Nginx做负载均衡,转发到NSQ节点上。
中间件NSQProxy
NSQProxy是唱吧服务器部门自住开发的轻量级中间件,Golang编写,性能强劲。
实现方式
-
启动第一步主备检测:
-
如果是主机,则一个监听4140端口,此时新启两个协程,该协程等待备机发PING并回PONG,一直阻塞等待accept。另一个协程(主协程)继续向下走。
-
如果是备机,则死循环向主机发PING,如果收到PONG,则sleep,程序会一直阻塞在这里。如果主机未回复,否则备机转为主机启动。
-
主备角色等信息由配置文件决定。
信号监听:
-
新启二个协程,一个是走主流程的(下面第3点),一个是走动态消费流程(下面第4点)。
-
主协程监听SIGINT(2), SIGTERM(15), SIGTRAP(5),如果是SIGINT(2)和SIGTERM(15)则程序退出,如果是SIGTRAP(5)忽略不管。主协程阻塞在这里。
主流程:
-
协程从 Mysql 获取管理后台的配置,每个队列都与NSQ建立一个链接,并根据配置的并发量(N个),启动N个协程,以FAST-CGI协议向PHP-FPM发送消费数据。
-
在刚进入主流程时,上一小步执行之前,会启一个新协程,以定时器的方式,定时更新系统配置建和管理后台的配置数据。
动态消费:
-
有些运营活动、push推送等突发性的入队,造成原本的并发量消费能力不足,这时候需要新增协程来帮助消费。
-
以定时器的方式,定时扫描NSQ中积压的队列,若某队列达到积压阈值,则会启动与NSQ建立新的连接,启动新的协程来增加消费能力。再以定时器的方式,达到设定时间(如300秒),就会关闭链接并协程协程。
性能测试
入队
借助apache-ab工具,消息长度:356(短信验证码的标准长度)。
-
Golang TCP协议:10个进程,每个进程入队10000,总入队100000
-
运行时间:8s
-
单次平均时间:8s / 10w = 0.08ms
-
单次真实时间: 0.08ms * 10 = 0.8ms
-
QPS: 10w/8s = 12500 个
-
CPU * 20核 ≈ 30%
Golang HTTP协议:10个进程,每个进程入队10000,总入队100000
-
运行时间:2.9s
-
单次平均时间:2.9s / 10w = 0.029ms
-
单次真实时间: 0.029ms * 10 = 0.29ms
-
QPS: 10w/2.9s = 34482 个
-
CPU * 20核 ≈ 30%
PHP HTTP协议:10个进程,每个进程入队10000,总入队100000
-
运行时间:3.4s
-
单次平均时间:3.4s / 10w = 0.034ms
-
单次真实时间: 0.034ms * c = 0.34ms
-
QPS: 10w/3.4s = 29412 个
-
CPU * 4核 ≈ 30%,16个核是0%
出队
消息长度:356(短信验证码的标准长度),消费操作就是打日志。速度是根据打日志的时间戳来统计。
-
20并发,最多推送40
-
QPS:2177
-
PHP-FPM:起初CPU * 20核 > 90%, 后来就长期维持100%。
-
NSQD:CPU会有个别一两个核,在个别时刻闪现到1%~2%,然后恢复0%,网络读写210k/960k
-
NSQProxy:CPU一个核30%,一个核15%,其余都是4%。网络读写2400k/3320k,磁盘每30秒写30M(每条消费log)
10并发,最多推送20
-
QPS:同上
-
起初CPU * 20核 > 60%, 后来就所有核长期维持100%。
-
NSQD:CPU会有个别一两个核,最多2个核闪现到1%,然后恢复0%,网络读写210k/960k
-
NSQProxy:CPU一个核30%,一个核13%,其余都是4%。网络读写2400k/3320k,磁盘每30秒写30M(每条消费log)
谁还不是写BUG的咋滴
PHP-FPM的特性决定了它的瓶颈
PHP-FPM就像HTTP协议一样,每次请求相互独立,不保存上次请求的状态和上下文。抛开优化点不谈,一次请求结束,删除变量,删除引用,释放内存,一切成空。下次个请求到来时,从头再来!
这就造成了一个问题:如敏感词检测的类,在new Class()时,会加载十几万行词库文件并解析,这一步大约耗时700ms,那么PHP-FPM每次消费前,都要重复这一步骤,执行完成后再销毁,这就会使消费速度大幅下降,队列积压严重。如果是Golang、 JAVA 等语言,则可以一次加载解析,永久使用。
PHP-FPM进程数限制
我们的异步任务几乎都是IO密集型,没有CPU密集型。所以可以开数百个进程跑,反正都在Sleep等待网络返回。而PHP-FPM却不可以,
在PHP7下,PHP-FPM开到200以上性能CPU的占用就开始飙升,同时每个PHP-FPM是同步执行,进程不可复用。那么在PHP-FPM进程数固定的大前提下,如果有一个任务执行特别慢,那么就会占用PHP-FPM进程不释放,这样的任务多来几个,很快就会把服务器上PHP-FPM的进程全部占光,导致其他消费快的队列却无可用的PHP-FPM。
二期畅想
一期工程所暴露的问题,在二期中会逐一解决。
针对以上的坑,列出几点解决方案,待探讨论证。
方式一
维持现状,NSQ和MemcacheQ共存,大多数队列在NSQ + PHP-FPM的方式,个别特殊队列仍然使用MemcacheQ + 死循环的方式。
方式二
安装NSQ-PHP客户端扩展,取代PHP-FPM。
方式三
简单粗暴执行:消费快的队列在PHP-FPM中执行,消费慢的队列仍然在while(true)中执行。NSQProxy不仅仅提供一个消息转发到PHP-FPM的功能(订阅发布的推送模式),同时还要监听一个端口可供消费者主动获取(拉取数据模式)。但是这样,既不优雅,也不统一和规范,尽管它可以快速解决当前问题。
方式四
PHP实现一个常驻内存的,监听某端口的功能,取代PHP-FPM。我小时候写的PHP-Socket项目有了用户之地:MeepoPS是Nginx + PHP-FPM的结合体,即对外监听端口,也可以直接运行PHP代码,压测数据表明比较稳定。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- vue项目实践004~~~一篮子的实践技巧
- HBase实践 | 阿里云HBase数据安全实践
- Spark 实践:物化视图在 SparkSQL 中的实践
- Spark实践|物化视图在 SparkSQL 中的实践
- HBase实践 | 数据人看Feed流-架构实践
- Kafka从上手到实践-实践真知:搭建Zookeeper集群
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
在线进制转换器
各进制数互转换器
XML、JSON 在线转换
在线XML、JSON转换工具