首页 文章 精选 留言 我的

精选列表

搜索[解耦化],共10020篇文章
优秀的个人博客,低调大师

双点双向重分布导致路由环路,你要怎么解?

加群交流在后台回复“加群”,添加小编微信,小编拉你进去 后台回复“ 724 ”获取入门资料 多个路由协议?再正常不过了 作为一名网络行业从业者,我多么希望面对的网络架构是完美的。 就好比玩游戏可以开挂,要是生活、工作也能开挂多好。 但日常工作中,你会发现企业网络也好,其他类型的网络也好。 总是存在各种瑕疵,各种不和谐。 其中一个不和谐,就是一张网同时由存在多个路由协议互联互通。 再奇葩一点,多个路由协议之间通过多台路由器重分发路由,来提供两两之间的可达性。 此话怎么讲? 让我们来看一个拓扑: 上图中,存在两个路由域,OSPF域以及RIP域, 两个域通过两台路由器互联在-起。 同时,为了交换两个域内的路由,让OSPF内的设备能够与RIP设备互联互通,我们需 要在两台路由器上作双点双向重分发。 什么时候存在多个路由域? 总之,不管什么原因,当两个不同的路由协议同时存在于网络中,并有两台路由器做双 向重分发时。 若不采取人为干涉,网络故障就发生了。 网络故障,什么网络故障,可否说清楚点? 这里故意卖个关子,先看一个案例,你就明白了。 一个诡异的路由问题: 上面以OSPF和RIP组成的拓扑为例讨论此问题。 既然这样,就顺水推舟,采用此拓扑搭建一个实验环境。 让我们一起来看看,当OSPF和RIP两个点互相重分发以后,到底会发什么事情? 拓扑如下: 拓扑简介 上述拓扑中,存在两个路由协议,分别是OSPF (下方) ,RIP (上方)。 OSPF内部有R5,R6四台路由器。 RIP内部有R3,R4四台路由器。 R1和R2则是同时处于RIP和OSPF域中。 路由器之间的IP地址为路由器的数字名称组合而成。例如R1和R3之间的链路则是: 13. 1.1.0/24。 R1是13.1.1.1/24,R3则是13.1.1.3/24, 以此类推,非常容易理解。 为了验证连通性, R4和R6_ 上开启了两个还回接口lo0。 R4的Io0 IP为4.4.4.4/32。R6的lo0 IP为6.6 6.6/32。 重分发说明: 此实验中,R1和R2同时双向重分发OSPF和RIP。即向OSPF里重分发了RIP的路由, 向RIP内重分发了OSPF的路由。 实验目的: 很简单,只需要让R4和R6的I0IP地址互 联互通即可。 实验配置:各路由器配置如下: #####R4: interface Loopback0 ip address4.4.4.4 255.255.255.255 interfaceEthernet0/0 ip address34.1.1.4 255.255.255.0 router rip version 2 network4.0.0.0 network 34.0.0.0 noauto-summary #####R3: interfaceEthernet0/0 ip address13.1.1.3 255.255.255.0 interface Ethernet0/1 ip address23.1.1.3 255.255.255.0 interfaceEthernet0/2 ip address 34.1.1.3 255.255.255.0 router rip version 2 network 13.0.0.0 network 23.0.0.0 network34.0.0.0 no auto-summary ####R1: interface Ethernet0/0 ip address 13.1.1.1 255.255.255.0 interface Ethernet0/1 ip address 15.1.1.1 255.255.255.0 router ospf 1 #重点来了,OSPF重分发RIP redistribute rip subnets network 15.1.1.0 0.0.0.255 area 0 router rip version 2 #RIP重分发OSPF redistribute ospf 1 metric 1 network 13.0.0.0 no auto-summary #####R2 interface Ethernet0/0 ip address 23.1.1.2 255.255.255.0 interface Ethernet0/1 ip address 25.1.1.2 255.255.255.0 router ospf 1 redistribute rip subnets #OSPF重分发RIP network 25.1.1.0 0.0.0.255 area 0 router rip version 2 redistribute ospf 1 metric 1 #RIP重分发OSPF network 23.0.0.0 no auto-summary ####R5 interfaceEthernet0/0 ipaddress25.1.1.5 255.255.255.0 interfaceEthernet0/1 ipaddress15.1.1.5255.255.255.0 interfaceEthernet0/2 ipaddress56.1.1.5255.255.255.0 router ospf 1 network0.0.0.0255.255.255.255 area 0 ####R6 interfaceLoopback0 ip address6.6.6.6 255.255.255.255 interface Ethernet0/0 ip address56.1.1.6 255.255.255.0 router ospf 1 network 0.0.0.0 255.255.255.255 area 0 上述配置浅显易懂,也没有什么复杂的网络技术,简单说来就是在R1和R2上开启了两 个路由协议,并做了互相重分发。 但是,往往越简单的东西,破坏力越大。 一段时间以后,各个路由协议收敛完成。 接下来看看各个路由器的路由表: R4: R3: R1: R2: R5: R6: 上面的路由表,不知道你是否看出来端倪。(悄悄提示:注意4. 4.4.4/32的路由。) 没看出来也无所谓,请继续往下走。 实验结果验证: 接下来进行验证环节。 很简单,验证方法为R6是否能够ping得通R4的还回地址4.4.4.4。 ###R6测试: 以R6的接口56.1.1.6/32为源来ping 4.4.4.4 非常完美 以R6的环回6.6.6。6为源来ping 4.4.4.4 居然不通???? 神奇的事情发生了,R6以56.1.1.6为源ping 4.4.4.4没有问题,但是以6.6.6.6为源ping 4.4.4.4则失败。 为了验证数据包的路径,这一次试试traceroute,如下所示: 采用R6的接口56.1.1.6为源 上述结果没有问题,数据包经过4跳后到达R4。 让我们继续采用R6的环回6.6.6.6为源做traceroute ##哎呀,环路了。 ##怪不得,ping不通呢 从上面的traceroute发现一个很奇怪的问题,当以R6的接口IP地址6.6.6.6为源时, 就出现环路。 “全网路由都正常,4.4.4. 4和6.6 66都在路由表里面,逻辑上没问题。” “莫非此网络还出神灵了不成,估计是Cisco bug,让我们重启路由器试试,不行的话再报case报修"。 这是很多朋友遇到此问题第一时间的反应。 但是,小编喜欢 刨根问底,分析细节。 环路问题分析 首先,从环路的信息开始分析。 上述traceroute环路发生在R1,R2, R3, R5之间, R3做了 一件很诡异的事情。 首先R3收到到达4.4.4 4的数据包,由于R3是唯一一台去往R4的路由器, 逻辑上来讲, 不犹豫R3会把此数据包直接转发给R4。 但是R3不仅没有,反而发送给了R2。 那是什么神奇的力量让R3做如此选择呢?让我们再看看R3的路由表: 有意思,R3去往R4的4.4.4.4居然有两个下一跳。 第一个是去往真正的目的地,即R4。 第二个则是去往R2。 凭什么让R2认为它自己可以到达R4的4.4.4.4? 这次查看R2的路由表: R2从R5学习到的4.4.4.4。 这件事情越来越有意思了。 以此类推,往下追查: . R5的路由: R1的路由: 最后发现,R5的路由来源于R1,而R1的4.4. 44/32则是来源于R3的RIP。 所以R1这里算是回到正轨了。 那为什么R5会有R1传来的4.4.4. 4/32路由,还是OSPF。 答案是:重分发。 看到这里,相信你对于此问题的理解心中已有八九分。 因为上述原因,R3又收到了一份R2发过来的4.4.4.4/32路由。凑巧的是,R3从R4那里 学到的4.4.4.4/32度量值metric为1,而R1在做OSPF>RIP重分发时,也指定了metric 度量值为1。 为此,R3上的4.4.4.4/32同时存在两个下—跳: 现在,我们总算了解清楚为什么R3上有两个下一跳的原因。 接着往下走。 详解R6诡异的ping 上述分析只是解释了R3的下一跳问题,但是开篇提到的最根本问题仍然没有回答。 为什么R6以56.1.1.6为源可以ping 4.4.4.4,但是以6.6.6.6接口地址为源反而不行? 在分析此问题之前,先说说一个背景知识,网络设备如何做负载均衡。 网络设备负载均衡浅谈 简单聊完负载均衡以后,回到话题。 对R3来说,去往4.4.4.4有两个下一跳,一个是正确的下一跳R4,另外一个则是错误的 下—跳R2。 从负载均衡的特性来看,R6通过不同IP地址ping R4的还回地址4.4.4.4,并得到了不同 的结果。 其原因可能就是遭糟遇了负载均衡算法的影响。 那如何证明此猜想是正确的。 还真有办法,方法就是去查阅转发表。 如下所示: #上述内容为转发表FIB针对4.4.4.4/32的表项 # 接下来就是见证奇迹的时刻。 #我们可以通过一个命令来确认R3针对某个IP源、目标地址组合会采用那一个下一跳。 如下所示: #上述命令询问R3,要是一个源地址为56.1.1.6目标地址为4.4.4.4的数据包,你选那一个接口? #R3回答:送它去R4(34.1.1.4) #同理,当源地址为6.6.6.6目标为4.4.4.4的数据包来时。 #R3说,送它去R2(23.1.1.2) 看了上面的输出,瞬间豁然开朗。 原来这就是R6出现如此奇葩i问题的根本原因。 再次证明,当出现某些奇葩问题时,先从每一个环节出发,掌握细节部分,逐个击破。 问题并不—定都是硬件设备故障或者软件BUG。 但是,故事还没有完,请继续。 把偶尔变成必然 综上所述,你可能质疑上述实验是一个“巧合”,现实生活中并不一定存在。 让我逐个澄清: 关于重分发度量值为1的情况,其实日常工作环境中,出现的比例相当高。例如在上述RIP网络中,R4后面可能还有更多的网络节点,而这些节点都通过RIP发布网段到网络中,最终导致R3上肯定有某一条路由和R2“"环路""的方式发布过来的度量值相同。 此时问题就发生了 而针对第二项,的确是一个概率性问题,而其导致的问题就是,你不知道什么时候某一个网段不工作了。 例如上述案例中是6.6.6.6为源到4.4.4.4不通,但是可能某一天又变为56.1.1.6为源到4.4.4.4不通了。 原因是转发表会把之前随机选择的端口记录超时掉,然后重新选择。(就好比打麻将,玩完一局以后洗牌再重来。) 但是,我个人认为上述"所谓的巧合"案例是极其值得分享,至少你遇到此种问题时,知道如何下手分析。 必然发生的网络环路: 最后,再给你演示—个必然发生的网络环路。 再次奉上R3的4.4.4.4路由条目: 你是否考虑过,如果R4哪一天突然不发送4 .4 4.4/32的RIP更新给R3了?例如接口down掉了。 会有什么事情发生? 实际操作一把看看: 再次查看R3的路由表: 恐怖的事情发生了,4.4.4.4/32仍然健在。 此时,网络中任何流量去往4.4.4.4都是像进入网络黑洞环路诞生了。 以R6为例,之前源为56.1.1 .6的IP地址可以与4.4.4.4通讯,现在再看看: R6无法ping通R4的4.4.4.4了,traceroute也证实了环路诞生。 不光是R6,随便挑一个路由器,咱们就挑R3吧,如下所示: R3也环路了!幸好这是实验室,若在现网设备,估计核心交换机都要挂掉了。 让我们简述下上述问题: R4 down掉其lo0的4.4.4.4以后,R3就全盘相信R2发来的4.4.4. 4/32路由,而R2从R5学到此路由,R5又从R1,R1其实也是从R3学来的,R3从R2学习到,以此类推,网络路由环路就诞生了。 相信很多朋友也有类似的经历,无论是计划维护,还是由于意外事故导致某个网段下线了,但是很神奇的是此网段仍然在全网的路由表里面可见,而伴随此奇怪现象的是很多网络设备的CPU利用率开始飙升。若对于某些老旧型号,并未做路由弓|擎防护策略的设备来说,冲垮它是分分钟的事情,包HSRP/VRRP离线,设备失去响应,OSPF邻居down掉等。 环路问题如何解? 对于以上路由环路问题,什么才是最根本的解法? 有人说,谁让你放两台路由器在那里的,还做了双向重分发。放一台不就没这问题了么? 道理上来说,放一台的确解决了此问题。 但是这就如饮鸩止渴,现在的问题解决了,但是更重要的网络高可用性,冗余性就没有了。 所以,两台甚至多台路由器做双向重分发仍然是需要的。 而解决方法,则是路由过滤。 再次复盘上述环路问题。 实问题的根源就在于一个路由协议域内的路由跑到了其他路由协议域内,然后通过另外一台路由器又跑回来了。 这就好比你买了一套有前后门的房子,然后叫家里小孩从家里前门出去超市给你买包烟,结果他刚从前门出去,马上又从后跑进屋玩"吃鸡"了。 唯一阻止的方法就是做标记。 标记如何做? 重点分析RIP -> OSPF方向,下述操作需要在R1和R2同时执行: 首先当R1以及R2重分发RIP路由到OSPF时,采用对应的策略工具把从RIP域里面重分发过来的路由打上一个特定的标记,此标记是一个数字,由你自己定义。(本例的Cisco工具为route-map)通过标记以后,当这些路由到达OSPF域内部时,我们可以很轻松的挑出这些从RIP来的路由。 接下来,当OSPF域里面的路由即将要被另外一台路由重分发回到RIP时,需要使用同样的策略工具,匹配上述定义的标记,从而把原来RIP来的路由网段给挑选出来,并禁止它们再次进入RIP域。(deny拒绝掉) 这样做,RIP域内部的路由器就不会收到重复的网段路由,例如R3就不会再收到从R2发过来的4.4.4.4/32路由,因为此路由在R1重分发OSPF- >RIP时,就被策略工具给过滤掉了。 在本例中,当R3不在收到R2发来的4 4.4.4/32后,任何问题都迎刃而解了。 配置演示: R1和R2配置完全相同,你需要同时在R1和R2上都做如下配置操作,此次仅以R1配置为例: #定义从rip重分布ospf时的策略route-map ,打上标记100。 #定义从ospf重分发到rip时的策略route-map,首先匹配任何标记为100的路由,并把它拒绝。 #同时允许其他所有路由。(这点很重要,因为你需要考虑到那些没有标记100的路由,该如何处置他们。) #最后,把两个route-map策略分别应用到对应的路由协议上: 配置相当简单,但是为了说明这个故障,我们花了非常大的篇幅来讲解故障细节,所以 理解细节是多么的重要。 让我们看看效果: #R3路由表: #R6ping和Traceroute测试: #最后,让我们通过一条命令看看那些被打上标记,挑出来的RIP路由: #此命令在R5上执行,因为R5仅仅加入OSPF域,不会引起阅读上的误解。 #为了简化输出,我特地加入了一个include entry,只挑出带“entry”关键字的输出项。 #你会发现,上述路由网段,全都是RIP的路由 #这也变相证明我们的目的达到了 一切恢复正常, 并没有任何环路。 R6的两个源地址均能顺畅的与R4的4.4.4.4通讯了。 问题圆满解决。 题外话:为什么不标记OSPF重分发的路由? 有朋友发现,上述操作一直针对的是RIP进入OSPF的路由,并给他们打上标记。 为什么不对OSPF进入RIP的路由也打上标记呢? 同样的,你的测试一直针对R4的问题, 难道同样的问题不会发生在R6身上么?即R6的6.6.66是否也会在R5.上存在两个下一跳? 其实这样做不是偷懒,而是有原因的。 因为OSPF和RIP的管理距离问题。 此问题最根本的原因,因为RIP的管理距离120不及OSPF的110来的优先。从而导致R2一方面学习到R3发来的4.4.4. 4/32,同时也学习到了R5发来的路由。 但是因为R5发来的是OSPF E2类型外部路由,虽说是外部E2路由。但是瘦死的骆驼比马驮,外部路由仍然是OSPF路由,AD. 上仍然是110。从而导致R1只认R5传来的4.4.4.4/32,并把它放入路由表,然后进而影响了后续的OSPF->RIP的重分发。 回过头来看R6。 当R1或者R2把R6的6.6.6.6重分发到RIP, R3收到以后,通过RIP发给R1或者R2。R1、R2看都不会看一眼,因为R3的RIP路由相比OSPF来说,弱爆了。(管理距离问题,不是因为长相。) 所以此问题就不会产生,自然而然,我们也不需要做什么过滤。 总结: 此篇文章,通过一个典型的OSPF和RIP双向双重分发的案例,向你讲述了一个经典的路由环路产生的过程,复现率100%。 以及如何通过简单的路由策略过滤解决掉此问题。 用一句话总结此篇文章:当两个路由协议通过多个节点互相重分发时,务必小心管理距离较低的一方,此处一定会产生次优路由,从而在特定环境下导致环路。 解决方法为,当管理距离较低的协议路由经过重分发进入管理距离较高的协议时,务必打上标记。等到从其他节点回来时,过滤掉这些带标签的,禁I上他们再次进入管理距离较低的网络。 END 猜您喜欢 往期精选▼ 华为USG防火墙配置DHCP及单臂路由小实验 Cisco双ISP双链路NAT接入案例 华为-DSVPN 案例配置及原理分析 华为——VRRP基础实验 Cisco私有协议—热备份路由协议HSRP详解 Cisco——HSRP实验案例一 Cisco——VRRP基础实验 Cisco策略路由PBR小案例 Cisco—DMVPN 案例配置及原理分析 思科、华为网络设备破解密码 干货分享:Cisco CCNP视频教程 cisco—RIPv1协议详解 Cisco—静态路由小实验 Cisco-路由器配置DHCP小实验 CISCO--telne小实验 Cisco-VLAN间路由:SVI+单臂路由(子接口) 思科CISCO常用命令汇总 eve-ng模拟ospf虚连接小实验 更多精彩尽在星球 本文分享自微信公众号 - 释然IT杂谈(gh_ad4551519762)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

