dapeng-soa zk watch失效分析

dapeng-soa

1. 背景

系统上线后,一直运行的很稳。 但随着时间的推移,逐渐暴露出一些问题:
1.1. 服务灰度上线后,zk上的服务路由没有同步到服务节点,导致灰度节点收到很多计划外的请求
1.2. 服务升级的时候,明明某服务节点已经从zk上摘除了, 可还是有些请求来到该节点,引发异常。

种种矛头指向了zookeeper的watch。莫非watch失效了?

2. 相关概念

zk session

每个zk 客户端都有个sessionId,用以表明该客户端在zkServer的唯一标识符。
zk客户端跟服务端断开的时候, 会带着这个sessionId去重连zk服务端。
在session 没有timeout之前连上的话, 那么SyncConnected事件会在客户端触发, 且该客户端所有的watcher都会自动重新注册上。
如果session timeout了,那么一方面服务端会清理该session注册的临时节点以及所有的watcher,且如果这时候,客户端带着已经超时的sessionId连接上来的话, 服务端会引发客户端一个Expired事件,然后该客户端的两个线程(io线程跟事件线程)会终止。客户端能做的就是关闭当前客户端,然后重新创建一个客户端再去连接zk。

session是否超时是有服务端判断的。

zk的线程

主要有两个,

  • IO线程(SendThread),负责跟zk服务端的通讯,包括数据收发以及心跳。
  • 事件线程(EventThread),事件产生的时候负责通知用户并回调用户的事件处理逻辑

zk的watcher

watcher有两种,

连接状态监控watcher

这种watcher是创建客户端的时候作为参数的,它负责管理zk客户端跟服务端的连接状态,一般的状态有synconnected, disconnected,expired,AuthFailed

zk数据监控watcher

监控zk上的目录或者节点变化

3. dapeng-soa zk 结构

dapeng-soa 使用zookeeper作为注册中心跟配置中心的核心组件。它包括如下几种节点:

3.1. 服务运行时信息

存放了服务的运行时信息,包括服务名,ip,端口, 版本号以及序号
其中,固定前缀+服务全限定名作为目录:

/soa/runtime/services/com.github.dapeng.service.DemoService

目录下存放具体的节点以及运行时信息:

192.168.10.126:9099:1.0.0:0000000300
192.168.20.133:9099:1.0.0:0000000304
192.168.10.130:9099:1.0.0:0000000305
192.168.20.102:9099:1.0.0:0000000303
192.168.10.131:9099:1.0.0:0000000302

服务运行时信息是zk临时节点,一旦某节点下线,则该节点信息从服务目录中消失。
序号作为服务节点选举master的依据。最早注册的服务节点序号最小,同时也是master节点

3.2. 服务配置信息

服务配置信息存放了dapeng-soa所需要的一些配置项,可通过命令行工具或者配置中心进行管理。
配置信息包括服务路由信息,服务限流配置信息以及其它配置信息(例如负载均衡策略、权重以及超时等)

3.2.1. 服务路由信息

目录路径:

/soa/config/routes

目录中存放各服务节点:

com.github.dapeng.service.DemoService
com.github.dapeng.service.OrderService

而路由信息作为data直接写在服务节点上。例如下面是订单服务的路由信息:

method match r"create.*" => ip"192.168.10.0/24"
cookie_storeId match %"10n+1..6" => ip"192.168.20.128"

详见dapeng-soa路由文档

3.2.2. 限流配置

目录路径:

/soa/config/freq

目录中存放各服务节点:

com.github.dapeng.service.DemoService
com.github.dapeng.service.OrderService

同样,限流信息作为data直接写在服务节点上。例如下面是订单服务的限流信息:

[rule1]
match_app = listOrder # 针对具体方法限流
rule_type = callerIp # 对每个请求端IP
min_interval = 60,5  # 每分钟请求数不超过5次
mid_interval = 3600,100 # 每小时请求数不超过100次
max_interval = 86400,200 # 每天请求数不超过200次

[rule2]
match_app = * # 针对服务限流
rule_type = callerIp # 对每个请求端IP
min_interval = 60,600  # 每分钟请求数不超过600
mid_interval = 3600,10000 # 每小时请求数不超过1万
max_interval = 86400,80000 # 每天请求数不超过8万

详见dapeng-soa限流文档

3.2.3. 服务白名单

