为什么不要把zookeeper 重启服务用于服务发现

为什么不使用ZooKeeper_百度知道使用etcd watch做服务发现监控时遇到的坑 | 峰云就她了
5,494 views
最近在公司折腾业务数据监控的事,听上面的意思,要把这监控做成部门的数据监控平台,接入各种各样的数据流监控,一听这目标,霎时间就不想继续开发了… 以前参与开发过基础的监控系统,那也是个巨坑的玩意,自从被伤过后,我有些抵触监控这玩意了… & 不扯了,世界是美好的,我要心情好好的….
在监控系统中,使用etcd做服务发现监控的文章,原文地址是&
开发的进度已经到了服务发现了,在选择zookeeper和etcd的时候,我还真犹豫了,虽然两个都曾用过,但是长久不用就都忘了,又查了下两者的对比资料,发现在服务发现方面,两者都差不多的。只是zookeeper有Ephemeral的概念,Ephemeral结点在Zookeeper中是一个临时结点,这些结点只要创建它的结点session不挂,它就一直存在,当session中止了,比如客户端进程挂掉了,那么在zookeeper的结点也就被删除了。 etcd不支持ZooKeeper的ephemeral临时节点的概念,要监控服务的状态似乎比较麻烦。 哥还在github etcd issue里,问了那个xiang90 (中国人)….
好了,不能只看别人zookeeper vs etcd的评价,人云亦云不是我的风格,昨天花了点时间把监控的服务发现功能在zookeeper和etcd都测试了下,结果他们都适合服务注册发现…. &虽然zookeeper公司有专门的集群,但是真是不想接入他们…. & 怎么简单怎么来,那就用etcd吧,etcd的设计理念跟运维都是比较简单的。
下面是我以前做测试的效果图,为了避嫌,我把图表的数据都裁剪了下,反正看明白他的意思就行了,绿色代码OK,红色代表问题。&
关于模块的服务注册我是这么设计的 ~
/buzz/buzzadmin/download
/buzz/buzzadmin/api
buzz是大项目,buzzadmin是子项目,api 跟 download都是子项目中的两个模块。 这样就有了层级的关系。 /A/B/C &,B死了,C也就无意义了。 A死了,B也就完了,下层受限于上层。
etcd和zookeeper kv的存储本来就是这种树形目录,很适合我上面的监控需求设计。&
我这里放两个python操作etcd服务的小例子。
set client: &(这个写入的客户端)
import etcd
client = etcd.Client()
for i in range(100):
client.write('/nodes/n%s'%i, 1,ttl=10)
&import etcdclient = etcd.Client()c = 0for i in range(100):&& client.write('/nodes/n%s'%i, 1,ttl=10)&& c += 1&& print i
watch client: (这个是监控端)
import etcd
client = etcd.Client()
print client.watch('/nodes/',recursive=True)
&import etcdclient = etcd.Client()&c = 0 while 1:&&&&print client.watch('/nodes/',recursive=True)&&&&c += 1&&&&&&&&print c
这样会造成什么问题? &我想大家在看我watch client代码的时候,估计发现了问题所在。&
写入端比如进行了100个set操作,以为他有ttl的配置,所以当他expire过期的时候,我的watch也是会收到请求的。 我一开始没有找到etcd watch有那样驻守监控的函数,所以就用以前监控socket recv那样,使用while循环地调用clent.watch() 。 &这样造成的问题是,我这边收到节点反馈后,再去注册监听,这时候会丢失很多etcd反馈的信息的。 & 再来看看他丢失了多少,写入段100个set,还有100个expire的action。watch理应说到200个操作,结果只有150个,也就是丢失了50个。
找了下资料,看到一老外在 上说,他也是用while,一刹那间,眼前漂出一行字,caonima…..
最后直接看python-etcd的代码,原以为他会写的复杂,结果这代码只是http api的封装罢了。 &下面代码是python-etcd里面关于watch监控的描述。注意有个eternal_watch,只是看字面的一面就知道他是我们要找寻找的,他构造了Generator生成器,然后用yield关键字返回。
另外watch函数其实就是self.read(self,wait=True)
def eternal_watch(self, key, index=None, recursive=None):
Generator that will yield changes from a key.
Note that this method will block forever until an event is generated.
key (str):
Key to subcribe to.
index (int):
Index from where the changes will be received.
client.EtcdResult
local_index = index
while True:
response = self.watch(key, index=local_index, timeout=0, recursive=recursive)
local_index = response.modifiedIndex + 1
yield response
def watch(self, key, index=None, timeout=None, recursive=None):
_log.debug("About to wait on key %s, index %s", key, index)
return self.read(key, wait=True, waitIndex=index, timeout=timeout,
recursive=recursive)
return self.read(key, wait=True, timeout=timeout,
recursive=recursive)
def read(self, key, **kwdargs):
_log.debug("Issuing read for key %s with args %s", key, kwdargs)
key = self._sanitize_key(key)
params = {}
for (k, v) in kwdargs.items():
if k in self._read_options:
if type(v) == bool:
params[k] = v and "true" or "false"
elif v is not None:
params[k] = v
timeout = kwdargs.get('timeout', None)
response = self.api_execute(
self.key_endpoint + key, self._MGET, params=params,
timeout=timeout)
return self._result_from_response(response)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
&def eternal_watch(self, key, index=None, recursive=None):&&&&"""&&&&Generator that will yield changes from a key.&&&&Note that this method will block forever until an event is generated.&&&&Args:&&&&&&&&key (str):&&Key to subcribe to.&&&&&&&&index (int):&&Index from where the changes will be received.&&&&Yields:&&&&&&&&client.EtcdResult&&&&"""&&&&local_index = index&&&&while True:&&&&&&&&response = self.watch(key, index=local_index, timeout=0, recursive=recursive)&&&&&&&&local_index = response.modifiedIndex + 1&&&&&&&&yield response&&def watch(self, key, index=None, timeout=None, recursive=None):&&&&&_log.debug("About to wait on key %s, index %s", key, index)&&&&if index:&&&&&&&&return self.read(key, wait=True, waitIndex=index, timeout=timeout,&&&&&&&&&&&&&&&&&&&&&&&& recursive=recursive)&&&&else:&&&&&&&&return self.read(key, wait=True, timeout=timeout,&&&&&&&&&&&&&&&&&&&&&&&& recursive=recursive)&&def read(self, key, **kwdargs):&&&&&_log.debug("Issuing read for key %s with args %s", key, kwdargs)&&&&key = self._sanitize_key(key)&&&&&params = {}&&&&for (k, v) in kwdargs.items():&&&&&&&&if k in self._read_options:&&&&&&&&&&&&if type(v) == bool:&&&&&&&&&&&&&&&&params[k] = v and "true" or "false"&&&&&&&&&&&&elif v is not None:&&&&&&&&&&&&&&&&params[k] = v&&&&&timeout = kwdargs.get('timeout', None)&&&&&response = self.api_execute(&&&&&&&&self.key_endpoint + key, self._MGET, params=params,&&&&&&&&timeout=timeout)&&&&return self._result_from_response(response)
另外说下通过第一层key,取出所有下层key的方法…
directory = client.get("/nodes")
#可以取出所有
for result in directory.children:
print(result.key + ": " + result.value)
#只能取出第一个
print(directory.children.next().value)
&directory = client.get("/nodes")&#可以取出所有for result in directory.children:&&print(result.key + ": " + result.value)&#只能取出第一个print(directory.children.next().value)
到此,我要讲述的坑就扯完成了…. & etcd是个好东西,够简单,以前做配置集中管理的时候有用过etcd,虽然后期替换成zookeeper了。另外我没在线上用过etcd集群,但在社区中看到不少人在用,反馈也不错。 &总之,etcd值得一用…..&
另外这里记录etcd的安装方法,环境是linux,以前记录的文档没了,就贴在这里吧。&
/coreos/etcd/releases/download/v2.1.3/etcd-v2.1.3-linux-amd64.tar.gz -o etcd-v2.1.3-linux-amd64.tar.gz
tar xzvf etcd-v2.1.3-linux-amd64.tar.gz
cd etcd-v2.1.3-linux-amd64
&curl -L&&https://github.com/coreos/etcd/releases/download/v2.1.3/etcd-v2.1.3-linux-amd64.tar.gz -o etcd-v2.1.3-linux-amd64.tar.gztar xzvf etcd-v2.1.3-linux-amd64.tar.gzcd etcd-v2.1.3-linux-amd64./etcd
或者是直接用docker启动
docker run -p
-v /usr/share/ca-certificates/:/etc/ssl/certs quay.io/coreos/etcd:v2.1.3
&docker run -p 2379:2379 -v /usr/share/ca-certificates/:/etc/ssl/certs quay.io/coreos/etcd:v2.1.3
另外需要注意的是,docker默认启动是绑定在本地的4001端口,如果想绑定所有的网卡上,也就是0.0.0.0 ,可以./etcd -addr 0.0.0.0:4001
etcd解压的时候,貌似没有默认的配置文件,有兴趣的朋友可以用我的etcd 配置文件。
addr = "127.0.0.1:4001"
bind_addr = "127.0.0.1:4001"
ca_file = ""
cert_file = ""
cpu_profile_file = ""
data_dir = "/var/xiaorui.cc/data/"
discovery = "http://etcd.local:4001/v2/keys/_etcd/registry/examplecluster"
http_read_timeout = 10.0
http_write_timeout = 10.0
key_file = ""
peers = []
peers_file = ""
max_result_buffer = 1024
max_retry_attempts = 3
name = "default-name"
snapshot = true
verbose = false
very_verbose = false
addr = "127.0.0.1:7001"
bind_addr = "127.0.0.1:7001"
ca_file = ""
cert_file = ""
key_file = ""
active_size = 9
remove_delay = 1800.0
sync_interval = 5.0
1234567891011121314151617181920212223242526272829303132
&addr = "127.0.0.1:4001"bind_addr = "127.0.0.1:4001"ca_file = ""cert_file = ""cors = []cpu_profile_file = ""data_dir = "/var/xiaorui.cc/data/"discovery = "http://etcd.local:4001/v2/keys/_etcd/registry/examplecluster"http_read_timeout = 10.0http_write_timeout = 10.0key_file = ""peers = []peers_file = ""max_result_buffer = 1024max_retry_attempts = 3name = "default-name"snapshot = trueverbose = falsevery_verbose = false&[peer]addr = "127.0.0.1:7001"bind_addr = "127.0.0.1:7001"ca_file = ""cert_file = ""key_file = ""&[cluster]active_size = 9remove_delay = 1800.0sync_interval = 5.0
如果大家觉得文章对你有些作用! &
帮忙点击广告. 一来能刺激我写博客的欲望,二来好维护云主机的费用.
如果想赏钱,可以用微信扫描下面的二维码. 另外再次标注博客原地址 && …… &&感谢!
您可能也喜欢:
上手在github提交了python gevent etcd的非阻塞模块。 正好这两天寂寞的很,打算再重写下etcd 输入更改数据这块的逻辑。&搜相关文档的时候,发现...
起因,我这边有个服务端,是专门控制服务发现和注册功能,任务的调度都是用gevent来实现,如果直接在gevent里使用python-etcd,会发生阻塞的,gevent对于那...
熟悉我博客的人知道我挺喜欢折腾新东西的,马上就要10 1 放假了,大家都没心思忙工作 ! &一堆的需求都让我给他扔到脑后面了。 &最近看了一些微...zookeeper 大量联接断开重连原因排查 - 互联网当前位置:& &&&zookeeper 大量联接断开重连原因排查zookeeper 大量联接断开重连原因排查&&网友分享于:&&浏览:0次zookeeper 大量连接断开重连原因排查
最后发现线上的zookeeper的日志zookeeper.out 文件居然有6G,后来设置下日志为滚动输出,参考:
http://blog.csdn.net/hengyunabc/article/details/
但是改了之后,发现一天的日志量就是100多M,滚动日志一天就被冲掉了,这个不科学。
再仔细查看下日志里的内容,发现有很多连接建立好,马上又断开:
[java] view plaincopy
2014-11-24 15:38:33,348 [myid:3] - INFO
[NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@1001] - Closed socket connection for client /10.0.0.3:47772 (no session established for client)
2014-11-24 15:38:33,682 [myid:3] - INFO
[NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@197] - Accepted socket connection from /10.0.0.3:32119
2014-11-24 15:38:33,682 [myid:3] - WARN
[NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@349] - caught end of stream exception
EndOfStreamException: Unable to read additional data from client sessionid 0x0, likely client has closed socket
at org.apache.zookeeper.server.NIOServerCnxn.doIO(NIOServerCnxn.java:220)
at org.apache.zookeeper.server.NIOServerCnxnFactory.run(NIOServerCnxnFactory.java:208)
at java.lang.Thread.run(Thread.java:745)
2014-11-24 15:38:33,682 [myid:3] - INFO
[NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@1001] - Closed socket connection for client /10.0.0.0:32119 (no session established for client)
从日志输出的时间来看,秒连秒断,非常诡异。
用netstat查看网络连接状态
到client的服务器上查看连接的状态:
[plain] view plaincopy
netstat -antp | grep 2181
发现有很多TIME_WAIT状态的连接:
[plain] view plaincopy
0 10.0.0.3:44269
10.0.1.77:2181
0 10.0.0.3:43646
10.0.1.77:2181
0 10.0.0.3:44184
10.0.1.77:2181
0 10.0.0.3:44026
10.0.1.77:2181
0 10.0.0.3:43766
10.0.1.77:2181
但是TIME_WAIT状态的连接是看不到进程号的。搜索研究了下netstat的参数,发现没有办法输出TIME_WAIT状态的连接的pid,只好尝试其它的办法。
再用 jstack -l pid 来查看进程的线程栈,也没有发现什么异常的东东。查看到有几个zookeeper连接的线程,但也是正常状态。
再检查了机器的IO,CPU,内存,也没有异常的情况。
没找到什么有用的信息,只好再研究下netstat的参数:发现用 netstat -ae 输出了一些信息:
[plain] view plaincopy
0 10.0.0.3:41772
10.0.1.77:eforward
0 10.0.0.3:41412
10.0.1.77:eforward
0 10.0.0.3:24226
10.0.1.77:2181
0 10.0.0.3:24623
10.0.1.77:2181
发现user是root。于是以为是非Java应用,在不断地连接zookeeper。于是停止java程序,发现没有TIME_WAIT连接了。但是确认是Java应用的问题,于是再重启Java应用,但没有再发现TIME_WAIT情况。很诡异。
问题不能重现了,相当的蛋疼。忽然想到线上的应用也许也有这个问题,于是到线下zookeeper服务器上查看了下,果然发现有同样的问题。
用tcpdump抓包和wireshark分析
先用tcpdump来查看下具体的网络连接,发现的确是连接连上再断开。于是先保存成cap文件,再用wireshark来分析:
[plain] view plaincopy
tcpdump -vv host 192.168.66.27 and port 2181 -w 2181.cap
但是也没有发现什么有用信息,的确是TCP连接连上,再FIN,ACK连接断开。
查看应用日志,发现Tomcat webcontext没有正常启动
没办法了,有两种考虑,一个是用strace,二是用btrace。但是btrace好久没用过了,不太想再去看例子文档。
还好,去下btrace之后,先去看了下应用的日志,发现应用报了一些ClassLoader的错误:
[plain] view plaincopy
Nov 24, :43 PM org.apache.catalina.loader.WebappClassLoader loadClass
INFO: Illegal access: this web application instance has been stopped already.
Could not load org.apache.zookeeper.ClientCnxnSocketNIO.
The eventual following stack trace is caused by an err
or thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access, and has no functional impact.
java.lang.IllegalStateException
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1564)
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1523)
at ch.qos.logback.classic.spi.PackagingDataCalculator.loadClass(PackagingDataCalculator.java:198)
at ch.qos.logback.classic.spi.PackagingDataCalculator.bestEffortLoadClass(PackagingDataCalculator.java:226)
at ch.qos.logback.classic.puteBySTEP(PackagingDataCalculator.java:132)
at ch.qos.logback.classic.spi.PackagingDataCalculator.populateUncommonFrames(PackagingDataCalculator.java:107)
at ch.qos.logback.classic.spi.PackagingDataCalculator.populateFrames(PackagingDataCalculator.java:99)
因为有经验了,马上知道这个Tomcat因为其它原因webcontext实始化失败退出,然后后面的一些线程继续跑时,会抛出ClassLoader,或者Class not found的异常。
于是猜想到原因了:
Tomcat webcontext初始化失败,zookeeper的重连线程自动不断重连。
但是为什么重启Tomcat之后,没有重现TIME_WAIT的情况?
再折腾了下,发现只有当zookeeper重启后,应用才会出现大量的TIME_WAIT连接。报的是下面这个异常:
[plain] view plaincopy
19:42:44,399 [Thread-3-SendThread(192.168.90.147:4181)] WARN
org.apache.zookeeper.ClientCnxn - Session 0x149c for server 192.168.90.147/192.168.90.147:4181, unexpected error, closing socket connection and attempting reconnect
java.lang.NoClassDefFoundError: org/apache/zookeeper/proto/SetWatches
at org.apache.zookeeper.ClientCnxn$SendThread.primeConnection(ClientCnxn.java:867) ~[zookeeper-3.4.5.jar:3.4.5-1392090]
at org.apache.zookeeper.ClientCnxnSocketNIO.doTransport(ClientCnxnSocketNIO.java:352) ~[zookeeper-3.4.5.jar:3.4.5-1392090]
这个异常的原因,是某些zookeeper的类没有加载到。
最终原因分析
梳理下整个流程:
Tomcat启动,初始化webcontext;
初始化spring, spring初始某些些bean,这些bean包括了zookeeper的连接相关的bean;
这时zkClient(独立线程)已经连接上服务器了,但是classloader没有加载到org/apache/zookeeper/proto/SetWatches类;
spring初始化失败,导致Tomcat webcontext初始化也失败,应用在挂起状态,但zkClient线程还是正常的;
zookeeper服务器重启,zkClient开始重连,连接上zookeeper服务器;
zkClient触发watch的一些代码,ClassLoader尝试加载org/apache/zookeeper/proto/SetWatches类,但是发现找不到类,于是抛出异常;
zkClient捕获到异常,认为重连失败,close掉connection,休眠几秒之后,再次重连;
于是出现了zkClient反复重试连接zookeeper服务器,而且都是秒连秒断的情况。
这次排查花了不少时间,有个原因是开始没有去查看应用的日志,以为应用的是正常的,而且zookeeper.out的输出日志很多,也有一段时间了。
还有线上的应用比较坑爹,活动已经过期很久了,但是程序还是线上跑,也没有人管是否出问题了。
所以,主要精力放在各种网络连接状态的获取上。对去查看应用日志比较排斥。
还有一个原因是,问题比较诡异,有点难重现,当发现可以重现时,基本已经发现问题所在了。
排查问题还是要耐心收集信息,再分析判断。
12345678910
12345678910
12345678910 上一篇:下一篇:文章评论相关解决方案 12345678910 Copyright & &&版权所有CAP满足哪几部分
failover方式是怎样
服务发现梳理
DNS最原始的配置文件和 DNS 来做服务发现,Host、端口都是写在配置文件里的,发生变更的时候只能修改配置文件并重启服务。所以当某台机器挂掉的时候,依赖它上面服务的其他系统也都全部会出问题。而应急的步骤都是先在别的机器上运行新的实例,修改配置文件并重启关联的其他系统。这样做费时、费力、且会有一个时间窗口内系统无法提供服务。
通过 Nginx 来做了负载均衡/主备的这样做还是有两个问题:(1)Nginx 本身成为一个故障点(2)连接数量翻倍,其中第二个问题曾导致我们的环境出现了 nf_conntrack table full 的问题。我们的关键服务都是多实例负载均衡的,当系统并发上升到一定程度的时候,某些服务器,尤其是跑着 Nginx 的机器很容易出现这个错误。
zk服务实例注册的 Node 类型是 ephemeral node,这种类型的节点只有在客户端保持着连接的时候才有效。所以当某个服务实例被停止或者出现网络异常的时候,对应的节点也会被删掉。因此,任何时候从 ZooKeeper 里查询到的都是当前活跃的实例。借助 ZooKeeper 的推送功能,服务的消费者可以得知实例的变化,从而可以从容应对服务实例的宕机和新实例的添加,无需重启。
DNS 变更延迟问题
中心化负载均衡,单点问题
zk,多语言问题
SmartStack,在zookeeper和haproxy上封装一层
etcd(coreos开发,系统级别的)etcd是一个采用HTTP协议的健/值对存储系统,它是一个分布式和功能层次配置系统,可用于构建服务发现系统。其很容易部署、安装和使用,提供了可靠的数据持久化特性。它是安全的并且文档也十分齐全。etcd比Zookeeper是比更好的选择,因为它很简单,然而,它需要搭配一些第三方工具才可以提供服务发现功能。
consul(go语言写的)Consul是强一致性的数据存储,使用gossip形成动态集群。它提供分级键/值存储方式,不仅可以存储数据,而且可以用于注册器件事各种任务,从发送数据改变通知到运行健康检查和自定义命令,具体如何取决于它们的输出。与Zookeeper和etcd不一样,Consul内嵌实现了服务发现系统,所以这样就不需要构建自己的系统或使用第三方系统。这一发现系统除了上述提到的特性之外,还包括节点健康检查和运行在其上的服务。Zookeeper和etcd只提供原始的键/值队存储,要求应用程序开发人员构建他们自己的系统提供服务发现功能。而Consul提供了一个内置的服务发现的框架。客户只需要注册服务并通过DNS或HTTP接口执行服务发现。其他两个工具需要一个亲手制作的解决方案或借助于第三方工具。Consul为多种数据中心提供了开箱即用的原生支持,其中的gossip系统不仅可以工作在同一集群内部的各个节点,而且还可以跨数据中心工作。
Netflix的Eureka方案
(里头有peer部署部分)
Eureka 由两个组件组成: Eureka 服务器 和 Eureka 客户端 。Eureka 服务器用作服务注册服务器。Eureka 客户端是一个 java 客户端,用来简化与服务器的交互、作为轮询负载均衡器,并提供服务的故障切换支持。Netflix 在其生产环境中使用的是另外的客户端,它提供基于流量、资源利用率以及出错状态的加权负载均衡。当一个中间层服务首次启动时,他会将自己注册到 Eureka 中,以便让客户端找到它,同时每 30 秒发送一次心跳。如果一个服务在几分钟内没有发送心跳,它将从所有 Eureka 节点上注销。一个 Amazon 域中可以有一个 Eureka 节点集群,每个可用区(Availability Zone)至少有一个 Eureka 节点。AWS 的域相互之间是隔离的。
zk是满足CP牺牲A,这个不对,看 ,其实zk只是满足最终一致性C,可用性A这个是保证的,并且保证一半的节点是最新的数据,分区性P这个得看节点多少及读写情况,节点多,则写耗时长,另外节点多了Leader选举非常耗时, 就会放大网络的问题,容易分区。
对于Service发现服务而言,宁可返回某服务5分钟之前在哪几个服务器上可用的信息,也不能因为暂时的网络故障而找不到可用的服务器,而不返回任何结果。所以说,用ZooKeeper来做Service发现服务是肯定错误的。总结一句就是,service不是强一致的,所以会有部分情况下没发现新服务导致请求出错。当部分或者所有节点跟ZooKeeper断开的情况下,每个节点还可以从本地缓存中获取到数据;但是,即便如此,ZooKeeper下所有节点不可能保证任何时候都能缓存所有的服务注册信息。如果ZooKeeper下所有节点都断开了,或者集群中出现了网络分割的故障(注:由于交换机故障导致交换机底下的子网间不能互访);那么ZooKeeper会将它们都从自己管理范围中剔除出去,外界就不能访问到这些节点了,即便这些节点本身是“健康”的,可以正常提供服务的;所以导致到达这些节点的服务请求被丢失了。
Eureka处理网络问题导致分区。如果Eureka服务节点在短时间里丢失了大量的心跳连接(注:可能发生了网络故障),那么这个Eureka节点会进入”自我保护模式“,同时保留那些“心跳死亡“的服务注册信息不过期。此时,这个Eureka节点对于新的服务还能提供注册服务,对于”死亡“的仍然保留,以防还有客户端向其发起请求。当网络故障恢复后,这个Eureka节点会退出”自我保护模式“。所以Eureka的哲学是,同时保留”好数据“与”坏数据“总比丢掉任何”好数据“要更好,所以这种模式在实践中非常有效。
Eureka就是为发现服务所设计的,它有独立的客户端程序库,同时提供心跳服务、服务健康监测、自动发布服务与自动刷新缓存的功能。但是,如果使用ZooKeeper你必须自己来实现这些功能。
Eureka一致性分析
通过配置eureka.serviceUrl.defaultZone来进行复制eureka.client.serviceUrl.defaultZone=http://&peer1host&:&peer1port&g...
貌似是根据配置的url的前后顺序来复制的
* Replicates all eureka actions to peer eureka nodes except for replication
* traffic to this node.
private void replicateToPeers(Action action, String appName, String id,
InstanceInfo info /* optional */,
InstanceStatus newStatus /* optional */, boolean isReplication) {
Stopwatch tracer = action.getTimer().start();
if (isReplication) {
numberOfReplicationsLastMin.increment();
// If it is a replication already, do not replicate again as this
// will create a poison replication
if (peerEurekaNodes == Collections.EMPTY_LIST || isReplication) {
for (final PeerEurekaNode node : peerEurekaNodes.get()) {
// If the url represents this host, do not replicate
// to yourself.
if (isThisMe(node.getServiceUrl())) {
replicateInstanceActionsToPeers(action, appName, id, info,
newStatus, node);
} finally {
tracer.stop();
触发的时机相当于热备:每增删改一次,就同步一次。然后默认是走url的第一个来查的,然后
调用的时候,第一个挂了,自动去找第二,详见
* Makes remote calls with the corresponding action(register,renew etc).
* @param action
the action to be performed on eureka server.
* @return ClientResponse the HTTP response object.
* @throws Throwable
on any error.
private ClientResponse makeRemoteCall(Action action) throws Throwable {
return makeRemoteCall(action, 0);
具体内部catch异常后,递归调用
* Makes remote calls with the corresponding action(register,renew etc).
* @param action
the action to be performed on eureka server.
Try the fallback servers in case of problems communicating to
the primary one.
* @return ClientResponse the HTTP response object.
* @throws Throwable
on any error.
private ClientResponse makeRemoteCall(Action action, int serviceUrlIndex)
throws Throwable {
String urlPath =
Stopwatch tracer =
String serviceUrl = eurekaServiceUrls.get().get(serviceUrlIndex);
ClientResponse response =
logger.debug("Discovery Client talking to the server {}", serviceUrl);
// If the application is unknown do not register/renew/cancel but
// refresh
if ((UNKNOWN.equals(instanceInfo.getAppName())
&& (!Action.Refresh.equals(action)) && (!Action.Refresh_Delta
.equals(action)))) {
WebResource r = discoveryApacheClient.resource(serviceUrl);
String remoteRegionsToFetchS
switch (action) {
case Renew:
tracer = RENEW_TIMER.start();
urlPath = "apps/" + appPathI
response = r
.path(urlPath)
.queryParam("status",
instanceInfo.getStatus().toString())
.queryParam("lastDirtyTimestamp",
instanceInfo.getLastDirtyTimestamp().toString())
.put(ClientResponse.class);
case Refresh:
tracer = REFRESH_TIMER.start();
final String vipAddress = clientConfig.getRegistryRefreshSingleVipAddress();
urlPath = vipAddress == null ? "apps/" : "vips/" + vipA
remoteRegionsToFetchStr = remoteRegionsToFetch.get();
if (!Strings.isNullOrEmpty(remoteRegionsToFetchStr)) {
urlPath += "?regions=" + remoteRegionsToFetchS
response = getUrl(serviceUrl + urlPath);
case Refresh_Delta:
tracer = REFRESH_DELTA_TIMER.start();
urlPath = "apps/delta";
remoteRegionsToFetchStr = remoteRegionsToFetch.get();
if (!Strings.isNullOrEmpty(remoteRegionsToFetchStr)) {
urlPath += "?regions=" + remoteRegionsToFetchS
response = getUrl(serviceUrl + urlPath);
case Register:
tracer = REGISTER_TIMER.start();
urlPath = "apps/" + instanceInfo.getAppName();
response = r.path(urlPath)
.type(MediaType.APPLICATION_JSON_TYPE)
.post(ClientResponse.class, instanceInfo);
case Cancel:
tracer = CANCEL_TIMER.start();
urlPath = "apps/" + appPathI
response = r.path(urlPath).delete(ClientResponse.class);
// Return without during de-registration if it is not registered
// already and if we get a 404
if ((!isRegisteredWithDiscovery)
&& (response.getStatus() == Status.NOT_FOUND
.getStatusCode())) {
if (logger.isDebugEnabled()) {
logger.debug("Finished a call to service url {} and url path {} with status code {}.",
new String[] {serviceUrl, urlPath, String.valueOf(response.getStatus())});
if (isOk(action, response.getStatus())) {
logger.warn("Action: " + action + "
=& returned status of "
+ response.getStatus() + " from " + serviceUrl
+ urlPath);
throw new RuntimeException("Bad status: "
+ response.getStatus());
} catch (Throwable t) {
closeResponse(response);
String msg = "Can't get a response from " + serviceUrl + urlP
if (eurekaServiceUrls.get().size() & (++serviceUrlIndex)) {
logger.warn(msg, t);
logger.warn("Trying backup: "
+ eurekaServiceUrls.get().get(serviceUrlIndex));
SERVER_RETRY_COUNTER.increment();
return makeRemoteCall(action, serviceUrlIndex);
ALL_SERVER_FAILURE_COUNT.increment();
logger.error(
+ "\nCan't contact any eureka nodes - possibly a security group issue?",
} finally {
if (tracer != null) {
tracer.stop();
(zk,etcd,samrtstack)
你可能感兴趣的文章
15 收藏,2.2k
13 收藏,1.7k
125 收藏,48.5k
本文采用 ,分享、演绎需署名且使用相同方式共享。
本文隶属于专栏
spring boot , docker and so on
分享到微博?
技术专栏,帮你记录编程中的点滴,提升你对技术的理解收藏感兴趣的文章,丰富自己的知识库
明天提醒我
我要该,理由是:
扫扫下载 App
SegmentFault
一起探索更多未知

我要回帖

更多关于 zookeeper 自动发现 的文章

 

随机推荐