宜信开源UAV心跳机制与容器进程数据采集

服务心跳机制主要用于确认服务的存活状态,UAVStack的心跳数据还负责上报节点的容器及进程监控数据,支持在前端实时查看应用容器和进程的运行状态,并根据这些数据对容器和进程做出预警。 一、背景在微服务架构中,服务心跳是一个简单但非常重要的机制,用于确认微服务的存活状态。UAVStack中的心跳是一个Http请求,MonitorAgent(以下简称MA)通过定时向HealthManager(以下简称HM)发送一个带有特定报文格式的Http请求完成一次心跳的发送过程。心跳报文含有发送时的时间戳,用于更新HM端的数据状态。 与普通的心跳不同,UAVStack中的心跳还负责上送MA端的应用容器和进程监控数据。每次发送心跳的时候,在MA端会有定时任务去收集MA所在的应用容器心跳的基本信息,及应用容器上的进程数据,随着心跳数据包一起上送。 本文将首先介绍UAVStack的基础心跳机制,之后对应用容器、进程的数据采集做详细说明。 二、基本架构心跳的实现有很多种方式,心跳的发起可以由客户端发起也可以由服务端发起,只需完成确认存活这一基本功能即可。但是在一般的实现中,我们更倾向于客户端主动向服务端进行报告,因为当客户端逐渐增加,单纯通过服务端的轮询会导致服务端的压力,影响性能。 在UAVStack的实现中,我们也采用了这样的方式,通过客户端(MA)主动向服务端(HM)发送心跳信息,告知HM自身的存活情况。 一次心跳由UAV的MA和HM共同完成: MA定时生成心跳数据,携带MA节点的应用容器信息、进程信息以及服务信息,通过Http请求上报给HM;HM负责将接收到的心跳数据存入Redis缓存,并定时扫描心跳数据,确认节点的存活状态。对于随同的应用容器等监控信息,会在Redis进行暂存,后续随着HM的定时任务最终存入OpenTSDB进行落盘。整体的架构如下所示: 三、基础心跳机制 心跳服务主要流程如上图所示,其逻辑有以下几步: 1)MA的定时心跳任务生成一个空心跳数据,将心跳数据交给MA端的容器、进程数据采集任务。 2)MA端的容器、进程数据采集任务负责产生心跳数据的时间戳、采集节点的应用容器、进程监控数据、节点的基本信息、节点的可用服务信息等。经过以上过程之后,心跳数据将包含以下内容: 心跳时间戳:节点发送心跳时的时间戳,方便HM后续比对判定节点的存活状态。应用容器的基本信息:包括节点ID、名称、主机名、IP等。应用容器的简单监控数据:包括CPU负载、内存使用、硬盘使用等。应用容器上的进程信息:包括每个进程的pid、资源占用等。节点的能力信息:包括该节点上启用的Feature功能。节点的服务信息:包括该节点上可提供的服务及其访问接口,用以实现服务发现。可选)子节点的节点信息:如果MA和HM部署在不同的网段,则MA无法通过直接Http的方式推送数据给HM,此时需要MA将数据上送给自己网段内的HM,再由该HM的心跳客户端发送给总的HM。这种情况下,上报的数据中会携带子节点的节点信息。每个节点信息均会包含上述几种数据。最后将心跳数据发送给HM。 3)HM端在接收到心跳数据之后,将其存入自身的Redis缓存。使用上报数据中的服务信息更新Redis中的服务状态,用于服务发现请求。 4)HM端在启动心跳接收服务时,会同时启动心跳检查任务。这个任务会定时扫描Redis中的心跳数据,根据当前系统时间与心跳时间戳的差,判断心跳节点的存活状态,更新节点的状态,并对于过期的节点做删除处理。 四、应用容器、进程数据采集UAV的心跳数据除了完成心跳功能之外,还要上报节点的应用容器及进程的监控数据。 将应用容器与进程数据通过Http方式上报是为了保证应用容器监控数据与应用监控数据的隔离,通过不同方式的上送可以保证在MQ服务不能使用时不影响容器与进程数据的采集。 本节将集中说明这些数据的采集细节。 4.1 应用容器数据采集应用容器的数据分为两部分: 其一是容器的基本信息,即节点的ID,主机名,系统信息和JVM信息等; 另一部分是一些简单的实时监控采集数据,包括CPU的负载、内存占用情况和磁盘占用情况等。这些数据在每次上报心跳数据的时候会分别从以下数据源实时采集: 应用启动后的System.getProperty:这部分数据主要包括操作系统的基本信息,JVM信息等。Java提供的工具类:这部分主要包括网卡信息。通过JMX获取的信息:包括CPU占用、内存占用等。系统本身记录的信息:这部分包括可提供的服务信息、启动的Feature信息、节点ID等。通过执行系统命令得到的信息:包括磁盘占用情况。通过直接读取/proc目录下的文件获取的信息:包括CPU占用、内存占用等。4.2 进程数据采集不同于应用容器数据采集,进程的数据并不是在心跳进程中进行采集的,而是由专门的Feature负责。在Feature中将进程数据采集进一步分解成进程端口流量数据采集以及其他数据采集。这两者均由定时任务完成,互相协作,最终由进程探测的定时任务更新心跳客户端的进程数据。 这种使用多个采集任务分别采集的方式可以针对不同的数据进行不同频度的采集。如对于网络端口流量的采集,就可以以更长的周期进行,以减低数据采集带来的性能损耗。同时,不同的任务也可以使用不同的线程执行,提升执行的效率。 进程数据采集流程大致如下图所示: 进程端口流量探测定时任务每隔一定时间读取本地变量端口列表,获取要采集的端口号。 之后对于Windows环境,采用JPcap获取网卡对象,并在网卡上设置tcp过滤器来统计一段时间内的端口流量。对于Linux环境则是直接通过调用Python脚本打开socket,分析流过的数据包获得。 获得全部端口上的流量数据后,任务会将采集数据交给进程数据采集任务,更新其本地变量,同时设置本次采集的时间戳。 进程探测定时任务由一系列子任务构成,在任务开始的时候,会先准备好一个Map结构的数据容器,用于存放采集到的进程信息,每个进程由pid区分,作为Map的key。 任务会先扫描所有的进程,获取pid和进程的端口。扫描到的进程会经过一个过滤器排除不需要采集数据的进程,之后正式采集每个进程上的数据。 对于每一个进程,会通过运行系统命令采集连接数、CPU、内存占用,磁盘读写数据以及网络端口流量数据。其中网络端口流量数据是由端口流量探测任务采集并更新的本地变量,而进程探测任务也会将扫描到的最新的端口列表更新到端口流量探测任务的本地变量。 如果应用是部署在容器上的,则还会有对应的容器信息采集。最后进程探测任务会将采集到的进程数据更新到心跳客户端的本地变量,随着每次心跳数据的生成被一起采集并上报。 进程数据的采集分别来自以下数据源: 系统命令:包括CPU、内存、连接数等(top等命令)/proc目录下各进程子目录:包括CPU、内存等信息、磁盘读写等信息执行脚本:包括Linux环境下的端口流量数据采集第三方工具包:包括win环境下的端口流量数据采集(JPcap)五、HM处理心跳数据和容器数据在通过Http上送到HM端之后,会由HM端对应的服务进行处理。 HM在启动时会启动自己的心跳客户端,负责发送本机的心跳数据和采集HM所在容器的监控数据。同时还会启动一个心跳服务,负责接收处理所有上送的心跳和容器数据信息。 心跳服务在收到心跳数据请求后,会根据HM的配置,判定当前的HM是不是Master节点。如果HM是Master节点,心跳服务会从Http携带的报文中拿出上报的数据,取得上报节点中的可用服务用于更新服务发现信息,之后将数据存入后端的Redis缓存中;如果不是Master节点,则会将数据移交至本机的心跳客户端,由其下次发送心跳时一起上送。 这样的设计是考虑到大规模监控时会有跨机房的情况存在,此时各监控节点往往不在同一个网段内,通过将同一个网段内的机器上交到边界的“网关”统一上交可解决这一问题。此时的HM即充当着“网关”这一角色。 HM在启动的时候同时还会启动一个定时任务,这个任务负责处理各节点的存活状况。任务定时从Redis中读取全部心跳数据,依次检查上送心跳数据中的客户端时间戳与当前系统时间戳的差值。 当时间超过一定的上送时间间隔之后,更改对应的节点存活状态。当超过一倍上送时间间隔,意味节点可能死亡,处于dying状态。当超过两倍时间间隔时,意味着节点已经死亡。当超过三倍时间间隔时,心跳服务会删除该节点的缓存记录。 随心跳一起上报的容器和进程数据会随着心跳数据一同被存入Redis中,后续由HM的其他定时任务读取并发送给预警中心进行处理,最终监控指标被格式化成特定的结构存入OpenTSDB。 同时采集的容器数据和进程数据会提供前端AppHub查看界面,如图所示: 点击页面上的每一个节点,可以查看详细的节点信息,包括节点的操作系统信息、JVM信息、提供的服务和安装的Feture等等。这些也就是前文所说的随心跳数据上报的那部分信息。如图所示: 六、总结心跳是微服务架构基础但重要的机制,通过定时发送心跳数据,MA节点报告了自身的存活状态,使得HM能够知晓当前系统的运行状态。 同时,UAVStack的心跳数据还同时负责上报节点的容器及进程监控数据,随着这些数据的上报,HM可以对监控的容器和进程做出预警,也能够在前端实时看到应用容器和进程的运行状态。 官方网站:https://uavorg.github.io/main/ 开源地址:https://github.com/uavorg 作者:张明明 来源:宜信技术学院

