24

独家|前端高性能队列应用实践探秘

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzI1NDc5MzIxMw%3D%3D&%3Bmid=2247486760&%3Bidx=1&%3Bsn=153aae799345ff429a24c76caec9a47d
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

导语

bull可以让node快速实现异步调用、流量削峰、分布式定时任务,进一步打破前端在高并发、分布式方面的限制。

本篇内容涵盖前端遇到的一些复杂的应用场景及实践经验,希望能给大家提供一些不一样的思路。

背景

2019年的今天,nodejs已经成为了前端研发必备的技术,几乎所有的前端团队都会涉及nodejs开发,nodejs在前端的应用场景,主要包括以下几个:

  • 命令行工具: 各种支持前端业务团队快速开发的脚手架,功能一般包括创建项目、编译项目、启动调试项目等。

  • 中间层: 为了解决跨域而出现的接口代理服务,以及为提升页面展示性能而出现的页面渲染服务等。

  • 和前端相关平台的后端服务: 比如配置平台,api管理平台等等。

这些场景都是在打造前端基础设施,使其更加完善,但是有时候前端也会遇到一些相对复杂的场景,比如面向用户或处理数据的服务,需要实现轻量级的异步调用、流量削峰和分布式定时任务, 这时会使用到队列框架。

选型

后端有非常成熟的消息队列框架,比如kafka、rocketmq等,但是这些队列中间件对于前端来说并不算友好,因为首先他们是Java开发的,在Java体系中使用,是前端人员并不熟悉的技术栈,学习成本较高,对接公司服务的成本也相对较高,需要开发node客户端。 其次,还是过于重量级了,比如rocketmq一般是独立集群部署,占用服务器资源非常多。

所以,前端如果想使用队列,就必须挑选基于node开发的、更适合的轻量级框架。 比较常见的包括Kue、Bull、Bee、Agenda。 下面的表格展示了四个框架的特点,可以清晰的看出四者的区别:

j2iu2eN.jpg!web

Kue是TJ大神的早期作品,最早流行起来的queue框架,但是更新不太频繁。Bull 是目前功能最完善的框架,同时支持Jobs和Messages。Bee在开发构成中参考了Bull,更加专注于小粒度任务的处理,并极大的优化了这种场景的性能,同时也只提供相对小的功能集。以上三种框架均基于redis,而Agenda是基于mongo的。

经过权衡,我们在线上报错收集场景中选用了Bull,基于以下几点原因:

  • 线上报错是发生时间短、请求峰值高的场景,所以Rate Limiter是必须的;

  • Bull 有强大的分布式Jobs处理能力,使前端的定时任务开发变的更加高效和合理;

  • Bull 基于redis,目前58内部Redis的应用变得更为主流,mongo逐渐被其他服务替代,所以我们也会顺应公司的趋势选择基于redis的框架;

  • Bull 的更新相对频繁,有比较好的项目交流,同时很多web框架都有Bull的中间件,而且Bull有多个可视化项目更加方便管理和查看。

流量削峰

早期,我们使用了常见的node的服务架构,架构图如下:

NnM7fmB.jpg!web

上报错误的请求会先打到node进程,由node进程经过简单处理后,基于业务直接对MySQL操作,表面上看似乎没什么问题,但是一旦出现线上错误集中爆发的情况,海量请求产生海量的SQL执行,会对MySQL服务造成极大的压力,如果存在效率较低的慢SQL,更是雪上加霜。

上报错误信息的业务逻辑相对简单,静默上报对用户无感知,用户也不关心是否真的处理成功,其瓶颈仅仅是对数据库的操作,比如保存错误环境信息等,那么可以引入Bull,把架构变更为:

NFBRbqF.jpg!web

node进程接收到请求后,把上报数据构建为一个bull的job,加入提前创建好的queue中,然后直接返回success。bull构建的队列是分布式队列,只要命名相同,同一个集群的不同节点的不同node进程都可以向同一个queue添加job。进入queue的job,会在queue中按照指定的顺序执行(默认为先进先出),一个job执行完,再执行下一个,可以在对queue设置数量限制,如果并发加入的job过多,超过了限制的阈值,则会对超过阈值的job延迟处理,加入Delay有序集合。通过这种设计,并发带来的压力会大大降低,当然这也有可能会造成队列的消息积压,需要用临时构建新的消费者的方式去快速处理消息,后面等流量降低再恢复正常处理。最终,我们的错误收集平台的稳定性得到大幅提升,达到了99.99%,也扛住了业务年初冲量时瞬时100QPS的流量高峰。

分布式定时任务