优秀的个人博客,低调大师

数据库主从不一致,怎么解?

在聊数据库与缓存一致性问题之前,先聊聊数据库主库与从库的一致性问题。 问:常见的数据库集群架构如何? 答:一主多从,主从同步,读写分离。 如上图: 一个主库提供写服务 多个从库提供读服务,可以增加从库提升读性能 主从之间同步数据 画外音:任何方案不要忘了本心,加从库的本心,是提升读性能。 问:为什么会出现不一致? 答:主从同步有时延,这个时延期间读从库,可能读到不一致的数据。 如上图: 服务发起了一个写请求 服务又发起了一个读请求,此时同步未完成,读到一个不一致的脏数据 数据库主从同步最后才完成 画外音:任何数据冗余,必将引发一致性问题。 问:如何避免这种主从延时导致的不一致? 答:常见的方法有这么几种。 方案一:忽略 任何脱离业务的架构设计都是耍流氓,绝大部分业务,例如:百度搜索,淘宝订单,QQ消息,58帖子都允许短时间不一致。 画外音:如果业务能接受,最推崇此法。 如果业务能够接受,别把系统架构搞得太复杂。 方案二:强制读主 如上图: 使用一个高可用主库提供数据库服务 读和写都落到主库上 采用缓存来提升系统读性能 这是很常见的微服务架构,可以避免数据库主从一致性问题。 方案三:选择性读主 强制读主过于粗暴,毕竟只有少量写请求,很短时间,可能读取到脏数据。 有没有可能实现,只有这一段时间,可能读到从库脏数据的读请求读主,平时读从呢? 可以利用一个缓存记录必须读主的数据。 如上图,当写请求发生时: 写主库 将哪个库,哪个表,哪个主键三个信息拼装一个key设置到cache里,这条记录的超时时间,设置为“主从同步时延” 画外音:key的格式为“db:table:PK”,假设主从延时为1s,这个key的cache超时时间也为1s。 如上图,当读请求发生时: 这是要读哪个库,哪个表,哪个主键的数据呢,也将这三个信息拼装一个key,到cache里去查询,如果, cache里有这个key,说明1s内刚发生过写请求,数据库主从同步可能还没有完成,此时就应该去主库查询 cache里没有这个key,说明最近没有发生过写请求,此时就可以去从库查询 以此,保证读到的一定不是不一致的脏数据。 总结 数据库主库和从库不一致,常见有这么几种优化方案: 业务可以接受,系统不优化 强制读主,高可用主库,用缓存提高读性能 在cache里记录哪些记录发生过写请求,来路由读主还是读从 文字很短,不能解决所有问题,但希望能给大家一些启示。