只有在白名单中的服务,才对外(三方合作方)开放访问权限。一般应用在服务网关中(dapengMesh)。
目录路径:

/soa/whitelist/services/

目录下存放具体的白名单:

com.github.dapeng.service.DemoService
com.github.dapeng.service.OrderService

4. dapeng-soa zk 变迁史

我们先来了解一下dapeng-soa zk的几个重大重构。

4.1 阶段一 粗放型

新建某服务的客户端的时候,同步该服务在zk上的运行时信息到客户端,并对zk上的运行时目录以及服务配置目录保持同步(新建一个watch,通过watch来监听服务信息的变化)。由于zk的一次性监听机制,处理同步的watch在消费完zk事件后需再次注册。

该方案简单清晰, 但由于我们的服务客户端很轻,且我们鼓励无状态的、对GC友好的编码方式,我们建议应用在调用服务的时候新建客户端,用完丢弃,而不是预先创建客户端缓存在应用中并多次重用。
那么在客户端空闲甚至被gc后,应用无需再继续保持对服务的监听。尤其是集群内部服务很多的情况下,海量的无用的watch监听会让zk不堪重负。

4.2 阶段二 精细型

由于阶段一的粗放型方式会导致无用的zk watch,我们通过java的虚引用机制,让客户端被gc后,通知清理线程取消对zk的watch,从而有效减少了不必要的同步。

4.3 阶段三 watch重用

阶段二的想法是服务客户端被gc后,取消对zk的watch。然而在实际应用中,取消watch并没有直接的api,只能等监听的事件触发消费后,watch才会真正消失。 经过一段时间的观察,我们发现线上watch的数量越来越多,不加节制的创建watch以及watch难以快速真正取消,甚至造成了几次内存溢出。
其实watch本质上也是无状态的,只是对zk 事件的简单同步处理而已。理解了这点后,我们果断以服务为单位缓存watch,一个服务有且仅有一个watch。
经过这个处理之后,watch的数量得到了真正有效的控制, 再也没有出现内存溢出的情况了。

自从进入阶段三以来,原本以为系统进入了坚如磐石的阶段,直到出现本文开头背景介绍中的故障

5. zk watch失效分析

首先查了一下最后一次watch生效之后的日志,发现zk客户端跟服务端的连接短暂的断开了(网络抖动)。
我们对zk连接事件的处理如下:

    /**
     * 连接zookeeper
     */
    private void connect() {
        try {
            CountDownLatch semaphore = new CountDownLatch(1);

            // default watch
            zk = new ZooKeeper(zkHost, 30000, e -> {
                LOGGER.info("ClientZk::connect zkEvent:" + e);
                switch (e.getState()) {
                    case Expired:
                        LOGGER.info("Client's host: {} 到zookeeper Server的session过期,重连", zkHost);
                        zk.close();
                        zk = null;
                        connect();
                        break;
                    case SyncConnected:
                        semaphore.countDown();
                        LOGGER.info("Client's host: {}  已连接 zookeeper Server", zkHost);
                        config.clear();
                        break;
                    case Disconnected:
                        LOGGER.error("Client's host: {} 到zookeeper的连接被断开, do nothing.", zkHost);
                        zk.close();
                        zk = null;
                        connect();
                        break;
                    case AuthFailed:
                        LOGGER.error("Zookeeper connection auth failed ...");
                        zk.close();
                        zk = null;
                        break;
                    default:
                        break;
                }
            });
            semaphore.await(10000, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            LOGGER.info(e.getMessage(), e);
        }
    }

当收到zk连接给断开的事件后,首先会调用close方法主动断开跟zk的连接,然后置空zk客户端,最后重新创建zk客户端。
实际上这个Disconnected事件的处理逻辑是画蛇添足的,收到该事件后,zk客户端会自动重连zk并重新注册所有的watch。 而现在我们是把zk客户端关闭了,那么也就不会有自动重连并注册watch这个过程了。 然后这里做了主动重连。
需要注意的是,主动重连zk后,产生了一个新的zk客户端。 而我们的watch,是注册在老的zk客户端上的。 随着老zk客户端的消亡,这些watch也就失效了。

解决方案很简单, 不要处理Disconnected,同时在处理Expired事件的时候清理所有已存在的watch,关闭zk连接并重新创建zk客户端。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355

推荐阅读更多精彩内容