July 12, 2019 · 1 min · jiezi

轻量级配置中心

项目地址单项目的时候只需要一个简单的配置文件即可完成配置管理。假如多个项目多个环境同时配置就会产生非常复杂的配置管理情况。这个时候就需要用到配置中心了,它的原理其实类似于redis缓存这种。不同之处在于配置中心只关注配置,并且有更多的有利于配置的功能。大概的功能如下:同时这些功能也是这次要开发的配置中心需要包含的功能。本次开发的配置中心是基于nodejs的版本。客户端获取配置的方式可以参考协议来开发属于自己的客户端SDK。目前已经提供的是javascript版本。功能设计配置中心的开发是基于nodejs的,这里先看一下大体的流程。从上面可以看到,一个配置中心最主要的功能包括:数据存储。这里使用存储协议匹配多种存储形式。定时任务。这里包含了定时存储和自定义的定时更新任务。web站点。主要是提供一个简单快速的设置配置的方式。心跳检测。使用TCP协议将客户端和配置中心相连,可以监听到配置的改动,及时获取最新的配置内容。开发功能落实到具体的开发上面其实非常简单,很多时候可能只需要一个了解和实践的过程。这里我把大概的思路跟大家捋一下。数据存储存储的目的只有2个:存储用到的配置。这里只是简单的实现了列表、存、取的功能用户登录。本教程目前只实现了本地json文件的读写,如果想要使用MySQL或者Redis等可以自己按照下面的协议实现。init(),存储库的初始化方法。在项目启动的时候会第一时间调用。list(),获取命名空间列表。这里使用命名空间区分不同的配置文件。这里默认使用def来保存第一个文件。all(namespace = “def”),获取对应命名空间下的配置内容。update(namespace, txt),更新一个命名空间的所有配置。这里传入的是字符串,保存的也是字符串。get(key, namespace = “def”),获取对应命名空间下的某个字段的内容。这里需要警惕,配置不一定是json对象的。set(key, val, namespace = “def”),设置对应命名空间下的某个字段的值。login(user, pwd),登录判断,目前返回true或者false就可以了。只要是实现了上面协议的存储库就可以无痕替换掉项目的存储方式。定时任务固定的定时任务只有定时存储当前缓存的配置数据。这里一个是为了项目重启的时候能够获得上次保存的配置内容,另外一个也是为了防止程序崩溃的情况下能够不丢失已经保存的数据。程序内容设定了一个状态变量changed,如果有对应的配置变动了,就会将其的状态变更为true。定时保存任务就会在启动的时候讲数据保存在存储库中。自定义定时任务的目的在于设置一个配置结果和定时时间,当时间到了的时候就触发一次更新操作。这个功能实现非常简单,但是对于使用的人来说是一个非常好用的功能。例如:半夜2点定时上线某些产品什么的。。。。在也不需要熬夜等了。这次分享的项目还没有实现这个自定义定时更新功能。在后续的更新中会逐步完善这个功能。web站点web站点就是操作配置的地方。项目默认提供了几个接口用来获取和更新配置。目前使用jQuery实现,界面比较简陋,基础功能已经实现了。这里可以看到最上面是命名空间的标签。下面是添加命名空间。再往下是显示和编辑配置的地方。心跳检测心跳其实才是配置中心的核心内容。它主要的任务就是及时并且快速的通知到客户端配置有更新,需要使用最新的配置。服务端使用nodejs的net.createServer方法创建一个TCP的监听服务,如果客户端连接就会将客户端的连接对象放入对象缓存池。在连接的时候这里做了2件事:给连接对象添加了一个uuid,方便辨认不同的客户端。通知客户端发送验证令牌。这里的逻辑比较简陋,只是简单的验证一下。在连接之后就是心跳检测的过程了。客户端需要定时去更新自己的状态,服务端根据这个请求来更新客户端的最后存在时间,加入超时或者断开连接就代表客户端断线,就会将客户端从对象缓存池中移除。如果web站点更新了对应的配置,服务端会主动发送命名到客户端。命令类似于操作|命名空间|更改值,客户端根据收到的命令触发客户端的配置更新监听并且使用远程api更新客户端的缓存配置。客户端本身会自动更新配置内容,同时提供了一个监听方法便于监听配置的更改。多环境配置在服务端根目录下有一个config目录,这里就是给服务端多环境提供的配置。只需要根据NODE_ENV的值创建对应的文件即可。项目启动的时候会自动根据环境参数使用对应文件的配置。如果你要问客户端的多环境?命名空间已经完全可以实现了。如果要添加更多级的环境参数可以自定义命名空间的命名协议,比如:test.conf1这样的方式即可在不更改主体程序的情况下实现多级配置环境。代价是需要修改web站点的界面。。。。结束语到此一个nodejs版本的轻量级配置中心已经开发完成。如果将上面最开始提到的功能全部完成,这个项目也就不仅仅是一个轻量级的配置中心。它的功能已经完全不亚于其他的开源配置中心了。有兴趣的可以参与进来一起更新最好用的配置中心。项目地址

November 20, 2018 · 1 min · jiezi

手摸手教你使用WebSocket[其实WebSocket也不难]

在本篇文章之前,WebSocket很多人听说过,没见过,没用过,以为是个很高大上的技术,实际上这个技术并不神秘,可以说是个很容易就能掌握的技术,希望在看完本文之后,马上把文中的栗子拿出来自己试一试,实践出真知。游泳、健身了解一下:博客、前端积累文档、公众号、GitHubWebSocket解决了什么问题:客户端(浏览器)和服务器端进行通信,只能由客户端发起ajax请求,才能进行通信,服务器端无法主动向客户端推送信息。当出现类似体育赛事、聊天室、实时位置之类的场景时,客户端要获取服务器端的变化,就只能通过轮询(定时请求)来了解服务器端有没有新的信息变化。轮询效率低,非常浪费资源(需要不断发送请求,不停链接服务器)WebSocket的出现,让服务器端可以主动向客户端发送信息,使得浏览器具备了实时双向通信的能力,这就是WebSocket解决的问题一个超简单的栗子:新建一个html文件,将本栗子找个地方跑一下试试,即可轻松入门WebSocket:function socketConnect(url) { // 客户端与服务器进行连接 let ws = new WebSocket(url); // 返回WebSocket对象,赋值给变量ws // 连接成功回调 ws.onopen = e => { console.log(‘连接成功’, e) ws.send(‘我发送消息给服务端’); // 客户端与服务器端通信 } // 监听服务器端返回的信息 ws.onmessage = e => { console.log(‘服务器端返回:’, e.data) // do something } return ws; // 返回websocket对象}let wsValue = socketConnect(‘ws://121.40.165.18:8800’); // websocket对象上述栗子中WebSocket的接口地址出自:WebSocket 在线测试,在开发的时候也可以用于测试后端给的地址是否可用。webSocket的class类:当项目中很多地方使用WebSocket,把它封成一个class类,是更好的选择。下面的栗子,做了非常详细的注释,建个html文件也可直接使用,websocket的常用API都放进去了。下方注释的代码,先不用管,涉及到心跳机制,用于保持WebSocket连接的class WebSocketClass { /** * @description: 初始化实例属性,保存参数 * @param {String} url ws的接口 * @param {Function} msgCallback 服务器信息的回调传数据给函数 * @param {String} name 可选值 用于区分ws,用于debugger / constructor(url, msgCallback, name = ‘default’) { this.url = url; this.msgCallback = msgCallback; this.name = name; this.ws = null; // websocket对象 this.status = null; // websocket是否关闭 } /* * @description: 初始化 连接websocket或重连webSocket时调用 * @param {*} 可选值 要传的数据 */ connect(data) { // 新建 WebSocket 实例 this.ws = new WebSocket(this.url); this.ws.onopen = e => { // 连接ws成功回调 this.status = ‘open’; console.log(${this.name}连接成功, e) // this.heartCheck(); if (data !== undefined) { // 有要传的数据,就发给后端 return this.ws.send(data); } } // 监听服务器端返回的信息 this.ws.onmessage = e => { // 把数据传给回调函数,并执行回调 // if (e.data === ‘pong’) { // this.pingPong = ‘pong’; // 服务器端返回pong,修改pingPong的状态 // } return this.msgCallback(e.data); } // ws关闭回调 this.ws.onclose = e => { this.closeHandle(e); // 判断是否关闭 } // ws出错回调 this.onerror = e => { this.closeHandle(e); // 判断是否关闭 } } // heartCheck() { // // 心跳机制的时间可以自己与后端约定 // this.pingPong = ‘ping’; // ws的心跳机制状态值 // this.pingInterval = setInterval(() => { // if (this.ws.readyState === 1) { // // 检查ws为链接状态 才可发送 // this.ws.send(‘ping’); // 客户端发送ping // } // }, 10000) // this.pongInterval = setInterval(() => { // this.pingPong = false; // if (this.pingPong === ‘ping’) { // this.closeHandle(‘pingPong没有改变为pong’); // 没有返回pong 重启webSocket // } // // 重置为ping 若下一次 ping 发送失败 或者pong返回失败(pingPong不会改成pong),将重启 // console.log(‘返回pong’) // this.pingPong = ‘ping’ // }, 20000) // } // 发送信息给服务器 sendHandle(data) { console.log(${this.name}发送消息给服务器:, data) return this.ws.send(data); } closeHandle(e = ’err’) { // 因为webSocket并不稳定,规定只能手动关闭(调closeMyself方法),否则就重连 if (this.status !== ‘close’) { console.log(${this.name}断开,重连websocket, e) // if (this.pingInterval !== undefined && this.pongInterval !== undefined) { // // 清除定时器 // clearInterval(this.pingInterval); // clearInterval(this.pongInterval); // } this.connect(); // 重连 } else { console.log(${this.name}websocket手动关闭) } } // 手动关闭WebSocket closeMyself() { console.log(关闭${this.name}) this.status = ‘close’; return this.ws.close(); }}function someFn(data) { console.log(‘接收服务器消息的回调:’, data);}// const wsValue = new WebSocketClass(‘ws://121.40.165.18:8800’, someFn, ‘wsName’); // 这个链接一天只能发送消息50次const wsValue = new WebSocketClass(‘wss://echo.websocket.org’, someFn, ‘wsName’); // 阮一峰老师教程链接wsValue.connect(‘立即与服务器通信’); // 连接服务器// setTimeout(() => {// wsValue.sendHandle(‘传消息给服务器’)// }, 1000);// setTimeout(() => {// wsValue.closeMyself(); // 关闭ws// }, 10000)栗子里面我直接写在了一起,可以把class放在一个js文件里面,export出去,然后在需要用的地方再import进来,把参数传进去就可以用了。WebSocket不稳定WebSocket并不稳定,在使用一段时间后,可能会断开连接,貌似至今没有一个为何会断开连接的公论,所以我们需要让WebSocket保持连接状态,这里推荐两种方法。WebSocket设置变量,判断是否手动关闭连接:class类中就是用的这种方式:设置一个变量,在webSocket关闭/报错的回调中,判断是不是手动关闭的,如果不是的话,就重新连接,这样做的优缺点如下:优点:请求较少(相对于心跳连接),易设置。缺点:可能会导致丢失数据,在断开重连的这段时间中,恰好双方正在通信。WebSocket心跳机制:因为第一种方案的缺点,并且可能会有其他一些未知情况导致断开连接而没有触发Error或Close事件。这样就导致实际连接已经断开了,而客户端和服务端却不知道,还在傻傻的等着消息来。然后聪明的程序猿们想出了一种叫做心跳机制的解决方法:客户端就像心跳一样每隔固定的时间发送一次ping,来告诉服务器,我还活着,而服务器也会返回pong,来告诉客户端,服务器还活着。具体的实现方法,在上面class的注释中,将其打开,即可看到效果。关于WebSocket怕一开始就堆太多文字性的内容,把各位吓跑了,现在大家已经会用了,我们再回头来看看WebSocket的其他知识点。WebSocket的当前状态:WebSocket.readyState下面是WebSocket.readyState的四个值(四种状态):0: 表示正在连接1: 表示连接成功,可以通信了2: 表示连接正在关闭3: 表示连接已经关闭,或者打开连接失败我们可以利用当前状态来做一些事情,比如上面栗子中当WebSocket链接成功后,才允许客户端发送ping。if (this.ws.readyState === 1) { // 检查ws为链接状态 才可发送 this.ws.send(‘ping’); // 客户端发送ping}WebSocket还可以发送/接收 二进制数据这里我也没有试过,我是看阮一峰老师的WebSocket教程才知道有这么个东西,有兴趣的可以再去谷歌,大家知道一下就可以。二进制数据包括:blob对象和Arraybuffer对象,所以我们需要分开来处理。 // 接收数据ws.onmessage = function(event){ if(event.data instanceof ArrayBuffer){ // 判断 ArrayBuffer 对象 } if(event.data instanceof Blob){ // 判断 Blob 对象 }}// 发送 Blob 对象的例子let file = document.querySelector(‘input[type=“file”]’).files[0];ws.send(file);// 发送 ArrayBuffer 对象的例子var img = canvas_context.getImageData(0, 0, 400, 320);var binary = new Uint8Array(img.data.length);for (var i = 0; i < img.data.length; i++) { binary[i] = img.data[i];}ws.send(binary.buffer);如果你要发送的二进制数据很大的话,如何判断发送完毕:webSocket.bufferedAmount属性,表示还有多少字节的二进制数据没有发送出去:var data = new ArrayBuffer(10000000);socket.send(data);if (socket.bufferedAmount === 0) { // 发送完毕} else { // 发送还没结束}上述栗子出自阮一峰老师的WebSocket教程WebSocket的优点:最后再吹一波WebSocket:双向通信(一开始说的,也是最重要的一点)。数据格式比较轻量,性能开销小,通信高效协议控制的数据包头部较小,而HTTP协议每次通信都需要携带完整的头部更好的二进制支持没有同源限制,客户端可以与任意服务器通信与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器结语看了本文之后,如果还是有点迷糊的话,一定要把文中的两个栗子,新建个html文件跑起来,自己鼓捣鼓捣一下。不然读多少博客/教程都没有用,实践才出真知,切勿纸上谈兵。希望看完的朋友可以点个喜欢/关注,您的支持是对我最大的鼓励。博客、前端积累文档、公众号、GitHub以上2018.10.22参考资料:WebSocket 教程理解WebSocket心跳及重连机制WebSocket协议:5分钟从入门到精通 ...

October 25, 2018 · 3 min · jiezi