优秀的个人博客,低调大师

每日一博 | 解 Bug 之路-dubbo 应用无法重连 zookeeper

前言 dubbo是一个成熟且被广泛运用的框架。饶是如此,在某些极端条件下基于dubbo的应用还会出现无法重连zookeeper的问题。由于此问题容易导致比较大的故障,所以笔者费了一番功夫去定位,现将排查过程写成博文分享出来。 Bug现场 这是一起在测试环境出现的故障。起因是网工做交换机切换演练,可能由于姿势不对,使得断网的时间从预估的秒级达到了分钟级。等网络恢复后,测试环境就炸开了锅,基本上所有应用再也无法提供服务,在dubbo控制台上也看不到任何提供者,他们和zk的连接都断开而且似乎完全没有重连的迹象。如下图所示: 无法快速恢复 为了不影响测试的进度,运维同学紧急进行了重启,但坑爹的是大部分系统都有启动依赖,盲目的重启只会因为xxx provider不存在而无法启动。只能从最基础的服务开始重启,慢慢恢复。如下图所示: 还好只是测试环境,但为了不让产线出现这种问题,必须一查到底,把这个Bug揪出来。 着手排查 模拟zookeeper连接断开 测试环境的好处是我们可以用各种手段去模拟复现,而不用和处理产线一样到处寻找蛛丝马迹然后进行逻辑推理(推理是一个非常烧脑的过程)。于是笔者联系了SA同学,通过iptables进行线下的断网模拟。命令如下所示: // 禁用本机和zk三台机器的流量进出 iptables -A INPUT -s zk-1-ip/32 -j DROP iptables -A INPUT -s zk-2-ip/32 -j DROP iptables -A INPUT -s zk-3-ip/32 -j DROP iptables -A OUTPUT -s zk-1-ip/32 -j DROP iptables -A OUTPUT -s zk-2-ip/32 -j DROP iptables -A OUTPUT -s zk-3-ip/32 -j DROP 拓扑图如下: 发现在drop对zk的包之后,不管等待多长时间,只要连接一放开,立马就能重连zk! 看来dubbo对zookeeper的重连还是非常靠谱的。 同时模拟DNS断开 由于模拟zk断开不会导致无法重连的现象。于是笔者开始思考,是否交换机异常的时候导致了所有的包都无法发送/接收,而导致重连出问题的并不是对zookeeper发起连接。于是笔者看了看配置,是否还有其它和重连有关联的点,仔细观察下这个配置: // 这其中有一个不容易注意到的点,就是域名解析也需要网络包的交互 dubbo.registry.address=zookeeper://dubbo-1.com?back=dubbo-2.com,dubbo-3.com 难道是DNS访问不到导致了这一问题?反正测试环境,继续模拟一发,命令如下所示: // 禁用本机和zk三台机器的流量进出 iptables -A INPUT -s zk-1-ip/32 -j DROP iptables -A INPUT -s zk-2-ip/32 -j DROP iptables -A INPUT -s zk-3-ip/32 -j DROP iptables -A OUTPUT -s zk-1-ip/32 -j DROP iptables -A OUTPUT -s zk-2-ip/32 -j DROP iptables -A OUTPUT -s zk-3-ip/32 -j DROP // 禁用本机和DNS两台机器的流量进出 iptables -A INPUT -s dns-ip/32 -j DROP iptables -A INPUT -s dns-ip/32 -j DROP iptables -A OUTPUT -s dns-ip/32 -j DROP iptables -A OUTPUT -s dns-ip/32 -j DROP 网络拓扑如下: 这次我们在禁用流量后,故意先放开对zk的流量,再放开对DNS的流量,如下图所示: 看来在dubbo对zookeeper重连过程中,如果DNS也无法响应,是会出现网络恢复后也再也无法重连的现象。但是,我们并不能下判断交换机的故障导致的无法重连肯定是这个Bug引起。需要找到证据来证明这一点! 它山之石,可以攻玉 有了DNS这个信息后,先google一下,看看能否有其它人遇到过这个坑。于是找到了这个链接 https://github.com/sgroschupf/zkclient/issues/23 按照github上的描述,zkclient在UnknownHostException抛出之后再也无法重连zookeeper。不过他是在Kafka中遇到的,但他推断所有用低版本org.apache.zookeper的都会有这个问题。按照上面给出的Bug Fix链接 https://issues.apache.org/jira/browse/ZOOKEEPER-1576 笔者发现其在3.5.0修复 于是将对应的应用的org.apache.zookeeper版本升级到3.5.5版本,重新实验后,发现问题解决了! 这里有个小技巧,我们可以通过 zip -d xxx.jar WEB-INF/lib/zookeeper-3.4.8.jar zip -r 0 xxx.jar WEB-INF/lib/zookeeper-3.5.5.jar // 以及zip -r 其它zookeeper-3.5.5新依赖的包 使得不用重新编译打包的方式即可修改应用使用的jar包版本,这样在快速验证的时候就不需要通知对应的开发修改依赖了。 寻找支持jdk1.6的zookeeper jar包 由于笔者所在的产线环境有很多老系统用的jdk1.6,而zookeeper-3.5.5之支持1.8及以上版本,所以需要寻找能够给jdk1.6使用的包。此时,笔者的同事由于负责kafka,其对kafa做过混沌测试,坚信kafka没有这个问题,于是笔者就用kafka依赖的zookeeper-3.4.13包继续进行测试,发现zookeeper-3.4.13也是okay的,具体代码改动将会在下面讲到。 搜索日志寻找证据 由于可能是UnknownHostException导致了这一问题,笔者在出问题的应用里面寻找,确实发现了UnknownHostException。日志如下所示: // 下面过滤了一大堆disconnected连接断开日志,只列出核心有关的 2020-03-19 21:06:28.926 [DubboZkclientConnector-EventThread] zookeeper state changed (Disconnected) 2020-03-19 21:06:28.926 [ZkClient-EventThread-101-dubbo.com] Zookeeper失去连接 2020-03-19 21:06:49.758 [DubboZkclientConnector-EventThread] zookeeper state changed (Expired) 2020-03-19 21:06:49:759 [DubboZkclientConnecto-SendThread] Unable to reconnect to ZooKeeper sercice ,session 0xXXXXX has expired 2020-03-19 21:07:29.793 [DubboZkclientConnector-EventThread] ERROR ClientCxnn - Error while calling watcher java.lang.RuntimeException: Exception while restarting zk client ...... ...... Caused by: java.net.UnknownHostException: dubbo-1.com at...lookupAllHostAddr... ...... at...StaticHostProvier... ...... at...reconnect...[zookeeper-3.4.8.jar:3.4.8--1] 上面日志反应出在zookeeper session expired之后重新建立session的过程中如果抛出java.net.UnknownHostException后,zkclient对应的线程就再也不会有其它动作。 代码分析 旧版本代码逻辑 上面的证据再配合实验的结果基本就能确定这个DNS异常会导致dubbo无法重连zookeeper的现象。于是笔者开发翻阅代码,首先我们看下dubbo重连的逻辑: public class ZkclientZookeeperClient extends AbstractZookeeperClient<IZkChildListener> { ...... public ZkclientZookeeperClient(URL url) { super(url); client = new ZkClientWrapper(url.getBackupAddress(), 30000); client.addListener(new IZkStateListener() { public void handleStateChanged(KeeperState state) throws Exception { ZkclientZookeeperClient.this.state = state; if (state == KeeperState.Disconnected) { stateChanged(StateListener.DISCONNECTED); } else if (state == KeeperState.SyncConnected) { stateChanged(StateListener.CONNECTED); } } // 这边是session重建的过程 public void handleNewSession() throws Exception { stateChanged(StateListener.RECONNECTED); } }); client.start(); } ...... } // StateListener.RECONNECTED的处理在zookeeperResigry.java中 public class ZookeeperRegistry extends FailbackRegistry{ ...... zkClient.addStateListener(new StateListener() { // 在收到RECONNECTED事件后,进行recover也就是恢复的过程 public void stateChanged(int state) { if (state == RECONNECTED) { try { recover(); } catch (Exception e) { logger.error(e.getMessage(), e); } } } }); ...... } 由上面的代码我们可以得知,在session expired之后内部会重建session,在新建session之后,dubbo的Statelistener会发送reconnected事件从而执行恢复的过程,如下图所示: 那么我们看下UnknownHostException抛出后会导致什么现象,代码如下所示: public class ZkClient implements Watcher { ...... private void processStateChanged(WatchedEvent event) { // 这边对应于session expired日志 LOG.info("zookeeper state changed (" + event.getState() + ")"); setCurrentState(event.getState()); if (getShutdownTrigger()) { return; } try { fireStateChangedEvent(event.getState()); if (event.getState() == KeeperState.Expired) { // UnknownHostException就是在这抛出。 reconnect(); fireNewSessionEvents(); } } catch (final Exception e) { // 这边对应于Error while calling watcher日志 throw new RuntimeException("Exception while restarting zk client", e); } } ...... } 异常是在reconnect抛出的,异常抛出后,就不会运行fireNewSessionEvents这个逻辑,也就不会执行listener中的handleNewSession逻辑,进而不会recover,从而导致dubbo无法重连!如下图所示: 新版本如何修复 由于UnknownHostException是在StaticHostProviver中触发,在这边笔者给出了新旧版本的对应代码,旧版本zookeeper-3.4.8 public final class StaticHostProvider implements HostProvider{ ...... public StaticHostProvider(Collection<InetSocketAddress> serverAddresses) throws UnknownHostException { for (InetSocketAddress address : serverAddresses) { InetAddress ia = address.getAddress(); // 这边没有抓住UnknownHostException异常 InetAddress resolvedAddresses[] = InetAddress.getAllByName((ia!=null) ? ia.getHostAddress(): address.getHostName()); for (InetAddress resolvedAddress : resolvedAddresses) { ...... if (resolvedAddress.toString().startsWith("/") && resolvedAddress.getAddress() != null) { this.serverAddresses.add( new InetSocketAddress(InetAddress.getByAddress( address.getHostName(), resolvedAddress.getAddress()), address.getPort())); } else { this.serverAddresses.add(new InetSocketAddress(resolvedAddress.getHostAddress(), address.getPort())); } } } if (this.serverAddresses.isEmpty()) { throw new IllegalArgumentException( "A HostProvider may not be empty!"); } Collections.shuffle(this.serverAddresses); } ...... } 新版本zookeeper-3.4.13小小的重构了一下,将DNS的逻辑放到next函数里面并抓住了UnknownHostException异常。 public final class StaticHostProvider implements HostProvider{ ...... public InetSocketAddress next(long spinDelay) { currentIndex = ++currentIndex % serverAddresses.size(); if (currentIndex == lastIndex && spinDelay > 0) { try { Thread.sleep(spinDelay); } catch (InterruptedException e) { LOG.warn("Unexpected exception", e); } } else if (lastIndex == -1) { // We don't want to sleep on the first ever connect attempt. lastIndex = 0; } InetSocketAddress curAddr = serverAddresses.get(currentIndex); try { String curHostString = getHostString(curAddr); List<InetAddress> resolvedAddresses = new ArrayList<InetAddress>(Arrays.asList(this.resolver.getAllByName(curHostString))); if (resolvedAddresses.isEmpty()) { return curAddr; } Collections.shuffle(resolvedAddresses); return new InetSocketAddress(resolvedAddresses.get(0), curAddr.getPort()); } catch (UnknownHostException e) { // 就是这边抓住了UnknownHostException进而修复了这一问题 return curAddr; } } ...... } 我们可以看到新版zookeeper-3.4.13抓住了这个UnknownHostException进而修复了这一问题。 BUG触发条件复盘 在与zookeeper服务连接异常并session expired(默认30s),DNS缓存也超时(默认30s)同时用的低版本zookeeper jar包就很容易达成上述Bug的情况。而之前测试环境由于交换机切换的某些原因使得网络断开超过了30s从而诱发了这一问题。而且不仅仅是Dubbo,任何用zookeeper低版本jar包都有可能出现这个问题! 总结 海恩法则指出,每一起严重事故的背后,必然有29次轻微事故和300起未遂先兆以及1000起事故隐患。所以对测试环境的任何问题都要引起重视,从而把问题消灭在萌芽阶段。 公众号 关注笔者公众号,获取更多干货文章: 原文链接 https://my.oschina.net/alchemystar/blog/3222767

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册