前端的定时任务通常会通过在linux服务器中,通过设置crontab来实现,但是存在一个问题,crontab是在集群的每一台服务器分别部署,是单机工具,自身缺乏分布式和集中管理的能力。所以为了简单,一般会在集群中挑选一个节点做某一个定时任务的部署,这种定时任务比较少,任务耗时比较少的时候还可以接受,当定时任务很多就会出现资源利用不合理、调配不及时的现象。

zIB7Zfu.jpg!web

阿里的node框架egg也实现了定时任务功能,同样不支持分布式。 但是给了两种解决方案,一种是类似于上面说的,通过配置写死服务器ip和定时任务的对应关系,不过如果是docker部署就不合适了,docker的ip会变,另一种是扩展自定义定时任务类型,把自己node进程当做被调度者,让其他分布式工具来调度管理,这是会让项目变的更加复杂。

经过调研,发现Bull本身就是基于redis分布式框架,又有强大的job管理能力,包含重复定时、沙箱处理等功能,可以完美的解决现存问题。 把定时任务,作为job加入bull,设置启动时间,设置是否自定义进程,通过bull的调度分布式集群的进程去完成工作。

3qAbmuE.jpg!web

核心原理

那么Bull是如何实现分布式调度和按指定顺序高性能执行的呢? 答案是Bull的底层工具Redis和Redis的brpoplpush命令,原理图如下:

r2eAbqA.jpg!web

Bull基于生产者消费者模型设计框架,利用redis的阻塞能力实现模型。

生产者是通过调用queue的add方法生产数据,即添加job,会向redis发起lpush或者 rpush命令向一个list中添加包含job的元素,如果list是nil,则会先创建list再添加。

redis的list类型天生支持用作消息队列,list类型是使用双向列表实现,支持从头部和尾部插入新的元素,并且效率非常高,即使list中已经存储了百万级的元素,也可以在常量时间插入完成。

消费者是通过绑定在queue的执行函数消费数据,在绑定执行函数时会向redis发起一个brpoplpush命令,这个命令将list中的最后一个元素返回给node进程。 当list为nil,brpoplpush会阻塞连接,直到等待超时,或有另一个node进程的生产者添加job数据。 当node进程处理完一个job,会再次发起brpoplpush。

一个集群包含多个节点服务器,部署着多个node进程,这些node进程会共同持有一个queue,每个node进程都可以生产数据和消费数据,意味着每个node进程都可以添加job,都会向redis发起brpoplpush命令,去争夺下一个需要被执行的job,redis执行哪个进程的brpoplpush命令,哪个进程就获得job,正是这种特性打造了一个非常简单又高效的分布式任务调度系统。

实践经验

在实践中,还需要注意几个问题:

  • 及时清理job。 queue中的job执行完务必要清理,否则会导致两个问题:

1)redis的keys会逐渐增加,最终导致内存被占满。

2)redis服务器和node进程之间会频繁通信,交换list数据,node集群的节点服务器和redis服务器的网卡流量会非常大,而node因为会缓存job数据,内存也会逐渐增大,很容易超过node进程设置的max-old-space-size,最终不断重启。

  • redis与部署环境一一对应。 通常公司会有多个部署环境,一般是生产环境、沙箱环境、测试环境,前端也经常会有 本地环境。 在非生产环境的部署中,尽量部署与研发环境分别对应的、不同的redis,否则会出现几个环境的node进程争夺job的问题,影响测试效率。

  • 构造可以JSON序列化的job。 job构建的时候传入的是一个对象,请让这个对象可以被序列化成JSON字符串,不要增加方法,或者持有process、ctx等对象,因为要在redis中存储。

总结和规划

本文介绍的是在前端错误收集的场景下,如何利用Bull这个强大的node队列框架解决流量高峰,定时任务的分布式调度的问题。 前端作为可以全栈开发的技术方向,在落地过程中总会遇到各技术方向都会遇到的问题,如何在前端体系下去实现,是摆在所有前端人前面的疑问,以上就是我们在摸索过程中积累的一些经验,如果有什么遗漏或者错误,欢迎大家指正,也欢迎大家一起相互交流继续探索。

参考文献

1. https://github.com/OptimalBits/bull

作者简介

李祎,58前端架构师 /  技术委员会委员

留言区分享你的评价、感想或实践经验,截止11月14日14:00点获赞数第一名的留言即可获得50元京东购物卡一张或技术类图书一本~

JjMnaeF.jpg!web

点击“在看”或分享至朋友圈让更多人看到哦~

Nb2iMvQ.jpg!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK