2
头图

一、前言

随着人们的生活水平的不断提高,人们对身体健康越来越重视,很多人都做过体检,一般公司都会有一年一度的体检福利,健康体检是家喻户晓了。

随着互联网的快速发展,同类同质产品之间的竞争越来越大,产品之间一个重要的差异就是用户体验。影响用户体验的,除了产品设计因素外,技术层面也是一个重要的影响因素,主要体现在服务的可用性和响应速度。提升服务可用性和响应速度如此重要,为了实现这样的目标,必须要有相应的手段,其中健康检查就是保障服务可用性和快速响应一个非常重要的前提。

健康检查有哪些项目、指标和方法呢?此文带你一一揭晓。

二、什么是健康检查

健康体检是指通过医学手段和方法对受检者进行身体检查,了解受检者健康状况、早期发现疾病线索和健康隐患的诊疗行为。而系统的健康检查是利用技术手段检测网络、主机、应用、服务等一系列对象是否健康或可用的过程。

三、为什么需要做健康检查

互联网产品对用户体验提出了很高的要求,但常常由于技术侧原因,发生服务响应慢或者服务不可用等一系列影响用户体验的问题,导致业务中断,影响收入,公司品牌和口碑也会受到巨大的负面影响。

影响服务不可用和响应慢的因素很多,可能是服务硬件损坏、光纤被挖断,可能是请求量过大导致数据库CPU负载、磁盘IO过高,又可能是某同学埋了雷,新上线的功能第一次运行就发生了OOM……

要保证系统高可用,我们应该怎么做呢?有人说,系统节点冗余消除单节点故障不就行了吗。说的没错,消除单节点是系统高可用的常用手段。消除单节点有一个很重要的前提是发现问题节点,把问题节点踢除或者把流量切换到其他正常节点。

如何“发现问题节点”,就是系统健康检查需要做的事情。

四、如何做健康检查

谈论如何做健康检查前,首先要弄明白的是要检查的对象究竟是谁。对象可以网络连接,可以是一个小小的功能组件,可以是一个进程,可以是服务集群,也可以是机房单元。所以,要做到“高可用”,首先要弄清楚要做哪层面的高可用,哪些对象可能存在单点问题,要把“对象”搞清楚。

那么,健康检查如何做呢?通常有两种方式:主动和被动。

4.1 主动模式

由检查方作为主动方,定时主动发起健康检查请求,请求的报文内容或者格式通常是独立设计的,被健康的对象作简单自检后返回响应。举个例子:

check interval=3000 rise=2 fall=5 timeout=1000 type=http;
check_http_send "HEAD /check.do HTTP/1.0\r\n\r\n";
check_http_expect_alive http_2xx http_3xx;

配置间隔2000豪秒定时向后台web服务器http://(ip:port)/check.do接口...

4.2 被动模式

被动健康检查不设计独立的健康检查请求,而是以正常连接情况或者业务请求的响应作为指标来衡量检查对象的健康状态。例如nginx官方开源版本的被动健康检查配置:

server 127.0.0.1:8080       max_fails=3 fail_timeout=30s;

Nginx是基于连接探测,如果在30s内尝试连接3次失败,则认为后端web服务不可用。

4.3 消除单点

上面谈到,要实现高可用就要消除单点故障,最简单直接的方案加备服务节点,通过定时心跳健康检查发现主服务节点宕机后,备服务节点把主的工作接管过来,客户端把请求流量切换到备服务节点。

主服务节点与备服务节点之间通过专用的心跳线进行健康检查,由于网络分区等原因它们可能无法收到对方心跳,这时备节点会认为主节点已宕机,主节点也认为备节点已宕机,但其实主从两节点状态都是正常的,客户端能正常访问到主从两节点,出现“双写”,这种现象在业界称为“脑裂(split-brain)”。

