本文由尚妆前端开发工程师 撰写
本文发表于
目前 node 端的服务逐渐成熟,在不少公司内部也开始承担业务处理或者视图渲染工作。不同于个人开发的简单服务器,企业级的 node 服务要求更为苛刻:
想象下一个存在安全隐患且没有监控预警系统的 node 服务在生产环境下运行的场景,当某个 node 实例挂掉的情况下,运维人员或者对应开发维护人员无法立即知晓,直到客户或者测试人员报告 bugs 才开始解决问题。在这段无人处理的时间内,损失的订单数和用户的忠诚度和信任度将是以后无法弥补的,因此对于 node 程序的业务开发者而言,这就要求 代码严谨、异常处理完备 ;对于 node 框架的维护者而言,则需要提供完善的 监控预警系统 。
当一个服务进程在后端运行时(daemon),作为开发者我们关注的信息主要有以下几点:
而作为一个运维人员,关注的不仅仅是 node 服务进程的相关信息,还包括物理主机的使用状况:
可以看出,不管是针对主机还是进程进行监控,我们的关注点大多数是资源使用率和业务量处理能力,因此我们的监控预警系统也着重实现这些功能。
目前生产环境下的 node 服务大多采用多进程或者 cluster 模式,而且为了响应突发流量往往采用多机部署,因此监控和预警的目标实体就是 多物理(虚拟)机下的多个子进程 。
比如,目前 node 服务在单机上往往采用 的进程模型:所谓 ,即 1 个主进程; ,表示 n 个工作进程,而且这些工作进程是从主进程上 fork 出来,同时根据经验,n 的值往往等同于主机的 cpu 核心数,充分利用其并行能力。那么,采用该种进程模型的 node 服务部署在线上 4 台物理机上,我们需要监控的则是 4xn 个进程,这涉及到了分布式数据同步的问题,需要寻找一种方法实现高效、准确和简易的数据存和读,并且尽可能的保证这些数据的可靠性。
在这里,笔者采用了分布式数据一致系统 ZooKeeper(下文简写为 ZK)实现数据的存和读。
采用 ZK 来实现多节点下的数据同步,可在保证集群可靠性的基础上达到数据的最终一致性,对于监控系统而言,不需要时刻都精确的数据,因此数据的最终一致性完全满足系统的需求。ZK 服务集群通过 paxos 算法实现选举,并采用 ZK 独特的算法实现数据在各个集群节点的同步,最终抽象为一个数据层。这样 ZK 客户端就可以通过访问 ZK 集群的任意一个服务节点获取或读写相同的数据,用通俗的语言来形容,就是 ZK 客户端看到的所有 ZK 服务节点都有相同的数据。
另外,ZK 提供了一种临时节点,即 ephemeral 。该节点与客户端的会话 session 相绑定,一旦会话超时或者连接断开,该节点就会消失,并触发对应事件,因此利用该种特性可以设置 node 服务的 isalive(是否存活)功能。不过,目前 node 社区针对 ZK 的客户端还不是很完善(主要是文档),笔者采用 node-zookeeper-client 模块并且针对所有接口 promise 化,这样在进行多级 znode 开发时更可读。
上图是笔者设计的监控预警系统的架构图,这里需要着重关注一下几点:
下面着重详述以上几点。
上节已提到,ZooKeeper 抽象为一个数据一致层,它是由多个节点组成的存储集群,因此在具体的线上环境下,ZK 集群是由多个线上主机搭建而成,所有的数据都是存储在内存中,每当对应工作进程的数据发生变化时则修改对应 znode 节点的数据,在具体实现中每个 znode 节点存储的是 json 数据,便于 node 端直接解析。
在具体的代码中,我们需要注意的是 ZK 客户端会话超时和网络断开重连的问题。默认,ZK 客户端会帮助我们完成网络断开后重连过程的简历,而且在重新连接的过程中会携带上次断开连接的 session id,这样在 session 未超时的前提下仍会绑定之前的数据;但是当 session 超时的情况下,对应 session id 的数据将会被清空,这就需要我们的自己处理这种情况,又称作 现场恢复 。其实,在监控系统中,由于需要实时查询对应节点数据,需要始终保持 session,在设定 session expire 时间的情况下终究会出现 ZK 客户端会话超时的情况,因此需要我们实现现场恢复,需要注意。
大多数开发者为了提高 node 程序的并行处理能力,往往采用一个主进程 + 多个工作进程的方式处理请求,这在不需要监控预警系统的前提下是可以满足要求的。但是,随着监控预警功能的加入,有很多人估计会把这些功能加入到主进程,这首先不说主进程工作职能的混乱,最主要的是额外增加了风险性(预警系统的职能之一就是打点堆快照,并提醒开发者。因此主进程内执行查询、打点系统资源、发送邮件等工作存在可能的风险)。因此为了主进程的功能单一性和可靠性,创建了一个 precaution 进程,该进程与主进程同级。
采用 1+n+1 模型并不会影响请求处理效率,工作进程的职能仍是处理请求,因此新的进程模型完全兼容之前的代码,需要做的就是在主进程和 precaution 进程执行的代码中添加业务部分代码。
在监控预警系统中,需要实现 precaution 进程 <-->master 进程、master 进程 <-->worker 进程、precaution 进程 <-->worker 进程 的双向通信,如打点内存,需要由 precaution 进程通知对应 worker 进程,worker 进行打点完成后发送消息给 precaution 进程,precaution 进行处理后发送邮件通知。
首先,worker 与 master 的通信走的是 node 提供的 IPC 通道,需要注意的是 IPC 通道只能传输字符串和可结构化的对象。可结构化的对象可以用一个公式简易表述:
- o = JSON.parse(JSON.stringify(o))
如 RegExp 的实例就不是可结构化对象。
其次,worker 和 precaution 的通信是通过 master 作为桥梁实现的,因此其中的关节点就在于 precaution 与 master 的通信。
最后,precaution 与 master 的通信采用 domain socket 机制实现,这两个进程是只是两个 node 实例而已,因此无法采用 node 提供的 IPC 机制,而进程间通信可以采用其他方法如:命名管道、共享内存、信号量和消息队列等,采用这些方法实现固然简单,但是缺点在于两个进程耦合度相对较高,如命名管道需要创建具体的管道文件并且对管道文件大小有限制。使用 domain socket,最大的好处就是灵活制定通信协议,且易于扩展。
node 的 net 模块提供了 domain socket 的通信方式,与网络服务器类似,采用 domain 通信的服务器侦听的不是端口而是 sock 文件,采用这种方式实现全双工通信。
这里提到的业务量,指的是监控预警系统所关注的数据业务,如内存和 cpu 利用率、吞吐量(request per minute)和响应时间。其中,内存和 cpu 利用率可以通过 linux 下的相关命令如 top 来查询,响应时间和吞吐量则通过 koa 中间件实现粗略统计。不过为了方便开发者把精力集中到业务上去而非兼容底层操作系统,建议使用 pidusage 模块完成资源利用率的测量,而针对吞吐量笔者并未找到相关的工具进行测量,仅在中间件中粗略计算得出。
在 precaution 进程中,设置了两个阈值。一个是 warning 值,当使用内存大小超过了该值则进行日志打点,并开始周期性的 node 堆内存打点;另一个是 danger 值,超过该值则进行内存打点并发送邮件提醒,根据附件中的近三个快照分析内存。
采用上述监控预警架构,可以有效的实现多节点下多进程的监控,在确保进程可靠性的基础上完成侵入性较小的、安全性较高的、可扩展性强的实现。以后不管是临时扩张主机节点还是更改子进程数量,都可以瞬时在 UI 界面上直观体现,如
来源: http://www.tuicool.com/articles/bYJfIr3