本文由上庄前端开发工程师余秀撰写。本文发表于github上庄博客,欢迎订阅!需求背景目前,节点端的服务逐渐成熟,许多公司开始承担业务处理或视图渲染的工作。与个人开发的简单服务器不同,企业级节点服务有更苛刻的要求:高稳定性、高可靠性、健壮性、直观的监控告警。试想一个存在安全隐患且没有监控预警系统的节点服务。在生产环境运行的场景中,当一个节点实例挂掉时,运维人员或者相应的开发维护人员无法第一时间知道,直到客户或者测试人员报告bug,问题才会得到解决。在这段无人化的处理过程中,丢失的订单和用户的忠诚度和信任度在未来是无法挽回的,所以对于节点程序的业务开发者来说,这需要严谨的代码和完整的异常处理;对于节点框架的维护者来说,需要提供完善的监控预警体系。Function当一个服务进程(daemon)在后台运行时,作为开发者,我们主要关注以下信息:服务进程是否运行,isalive服务进程的内存使用情况,是否有未回收(释放)memory服务进程的cpu使用率,在计算量大的情况下,是否需要分片处理和延迟处理,来处理服务进程的实时响应时间和吞吐量。作为运维人员,不仅关注节点服务进程的相关信息,还包括物理主机的使用状态:物理硬盘剩余存储空间、内存、CPU使用率、网络是否接入可见是正常的。无论是监控主机还是进程,我们关注的最多的是资源使用和业务,所以我们的监控预警系统也着重于实现这些功能。系统架构简单目前生产环境中的节点服务大多采用多进程或集群模式,为了应对突发流量,往往采用多机部署。因此,监控预警的目标实体是多个物理(虚拟)机下的多个子进程。比如现在的节点服务在单机上往往采用1+n的进程模型:所谓1就是1个主进程;n表示n个工作进程,这些工作进程是从主进程fork出来的。同时,根据经验,n的取值往往等于主机的CPU核数,以充分利用其并行能力。那么,采用这种进程模型的节点服务在线部署在4台物理机上,我们需要监控的是4xn进程,这就涉及到分布式数据同步的问题。我们需要想办法实现高效、准确、简单的数据存储和读取,并尽可能保证这些数据的可靠性。在这里,笔者使用分布式数据一致性系统ZooKeeper(以下简称ZK)来实现数据的存储和读取。传统数据库之所以不用,是因为读写表的性能问题。例如,为了防止多个进程同时写表造成的冲突,必须进行锁表等操作,读写硬盘的性能相对于内存读写来说是比较低的;之所以没有使用IPC+事件机制实现多进程通信,主要是因为node提供的IPC通信机制仅限于父子进程,不同主机的进程无法通信或者实现复杂度高,所以不采用这种方法。使用ZK实现多节点下的数据同步,可以在保证集群可靠性的基础上实现数据的最终一致性。对于监控系统来说,不需要时刻准确的数据,所以数据的最终一致性完全满足了系统的需求。ZK服务集群通过paxos算法实现选举,利用ZK独特的算法在集群各节点同步数据,最后抽象成一个数据层。这样,ZK客户端可以通过访问ZK集群的任意一个服务节点来获取或读写相同的数据。通俗点说,ZK客户端看到的所有ZK服务节点都是一样的数据。另外,ZK提供了一个临时节点,即ephemeral。此节点绑定到客户端的会话会话。一旦会话超时或连接断开,该节点将消失并触发相应的事件。因此,可以利用该特性来设置节点服务的isalive(是否存活)功能。但是目前node社区的ZK客户端并不完善(主要是文档)。作者使用了node-zookeeper-client模块,并承诺所有接口,这样在开发多级znode时可读性更强。上图是笔者设计的监控预警系统的架构图。这里需要重点关注以下几点:ZooKeeper部署和znode节点使用单机内部节点进程的进程模型:1+n+1precaution进程的工作内容以及与master和worker的通信方式。下面着重对以上几点进行详细介绍。ZooKeeper部署和编码细节在上一节中已经提到。ZooKeeper被抽象为数据一致性层,是由多个节点组成的存储集群。因此,在特定的线上环境下,一个ZK集群是由多台线上主机搭建而成。这样一来,所有的数据都保存在内存中,每当工作进程对应的数据发生变化时,相应的znode节点的数据也会被修改。具体实现中,每个znode节点存储json数据,方便节点端直接解析。在具体的代码中,需要注意ZK客户端session超时和断网重连的问题。默认情况下,ZK客户端会在网络断开后帮我们完成重连过程的恢复,并且在重连过程中会携带上次断开的sessionid,这样在session没有计时前仍然会绑定出去。数据;但是当session超时时,sessionid对应的数据会被清除,这就需要我们自己处理这种情况,也就是所谓的现场恢复。实际上在监控系统中,由于需要实时查询相应节点数据,因此需要一直保持session。当设置了session过期时间后,ZKclientsession最终会超时。因此,我们需要实施现场恢复,需要引起重视。进程模型为了提高节点程序的并行处理能力,大部分开发者往往采用一个主进程+多个工作进程来处理请求,无需监控预警系统即可满足需求。但是随着监控预警功能的加入,很多人估计这些功能会被添加到主进程中。首先,这还不算主进程工作功能的混乱。最重要的是增加了风险(预警系统的功能之一)就是对堆进行快照,提醒开发者。因此在主进程中执行查询、管理系统资源、发送邮件等都可能存在风险)。因此,为了主进程的功能简单性和可靠性,创建了一个与主进程处于同一级别的预防进程。采用1+n+1的模式,不会影响请求处理的效率。工作进程的作用仍然是处理请求。因此,新的流程模型与以前的代码完全兼容。需要做的就是添加到主进程和预防进程执行的代码中。业务部分代码。通信方式在监控预警系统中,需要实现预警进程<-->master进程,master进程<-->worker进程,预警进程<-->worker进程的双向通信,如dottedmemory,需要prevention进程通知worker进程,worker管理完成后发消息给prevention进程,prevention处理后发邮件通知。首先,worker和master之间的通信是通过node提供的IPC通道进行的。需要注意的是,IPC通道只能传输字符串和可结构化对象。结构化对象可以简单地用公式表示:o=JSON.parse(JSON.stringify(o))例如,RegExp的实例不是结构化对象。其次,工防之间的沟通是通过师傅这个桥梁来实现的,所以重点是防防与师傅之间的沟通。最后,prevention和master之间的通信是使用domainsocket机制实现的。这两个进程只是两个节点实例,因此不能使用节点提供的IPC机制,可以使用其他方法进行进程间通信如:命名管道、共享内存、信号量和消息队列,这些很容易实现方法,但缺点是两个过程之间的耦合度比较高。例如,命名管道需要创建特定的管道文件,并且对管道文件的大小有限制。使用域套接字的最大优点是可以灵活制定通信协议,并且易于扩展。node的net模块提供了domainsocket的通信方式,类似于网络服务器。使用域通信的服务器不监听端口而是监听sock文件。这种方法实现了全双工通信。业务量计算和数据管理这里所说的业务量是指监控预警系统关注的数据业务,如内存和CPU利用率、吞吐量(每分钟请求数)、响应时间等。其中内存和cpu利用率可以通过top等linux下相关命令查询,响应时间和吞吐量可以通过koa中间件粗略统计。但是为了方便开发者专注于业务而不是兼容底层操作系统,推荐使用pidusage模块来完成资源利用率的测量,但是笔者并没有找到相关的吞吐量测量工具,也只是在中间件中粗略计算推断出来的。在预防过程中,设置了两个阈值。一是警告值,当使用的内存大小超过这个值时,将进行日志管理,并开始周期性的节点堆内存管理;另一个是危险值,如果超过这个值,就会进行内存管理,并发送邮件提醒,根据附件将近三张快照分析内存。结论采用上述监控预警架构,可以有效实现多节点下多进程的监控,在保证进程可靠性的基础上,完成入侵少、安全性高、可扩展性强的实现。以后无论是临时扩展宿主节点,还是改变子进程数量,都可以瞬间反映在UI界面上,比如