出现脑裂会导致数据混乱的灾难事件发生,影响业务的正确性,这时引入第三方机构进行仲裁可以有效避免脑裂的发生。出现脑裂会导致数据混乱的灾难事件发生,影响业务的正确性,这时引入第三方机构进行仲裁可以有效避免脑裂的发生。

4.4 第三方仲裁

既然主从双方无法确认对方的存活,出现争议时可以由第三方仲裁节点做出决定,到底谁是主由它说了算,第三方仲裁节点一般是由Zookeeper这种高可用方案来实现。

五、健康检查例子

5.1 网络设备

Keepalived是一款保证集群高可用的服务软件,其功能类似于heartbeat,用于防止单点故障。但是它一般不会单独出现,而是与其它负载均衡技术(如LVS、HAProxy、Nginx)一起工作来达到集群的高可用。

它的健康检查也包含两个方面,一个是Keepalived组件之间的健康检查(通过VRRP心跳报文),如下图所示

另一个是Keepalived组件与本地负载均衡组件的健康检查,配置如下:

vrrp_script check_nginx_running {
    script "/usr/local/bin/check_running"(定义脚本)
    interval 10(脚本执行的间隔)
    weight -10(脚本执行的优先级)
}

其中,应用的健康检查方式通过自定义脚本实现。

Keepalived组件之间通过VRRP协议进行健康检查,如果主服务器宕机,备服务器通过VRRP协议选举成为新的主服务器,把虚拟IP从旧的主服务器上争抢过来,实现高可用。

VRRP报文是封装在IP报文上的,支持各种上层协议,网络设备通常也是使用VRRP协议实现主备高可用切换,如交换机、路由器、防火墙等。

当网络设备发生故障时,VRRP机制能够选举出新的网络设备承担数据流量,从而保障网络的可靠通信。

5.2 网络连接

移动设备连接互联网通过NAT方式,移动App的PUSH推送需要与服务器保持长连接,但大部分移动网络运营商都在连接一段时间没有数据交互时,会淘汰 NAT列表中的对应连接,造成连接中断。为了保持网络连接的“健康”可用,我们可以在连接建立后,App与服务器互相定期发送Ping Pong心跳信息来保持连接的持续有效。

以上是应用层的连接健康检查方案,操作系统也支持底层网络的连接健康检查即Keepalive。TCP Keepalive可以在连接无活动一段时间后,发送一个空的探测报文,使TCP连接不会被客户端或者防火墙等中间网络设备关闭。Linux可以通过以下三个参数对Keepalive的间隔、频率和阈值和进行配置:

net.ipv4.tcp_keepalive_time = 7200
net.ipv4.tcp_keepalive_intvl = 75
net.ipv4.tcp_keepalive_probes = 9

5.3 主机与进程

主机之间的可达性可以通过Ping命令进行识别,Ping命令使用的是ICMP协议,它能识别从客户端到目标主机整个路径的网络连通性。Ping通常用于手工测试某台主机是否启动和网络是否联通。

ICMP是网络层协议,与具体进程是没关系的,无法通过Ping识别进程是否存在。但进程有端口,有进程信息,可以通过telnet端口或ps命令检测进程是否存在。进程可能会由于内存不足被kill或者其他原因异常关闭,可以通过cron定时脚本检测识别后自动拉起,这种方案对老破旧项目中只能单实例部署的应用的可用性提升非常有效。

5.4 中间件-RocketMQ

NameServer是RocketMQ的路由中心,NameServer中维护着Producer集群、Broker集群、 Consumer集群的服务状态和路由信息。当有新的Consumer加入集群时,除了上报自身信息外,还获取各个Broker的地址、Topic、队列等信息,这样就能知道它消费的Topic消息存储到哪个Broker和队列上。

NameServer可以部署多个,NameServer之间相互独立不互通。Producer、Broker、Consumer服务启动时需要指定多个NameServer,服务的信息会同时注册到多个指定的 NameServer上,达到高可用。

