ZooKeeper 原理总结
ZooKeeper 是分布式的、开源的分布式应用程序协调服务,原本是 Hadoop、HBase 的一个重要组件。它为分布式应用提供一致性服务的软件,包括:配置维护、域名服务、分布式同步、组服务等。
ZooKeeper 是分布式的、开源的分布式应用程序协调服务,原本是 Hadoop、HBase 的一个重要组件。它为分布式应用提供一致性服务的软件,包括:配置维护、域名服务、分布式同步、组服务等。
只要是具备 CP(CAP 取 CP)特点的分布式 KV 系统,原则上都可以作为 ZooKeeper 的替代品,但是选择时仍然要考虑设计原理上的差异、落地方案的成熟程度及业务场景实际所需。
ZooKeeper的连接与会话就是客户端通过实例化ZooKeeper对象来实现客户端与服务器创建并保持TCP连接的过程。
配置多个实例共同构成一个集群对外提供服务以达到水平扩展的目的,每个服务器上的数据是相同的,每一个服务器均可以对外提供读和写的服务,这点和 redis 是相同的,即对客户端来讲每个服务器都是平等的。
zookeeper 提供了三种集群选举方式:
默认的算法是 FastLeaderElection,所以这里主要分析它的选举机制。
主要看这个类,只有 LOOKING 状态才会去执行选举算法。每个服务器在启动时都会选择自己做为领导,然后将投票信息发送出去,循环一直到选举出领导为止。
1 | public void run() { |
它是 zookeeper 默认提供的选举算法,核心方法如下。可以与本文上面的流程图对照。
1 | public Vote lookForLeader() throws InterruptedException { |
在投票完成后,需要将投票信息发送给集群中的所有服务器,它包含如下内容。
目前有 5 台服务器,每台服务器均没有数据,它们的编号分别是 1,2,3,4,5,按编号依次启动,它们的选择举过程如下:
描述 Leader 选择过程中的状态变化,这是假设全部实例中均没有数据,假设服务器启动顺序分别为:A,B,C。
不会,ZooKeeper 更倾向于保持一致性,如果配置中的部分服务器不可用,那么整个集群都是不可用的。
最近有点迷 ARPG,但是自己的 T470P 上只有一个 Ubuntu 系统,所以买了一个 2422 的固态往上面装个 Win10,Linux 下构建启动盘还是蛮多坑的,下面记录一下操作流程,免得以后忘了。
Collect, report, or save system activity information.
根据要统计的信息类型的不同,输出格式也不同。
1 | # 查看全天 |
CPU统计信息输出格式:
1 | # 查看全天 |
内存统计信息输出格式
1 | # 查看全天 |
IO信息输出格式
1 | # 查看全天 |
流量信息输出格式:
1 | head -n 1 /etc/issue |
1 | uname -a |
1 | hostname |
1 | lspci -tv |
1 | lsusb -tv |
1 | lsmod |
1 | cat /proc/cpuinfo |
查看进程状态。
参数 | 作用 |
---|---|
-a | 显示所有的进程(包括其他用户的) |
-u | 用户以及其他详细信息 |
-x | 显示没有控制终端的进程 |
1 | ps -ef |
linux 系统中进程最常见的 5 种状态为:
当执行”ps aux”命令后通常会看到下面格式的进程状态,表格中只是列举了部分输出值,而且正常的输出值中不包括中文注释部分:
USER | PID | %CPU | %MEM | VSZ | RSS | TTY | STAT | START | TIME | COMMAND |
---|---|---|---|---|---|---|---|---|---|---|
进程的所有者 | 进程 ID 号 | 运算器占用率 | 内存占用率 | 虚拟内存使用量(单位是 KB) | 占用的固定内存量(单位是 KB) | 所在终端 | 进程状态 | 被启动的时间 | 实际使用 CPU 的时间 | 命令名称与参数 |
root | 1 | 0.0 | 0.4 | 53684 | 7628 | ? | Ss | 07:22 | 0:02 | /usr/lib/systemd/systemd |
root | 2 | 0.0 | 0.0 | 0 | 0 | ? | S | 07:22 | 0:00 | [kthreadd] |
root | 3 | 0.0 | 0.0 | 0 | 0 | ? | S | 07:22 | 0:00 | [ksoftirqd/0] |
root | 5 | 0.0 | 0.0 | 0 | 0 | ? | S< | 07:22 | 0:00 | [kworker/0:0H] |
root | 7 | 0.0 | 0.0 | 0 | 0 | ? | S | 07:22 | 0:00 | [migration/0] |
查询某个指定服务的进程 PID 号码值,比如
1 | pidof firefox |
终止某个指定 PID 号码的进程
1 | $ kill <PID> |
终止某个指定名称的服务所对应的全部进程,因为一般大型软件的服务程序通常都会有数个进程协同为其提供服务,如果逐个去结束 PID 实在麻烦,所以可以使用 killall 命令来批量结束某个服务程序的全部进程,比如
1 | $ killall httpd |
top 命令是 Linux 下常用的性能分析工具,能够动态监视进程活动与系统负载等信息,类似于 Windows 的任务管理器
可以直接使用 top 命令后,查看%MEM 的内容。可以选择按进程查看或者按用户查看,如想查看 oracle 用户的进程内存使用情况的话可以使用如下的命令:
1 | $ top -u oracle |
示例输出如下:
1 | top - 17:59:11 up 289 days, 5:57, 20 users, load average: 0.01, 0.02, 0.05 |
前面的五行为系统整体的统计信息,下面我们来逐行的讲解:
11.2 id
意味着有 11.2%的 CPU 资源是空闲的。后面每行是进程的统计数据,其中:
PID:进程的 ID
USER:进程所有者
PR:进程的优先级别,越小越优先被执行
NInice:值
VIRT:进程占用的虚拟内存
RES:进程占用的物理内存
SHR:进程使用的共享内存
S:进程的状态。S 表示休眠,R 表示正在运行,Z 表示僵死状态,N 表示该进程优先值为负数
%CPU:进程占用 CPU 的使用率
%MEM:进程使用的物理内存和总内存的百分比
TIME+:该进程启动后占用的总的 CPU 时间,即占用 CPU 使用时间的累加值。
COMMAND:进程启动命令名称
常用的命令:
P:按%CPU 使用率排行
T:按 MITE+排行
M:按%MEM 排行
可以根据进程查看进程相关信息占用的内存情况,(进程号可以通过 ps 查看)如下所示:
1 | $ pmap -d 14596 |
查看内存总量和空闲内存量
1 | $ grep MemTotal /proc/meminfo |
内存和交换分区容量及使用情况
1 | $ free |
total:总计内存量
used:已用量
free:可用量
shared:进程共享的内存量
buffers:磁盘缓存的内存量
cached:缓存的内存量
TODO
硬盘分区使用情况
1 | df |
查看某个目录的大小
1 | du -sh <目录> |
TODO
获取网卡配置与网络状态等信息
1 | $ ifconfig |
注意每段开头的网卡名称、inet 参数后面的 IP 地址、ether 参数后面的物理 mac 地址,以及 RX、TX 的接受与发送数据包的大小
TODO
如果发现自己的 java 进程悄无声息的消失了,几乎没有留下任何线索,那么 dmesg 一发,很有可能有你想要的。
1 | sudo dmesg|grep -i kill|less |
去找关键字 oom_killer。找到的结果类似如下:
1 | [6710782.021013] java invoked oom-killer: gfp_mask=0xd0, order=0, oom_adj=0, oom_scoe_adj=0 |
以上表明,对应的 java 进程被系统的 OOM Killer 给干掉了,得分为 854.
解释一下 OOM killer(Out-Of-Memory killer),该机制会监控机器的内存资源消耗。当机器内存耗尽前,该机制会扫描所有的进程(按照一定规则计算,内存占用,时间等),挑选出得分最高的进程,然后杀死,从而保护机器。
dmesg 日志时间转换公式:
log 实际时间=格林威治 1970-01-01+(当前时间秒数-系统启动至今的秒数+dmesg 打印的 log 时间)秒数:
1 | date -d "1970-01-01 UTC `echo "$(date +%s)-$(cat /proc/uptime|cut -f 1 -d' ')+12288812.926194"|bc ` seconds" |
剩下的,就是看看为什么内存这么大,触发了 OOM-Killer 了。
系统负载
1 | $ cat /proc/loadavg |
包括当前系统时间、系统已运行时间、当前在线用户、平均负载值等。
平均负载值指的是最近 1/5/15 分钟的系统压力情况,负载值越低越好,尽量不要长期超过 1。
1 | $ uptime |
配合 watch 命令来每秒刷新一次来获得当前系统负载情况:
1 | watch -n 1 uptime |
TODO
TODO
Linux 命令尤其多,没办法一下子全部搞明白,下面来不及看的部分我就用 TODO 标出了。
原来:负载均衡器会根据配好的 IP 和主机名来进行负载均衡,但是对 AWS cloud 这样体量的系统来说,因为服务实例宕机恢复十分频繁,所以负载均衡器还会有一个更复杂的注册 / 注销服务的机制。
现在:Eureka 在中间层提供一种负载均衡的可能。
Feature | Consul | zookeeper | etcd | euerka |
---|---|---|---|---|
服务健康检查 | 服务状态,内存,硬盘等 | (弱)长连接,keepalive | 连接心跳 | 可配支持 |
多数据中心 | 支持 | — | — | — |
kv存储服务 | 支持 | 支持 | 支持 | — |
一致性 | raft | paxos(zab) | raft | — |
cap | ca | cp | cp | ap |
使用接口(多语言能力) | 支持http和dns | 客户端 | http/grpc | http(sidecar) |
watch支持 | 全量/支持long polling | 支持 | 支持 long polling | 支持 long polling/大部分增量 |
自身监控 | metrics | — | metrics | metrics |
安全 | acl /https | acl | https支持(弱) | — |
spring cloud集成 | 已支持 | 已支持 | 已支持 | 已支持 |
Euraka 使用时需要显式配置健康检查支持;Zookeeper,Etcd 则在失去了和服务进程的连接情况下任务不健康,而 Consul 相对更为详细点,比如内存是否已使用了90%,文件系统的空间是不是快不足了。
Consul 通过 WAN 的 Gossip 协议,完成跨数据中心的同步;而且其他的产品则需要额外的开发工作来实现。
除了 Eureka ,其他几款都能够对外支持 k-v 的存储服务,所以后面会讲到这几款产品追求高一致性的重要原因。而提供存储服务,也能够较好的转化为动态配置服务哦。
Eureka 典型的 AP,作为分布式场景下的服务发现的产品较为合适,服务发现场景的可用性优先级较高,一致性并不是特别致命。其次 CA 类型的场景 Consul,也能提供较高的可用性,并能 k-v store 服务保证一致性。 而Zookeeper,Etcd则是CP类型 牺牲可用性,在服务发现场景并没太大优势。
Zookeeper的跨语言支持较弱,其他几款支持 http11 提供接入的可能。Euraka 一般通过 sidecar的方式提供多语言客户端的接入支持。Etcd 还提供了Grpc的支持。 Consul除了标准的Rest服务api,还提供了DNS的支持。
Zookeeper 支持服务器端推送变化,Eureka 2.0(正在开发中)也计划支持。 Eureka 1,Consul,Etcd则都通过长轮询的方式来实现变化的感知。
除了 Zookeeper ,其他几款都默认支持 metrics,运维者可以搜集并报警这些度量信息达到监控目的。
Consul,Zookeeper 支持ACL,另外 Consul,Etcd 支持安全通道https。
目前都有相对应的 boot starter,提供了集成能力。
总的来看,目前Consul 自身功能,和 spring cloud 对其集成的支持都相对较为完善,而且运维的复杂度较为简单(没有详细列出讨论),Eureka 设计上比较符合场景,但还需持续的完善。
Eureka Client 将信息注册到 Eureka Server,注册过程发生在第一次心跳时(在 30 秒后)。
正常情况下,Client 必须显式调用 Unregister 来释放自己的注册信息,除非是由于”unclean termination”而导致心跳丢失超过 3 次。
客户端每30秒通过发送一次心跳(heartbeats)来续约(renewal),心跳告知Eureka Server本实例仍然存活,如果Server在90秒内没有收到续约请求,它将从服务注册表中移除该实例。
Eureka clients fetches the registry information from the server and csort_bufferhes it locally. After that, the clients use that information to find other services. This information is updated periodically (every 30 seconds) by getting the delta updates between the last fetch cycle and the current one. The delta information is held longer (for about 3 mins) in the server, hence the delta fetches may return the same instances again. The Eureka client automatically handles the duplicate information.
After getting the deltas, Eureka client reconciles the information with the server by comparing the instance counts returned by the server and if the information does not match for some reason, the whole registry information is fetched again. Eureka server caches the compressed payload of the deltas, whole registry and also per application as well as the uncompressed information of the same. The payload also supports both JSON/XML formats. Eureka client gets the information in compressed JSON format using jersey apache client.
Eureka client sends a cancel request to Eureka server on shutdown. This removes the instance from the server’s instance registry thereby effectively taking the instance out of traffic.
This is done when the Eureka client shuts down and the application should make sure to call the following during its shutdown.
DiscoveryManager.getInstance().shutdownComponent()
All operations from Eureka client may take some time to reflect in the Eureka servers and subsequently in other Eureka clients. This is because of the caching of the payload on the eureka server which is refreshed periodically to reflect new information. Eureka clients also fetch deltas periodically. Hence, it may take up to 2 mins for changes to propagate to all Eureka clients.
Eureka Client默认使用Jersey发送基于Jackson封装的JSON数据包给Eureka Server。
Eureka 不限制通信协议,Thrift、HTTP(S)等均可。
Eureka Client 的高可用设计:
Eureka Server 的高可用设计:
Renewal Threshold
并开始接收 Client 的心跳;Renews(last min)
(上一分钟内收到的心跳次数)达到了Renews threshold
(Server 期望在每分钟中收到的心跳次数,一般是 3),或者过去 15 分钟内的统计数据小于eureka.server.renewalPercentThreshold
(renews / renews threshold 的比值,默认为 0.85,当在 15 分钟内微服务心跳数低于 85%,则 Server 会进入自我保护状态,在这种情况下 Server 不会删除注册信息),则进入保护模式,自我保护状态其实是为了防止突发网络不稳定或断电时微服务心跳数剧减,导致微服务注册信息被大量删除的情况。Renews
达到了Renews threshold
;eureka.server.enableSelfPreservation=false
。orphaned server
,一些 Client 会注册到这些 Server 上,导致一些 Client 能看到这些注册信息而其他的一些则不能。比如在测试环境中出现:
1 | EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE. |
解决办法:
配置:
Configuring Eureka
Eureka Server 开放的 REST 接口提供动态配置功能:
Eureka REST operations
静态设置:
1 | eureka.metadata.mykey=myvalue |
设置后,相当于将mykey:myvalue
添加到 eureka 的metadata map
中。
动态设置:
需要提供一个自定义的
获取:
1 | String myValue = instanceInfo.getMetadata().get("myKey"); |
EurekaClient
ExampleEurekaGovernatedService
DefaultEurekaClientConfig extends EurekaClientConfig
EurekaServerConfig extends DefaultEurekaServerConfig
CloudInstanceConfig extends PropertiesInstanceConfig
MyDataCenterInstanceConfig extends PropertiesInstanceConfig
To dynamically do this, you will need to first provide your own custom implementation of the EurekaInstanceConfig
interface. You can then overload the public Map<String, String> getMetadataMap() method to return a metadata map that contains the desired metadata values. See PropertiesInstanceConfig
for an example implementation that provides the configuration based system above.
Zuul 基于 Netty 开发,使用 filters 包含了核心业务逻辑,Filter 是使用 Groovy 写的,主要是为了提供动态编译加载的能力,filters 主要包含了三类:
ProxyEndpoint
filter will route the request to the origin.HttpInboundSyncFilter
或HttpOutboundSyncFilter
来实现一种同步 Filter,例子见Zuul 源码中的Routes.groovy
;HttpInboundFilter
或HttpOutboundFilter
,例子见Zuul 源码中的SampleServiceFilter.groovy
。Type: most often defines the stage during the routing flow when the Filter will be applied (although it can be any custom string)
Async: define if the filter is sync or async, generally meaning do you need to make an external call or just doing work on-box
Execution Order: applied within the Type, defines the order of execution across multiple Filters
Criteria: the conditions required in order for the Filter to be executed
Action: the action to be executed if the Criteria is met
这些例子是zuul-sample中的代码。
默认情况下 Zuul 不会缓存请求体,因为 Filter 一般用到请求头就够了,但是如果需要在 inbound 中用到请求头或在 outbound 中用到响应头,则需要明确指定 Zuul 缓存,可以重写 Filter 的needsBodyBuffered()
:
1 | @Override |
Zuul 支持修改暴露服务时使用的协议,使用方法见 sample 项目中的SampleServerStartup
。
Push Messaging 机制可以支持从 Server 端推送消息到 Client 端,支持两种协议:WebSockets
和 Server Sent Events (SSE)
。
Push Messaging
TODO
原生 API 如何使用见:Netflix / ribbon - Getting Started
如果是搭配 Spring Boot,可以参考 Spring Could 文档。
a logic component to determine which server to return from a list
a component running in background to ensure liveness of servers
this can be static or dynamic. If it is dynamic (as used by DynamicServerListLoadBalancer
), a background thread will refresh and filter the list at certain interval
BaseLoadBalancer.setServerList()
中。Archaius ConfigurationManager
来设置 Server 列表。1 | myClient.ribbon.NIWSServerListClassName=com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList |
ServerListFilter 是DynamicServerListLoadBalancer
的组件,用于过滤从ServerList
返回的服务器列表,现在有两种实现:
1 | myclient.ribbon.EnableZoneAffinity=true |
ServerList
返回的全体服务器的一个固定子集,如果有服务器可用性较弱,则可以定期用新服务器替换老服务器。可以通过设置以下属性启用该Filter: 1 | myClient.ribbon.NIWSServerListClassName=com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList |
com.netflix.loadbalancer.DynamicServerListLoadBalancer#updateListOfServers
com.netflix.loadbalancer.ServerList#getUpdatedListOfServers
com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList#obtainServersViaDiscovery
TODO