Dubbo服务健康检查

这个例子主要演示如何实现“RPC模式下的Dubbo服务的健康检查”。

在RPC模式下,Dubbo服务注册后,注册中心(zookeeper)通过zk的客户端和zk服务器之间的连接状态来确认服务提供者的可用性,或者说“Dubbo RPC依赖zookeeper的连接管理来确认服务可用”。因为这种机制,导致在一种情况下注册中心无法识别出provider已经失效:jvm挂起(比如长时间gc),或者服务挂起。此时provider和zk之间的连接是正常的,但是访问provider会超时和失败。这种“基于链路层的健康检查”对于高SLA的业务是不够的,所以需要“服务层”的健康检查。

这里我们采用的例子是Dubbo官方Sample中的zookeeper例子程序,程序可以从Dubbo官方repo下载: https://github.com/apache/dubbo-samples;也可以从我们fork的例子下载https://github.com/flomesh-io/dubbo-samples-zookeeper

版权与免责

本文所述的piped程序为Flomesh团队开发并拥有版权(并非open source software或者freeware),这里作为测试提供。此文档及安装介质可以免费下载用于测试、学习、非盈利目的使用。对于非原文转载,请注明出处(http://flomesh.cn)。piped程序不得作为盈利目的使用。Flomesh团队会维护该文档确保有效性,维护piped程序确保稳定、可靠,但是Flomesh团队不对此文档和该程序的使用负有责任。

勘误,或者有不同意见和想法,或者需要交流的,可以给我们发邮件:support@polaristech.io,或者加微信13700113993

方案原理

该方案的原理是和Dubbo的服务进程一起部署一个piped的进程,把Dubbo的服务通过piped暴露成REST接口;用一个定时脚本去访问该REST接口,如果连续的出现了服务访问失败,则发出告警信息,比如发email,同时也可以采取“服务恢复”的操作--强制杀掉Dubbo服务进程,重启。

拓扑与准备

这个演示的拓扑非常简单:

  1. 独立运行的zookeeper进程,监听2181端口,没有访问控制
  2. 运行在elicpse中的Dubbo服务provider和consumer,provider监听20880端口
  3. 独立运行的piped进程,在8080端口提供REST服务,把REST请求转发到运行在eclipse中的服务provider(端口20880)
  4. 健康检查脚本,通过crond触发执行,每分钟一次。脚本中使用curl访问8080端口,检查返回结果,如果连续3次错误,则调用sendmail命令发出告警邮件

获取演示代码

首先获取dubbo-sample-zookeeper演示代码:

git clone git@github.com:flomesh-io/dubbo-samples-zookeeper.git

获取piped程序

piped程序是Dubbo网关的核心程序

wget http://repo.polaristech.io/piped/piped-0.1.0-62.el7_pl.x86_64.rpm
yum -y localinstall piped-0.1.0-62.el7_pl.x86_64.rpm

或者

wget http://repo.polaristech.io/images/piped-alpine-0.1.0-62.tar.gz

Flomesh团队推荐使用RHEL7或者衍生版本(如centos7)运行piped

获取zookeeper

这个演示使用的是zookeeper 3.4.13版本:

wget 
https://archive.apache.org/dist/zookeeper/zookeeper-3.4.13/zookeeper-3.4.13.tar.gz

获取eclipse

演示使用的是linux版本的eclipse,和其他版本没有区别:

http://mirror.csclub.uwaterloo.ca/eclipse/technology/epp/downloads/release/2019-12/R/eclipse-java-2019-12-R-linux-gtk-x86_64.tar.gz

运行zookeeper

运行zookeeper非常简单:

tar xzvf zookeeper-3.4.13.tar.gz
cd zookeeper-3.4.13
cp conf/zoo_sample.cfg conf/zoo.cfg
bin/zkServer.sh start

确认zookeeper监听在2181端口:

root@localhost:~/zookeeper-3.4.13$ netstat -ntlp | grep java
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
tcp6       0      0 :::2181                 :::*                    LISTEN      4534/java           
tcp6       0      0 :::41707                :::*                    LISTEN      4534/java      

运行Dubbo网关(piped)

准备一个如下的配置文件,我把这个放在/etc/piped/healthcheck.ini:

[pipeline.http]
listen = 127.0.0.1:8080
[module.1]
name = http-request-decoder
[module.2]
name = json-decoder
[module.3]
name = hessian2-encoder
[module.4]
name = dubbo-request-encoder
[module.5]
name = proxy
upstream = 127.0.0.1:20880
[module.6]
name = dubbo-response-decoder
[module.7]
name = hessian2-decoder
[module.8]
name = json-encoder
[module.9]
name = http-response-encoder

启动piped:

[root@piped piped]# piped /etc/piped/healthcheck.ini 
Loading configuration from file /etc/piped/healthcheck.ini
---
Pipeline [http] listening on 127.0.0.1:8080
  - http-request-decoder [1]
  - json-decoder [2]
  - hessian2-encoder [3]
  - dubbo-request-encoder [4]
  - proxy [5]
      .upstream: 127.0.0.1:20880
  - dubbo-response-decoder [6]
  - hessian2-decoder [7]
  - json-encoder [8]
  - http-response-encoder [9]
---
Loaded 1 pipeline(s)
Wed Mar 18 17:36:13 2020 [info] Listening on 127.0.0.1:8080

启动Provider

把dubbo-sample-zookeeper项目导入eclipse,然后打开ProviderBootstrap.java,然后“运行”,可以看到服务注册到zookeeper,并且服务处于启动状态: 启动Provider

确认一下20880端口已经监听:

root@localhost:~# netstat -ntlp | grep java
tcp6       0      0 :::2181                 :::*                    LISTEN      4534/java           
tcp6       0      0 :::41707                :::*                    LISTEN      4534/java           
tcp6       0      0 :::22222                :::*                    LISTEN      13512/java          
tcp6       0      0 :::20880                :::*                    LISTEN      13512/java         

CURL访问healthcheck接口

这个时候我们用curl访问piped的8080端口,就可以获取Dubbo服务器的返回了。不过因为Dubbo RPC在访问的时候需要“描述接口”,所以我们准备如下一个json文件,作为curl POST的内容,我们把这个文件放在/etc/piped/healthcheck.json,内容如下:

[
  "2.0.2",
  "org.apache.dubbo.samples.api.GreetingService",
  "1.0.0",
  "healthCheck",
  "",
  {
    "path": "org.apache.dubbo.samples.api.GreetingService",
    "remote.application": "healthchecker",
    "interface": "org.apache.dubbo.samples.api.GreetingService",
    "version": "1.0.0",
    "timeout": "3000"
  }
]

Tips: 这个JSON是服务“org.apache.dubbo.samples.api.GreetingService”的"healthCheck"方法的描述。这个JSON的格式是Dubbo RPC协议的JSON表达,关于对于不同的服务,如何获得这个JSON,可以参考"接口录制"文档

我们把这个JSON用CURL发送给piped:

[root@piped piped]# curl -i -X POST http://localhost:8080/ --data @/etc/piped/healthcheck.json
HTTP/1.1 200 OK
Content-Length: 51
x-dubbo-request-id: 0
x-dubbo-status: 20
x-dubbo-event: 0
x-dubbo-2-way: 0

[
  4,
  "OK",
  {
    "dubbo": "2.0.2"
  }
]

可以看到请求成功,放回的内容也是一个JSON,其中第二个字段是“OK”,这个是Dubbo Provider的healthCheck方法的返回值。

源码中AnnotatedGreeting.java的33~35行是healthCheck方法

通过判断这个curl的HTTP Response Code是否为200,并且返回JSON里返回字符串是“OK”,我们就可以判断Dubbo Provider当前正常响应服务请求。

用脚本定时执行检查

这个部分留给感兴趣的自己实现~


采用已有的服务做healthCheck

刚才的例子中,我们定义了一个healthCheck方法,然后通过REST来访问。其实也可以直接访问已有的Dubbo服务,接下来我们演示如何访问例子中已有的sayHello方法。

和上边访问healthCheck唯一的不同,是POST的JSON内容,访问sayHello接口用这个JSON,我们把内容保存在/etc/piped/sayHello.json :

[
  "2.0.2",
  "org.apache.dubbo.samples.api.GreetingService",
  "1.0.0",
  "sayHello",
  "Ljava/lang/String;",
  "piped",
  {
    "path": "org.apache.dubbo.samples.api.GreetingService",
    "remote.application": "zookeeper-demo-consumer",
    "interface": "org.apache.dubbo.samples.api.GreetingService",
    "version": "1.0.0",
    "timeout": "3000"
  }
]

然后使用curl把这个JSON文件POST给piped:

[root@piped piped]# curl -i -X POST http://localhost:8080/ --data @/etc/piped/sayHello.json
HTTP/1.1 200 OK
Content-Length: 61
x-dubbo-request-id: 0
x-dubbo-status: 20
x-dubbo-event: 0
x-dubbo-2-way: 0

[
  4,
  "hello, piped",
  {
    "dubbo": "2.0.2"
  }
]

可以看到REST的返回代码是200,sayHello接口调用返回的字符串“hello, piped”。

这样,对于已有的存量Dubbo服务,即使不做任何代码改动,只要做如下的操作,就可以实现“服务级的健康检查”:

  1. 获取接口描述的JSON。可以参考“接口录制”文档
  2. 把piped和服务提供者搭配部署(实际是一种sidecar部署),把服务暴露成REST
  3. 配置定时任务用curl去访问piped的REST端口
  4. 判断curl的返回结果,如果发现服务异常就做相应的处理:告警,并且重启服务等