NSQ 最佳实践

栏目: PHP · 发布时间: 6年前

内容简介:目前,全新的异步任务服务每天高效稳定的为唱吧提供数亿次的调用。服务器团队用全新的方式重新定义了异步任务实现方式,以为云计算而生的NSQ、成熟的PHP执行者PHP-FPM、自主开发的中间件NSQProxy以及admin管理后台共同组成了异步任务的队列服务。打开唱吧服务器代码,一股历史的厚重感扑面而来,“这块代码是历史原因”成了同学们的口头禅。为了提高响应,减少用户等待,和线上用户无直接关系的代码挪到异步后台执行,这样可以让前台业务逻辑代码更加简洁,执行速度更快。比如:用户购买礼物时,余额检查、付款、礼物进入

目前,全新的异步任务服务每天高效稳定的为唱吧提供数亿次的调用。服务器团队用全新的方式重新定义了异步任务实现方式,以为云计算而生的NSQ、成熟的 PHP 执行者PHP-FPM、自主开发的中间件NSQProxy以及admin管理后台共同组成了异步任务的队列服务。

唱吧异步任务的前世

打开唱吧服务器代码,一股历史的厚重感扑面而来,“这块代码是历史原因”成了同学们的口头禅。

为什么要用异步任务

为了提高响应,减少用户等待,和线上用户无直接关系的代码挪到异步后台执行,这样可以让前台业务逻辑代码更加简洁,执行速度更快。比如:用户购买礼物时,余额检查、付款、礼物进入用户背包等逻辑放在同步执行,统计等放在异步执行。

流程

  1. 同步执行的代码将数据插入队列(MemcacheQ)

  2. 以crontab的方式在后台启进程,进程开头代码就是while(true)

  3. 从队列中去取出数据消费,然后sleep,一直死循环下去。

NSQ 最佳实践

弊端

  • MemcacheQ远不如Nginx等足够成熟稳定,偶尔会莫名其妙的卡主自己好几秒。

  • 消费者自由散漫,分散在各个机器上,开几个进程也都是随心所欲。

  • 不支持订阅发布(推送),只能死循环里去get,get不到就sleep。出现了1次set对应136次get(135次get到空),白白浪费服务器资源。

  • 消费者以死循环的方式常住内存,导致代码更新不及时,生成端的数据消费端不认识,必须上服务器手动kill进程,这期间会造成消费失败的数据丢失。

  • 每一个环节都是单点,无法避免单点故障,坏一个洞全船都要沉。

鸟哥: 不要拿PHP做常驻内存的事情,因为我没给PHP写优雅的GC

唱吧异步任务的今身

NSQ替换MemcacheQ

Golang编写,云计算时代的产物,MQ领域的新星,为分布式消息队列而生,性能强劲,部署及其方便,二次开发难度低,有 GoPython 、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节点上。

NSQ 最佳实践

中间件NSQProxy

NSQProxy是唱吧服务器部门自住开发的轻量级中间件,Golang编写,性能强劲。

实现方式

NSQ 最佳实践

  1. 启动第一步主备检测:

  • 如果是主机,则一个监听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(短信验证码的标准长度)。

    1. 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(短信验证码的标准长度),消费操作就是打日志。速度是根据打日志的时间戳来统计。

    1. 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代码,压测数据表明比较稳定。


    以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

    查看所有标签

    猜你喜欢:

    本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

    创新者的处方

    创新者的处方

    [美]克莱顿·克里斯坦森、杰罗姆·格罗斯曼、黄捷升 / 朱恒鹏、张琦 / 中国人民大学出版社 / 2015-9 / 89.90元

    [内容简介] ● 创新大师克里斯坦森采用了哈佛商学院在20年研究中总结而出的、在各行业实践中获得成功的管理创新经验,把颠覆式创新理念引入美国医疗行业研究。医疗机构需要量体裁衣,选择合适的商业模式展开创新之举。 ● 作者同时探讨了医疗保险公司、制药企业、医学院和政府机构在医疗改革中起到的作用,从社会性角度深入剖析了医疗保健行业未来之路。 ● 医疗界人士、政策制定者、对医疗界现......一起来看看 《创新者的处方》 这本书的介绍吧!

    在线进制转换器
    在线进制转换器

    各进制数互转换器

    XML、JSON 在线转换
    XML、JSON 在线转换

    在线XML、JSON转换工具