每个Broker节点与所有NameServer会保持TCP长连接,每隔30s给NameServer发送心跳报文,告诉NameServer自己还活着。而每个NameServer每隔10s检查一下各个Broker的最近一次心跳时间,如果发现某个Broker超过120s都没发送心跳报文,就认为这个Broker已经宕机了,会关闭对应的网络连接channel,并将其从路由信息里移除。

5.5 应用层 - Spring Boot Actuator

一个服务实例或者进程会通过定期的心跳包向其他服务来报告它的存活,但有这个心跳包还是不够的,不足以反映它的健康状况。比如磁盘空间不足了,服务已经无法再写数据了,但它还能响应心跳包;服务依赖Redis,但Redis服务出了问题连接不上,但它还能响应心跳包;服务的某些功能依赖分布式存储服务,但分布式存储服务不可用了,但它依然能响应心跳包。我们可以看到,要确定一个服务实例是否存活并且“健康”,还是有很多方面需要考虑的。Spring Boot Actuator能比较好的解决这个问题,它能反映整个服务的健康状况,包括它所依赖的子系统的健康状况。

Spring Boot Actuator是Spring Boot的一个子项目,Actuator提供Endpoint(端点)给外部应用程序进行访问和交互。Actuator包括许多功能,比如健康检查、审计、指标收集等等,可帮助我们监控和管理Spring Boot应用程序。Health就是其中一个Endpoint,它提供了关于Spring Boot应用的基本健康情况信息,允许其他云服务或者k8s等定时检测到应用的健康状况,对异常情况及时作出响应。

假如某微服务应用使用到了MySQL、Amazon S3、Elastic Search、DynamocDB这些资源系统,它的健康检查结果就应该包含所有这些子系统的健康状况:

Actuator的健康检查由HealthIndicator接口实现,HealthIndicator接口只有一个health()方法,返回值是Health健康对象。

@FuncationalInterface
public class HealthIndicator {
 
    /**
     * Return an indication of health.
     * @result the health for
     */
    public Health health();
 
}

Health对象有状态status和details两个字段,status默认有UNKNOWN、UP、DOWN和OUT\_OF\_SERVICE四个值,用户可以自定义和扩展,details是一个KV结构,用户可以随意自定义要返回的数据值。

@JsonInclude(Include.NON_EMPTY)
public final class Health extends HealthComponent {
 
    private final Status status;
 
    private final Map<String, Object> details;
   
    ...
 
}

Actuator内置了很多常用的HealthIndicator:

用户可以根据实际情况自定义,比如:

@Override
public Health health() {
    int errorCode = check(); // perform some specific health check
    if (errorCode != 0) {
        return Health.down().withDetail("Error Code", errorCode).build();
    }
    return Health.up().build();
}

默认情况下health的状态是启用且对外开放的,通过http://locahost:8080/actuator... “UP”},这是一个汇总的状态,详细的健康信息可以通过配置项management.endpoint.health.show-details=always打开,一个完整的包含details的健康检查信息如下:

汇总的健康状态由 HealthAggregator 汇总而成的,汇总的算法是:所有子系统的健康状态按DOWN、OUT\_OF\_SERVICE、UP、UNKNOWN这个顺序进行排序取最前面一个状态值。

比如ehCache是UP,MySQL是UNKNOWN,diskSpace是OUT\_OF\_SERVICE;那么排序下来就是:OUT\_OF\_SERVICE、UP、UNKNOWN,取第一个就是OUT\_OF\_SERVICE,即服务不可用。

六、总结

高可用是一个很复杂的工程问题,它是由一系列的子问题构成,健康检查和健康度量只是其中一个。业务要保持连续不中断,系统要保证持续运行,就要保证全链路所有参与的节点都是高可用的,避免出现单点故障。

如何及时发现不健康或故障的节点并告警,如何在节点出现不健康或故障时及时failfast/failover避免发生雪崩效应,健康检查在其中扮演着非常重要的作用。

作者:vivo 互联网服务器团队-Chen Jianbo

vivo互联网技术
3.3k 声望10.2k 粉丝