近期这段时间在设计和实现日志系统。在整个日志系统系统中Zookeeper的做用很是重要——它用于协调各个分布式组件并提供必要的配置信息和元数据。这篇文章主要分享一下Zookeeper的使用场景。css
这里主要涉及到Zookeeper在日志系统中的使用,但事实上它在咱们的消息总线和搜索模块中也相同很是重要。node
日志的类型和日志的字段这里咱们统称为日志的元数据。咱们构建日志系统的目的终于主要是为了:日志搜索,日志分析。这两大块咱们很是大程度上依赖于——ElasticSearch(关于什么是ElasticSearch,这里就很少作介绍了)。mysql
日志的字段定义,在ElasticSearch中是一个索引中某个mapping的Schema。ElasticSearch是一个号称Schema Free
的分布式全文检索系统。这里需要正确地理解Schema Free
,它并不是不需要Schema。也不是强制要求你必须有明白的Schema,有没有都可以作全文检索。但是像聚合、分析等很是多高级功能都创建在明白的Schema的基础上。所以。从分析统计的角度看,咱们对日志进行明白的字段定义是很是有必要的。nginx
日志元数据是日志系统的基础信息。咱们在web管控台管理它并同步至Zookeeper供其它模块使用。比方搜索模块。因为上文提到日志的类型、字段事实上跟ElasticSearch的Mapping Type
是对等的映射关系。因此搜索模块会重度依赖日志元数据。git
另外。为了保证新的日志元数据(通常是一个新的日志类型被建立)尽快同步至ElasticSearch。咱们利用了Zookeeper的事件Push机制(或者叫Pub/Sub机制)来实时获知日志元数据的变化。一旦有新的日志元数据产生。咱们的搜索模块会立刻获得事件通知,它会获取最新的日志元数据。而后为其在ElasticSearch的indices中新建一个mapping。为这样的日志类型的日志存入ElasticSearch作好准备。github
这样的方式带来了哪些优势。眼下来看至少有三点:web
从以前的文章中,你应该可以找到日志採集器的选型,出于多种缘由(跟消息总线的集成、可定制性、支持从zookeeper获取配置等),咱们选择了Flume-ng。它在最新的稳定版(1.6.0)提供了从zookeeper获取採集配置的功能。sql
因而,日志採集花费在运维上的成本就大大减小了。因为有了配置以后,运维人员就不需要手动在目标server上改动配置文件,完毕各类配置,仅仅需要固定键入例如如下的启动命令就能够:markdown
sudo bin/flume-ng agent --conf conf -z 127.0.0.1:2181 -p /component/log/mysqlSlowquery/flume -name mysql-slowquery-30 -Dflume.root.logger=DEBUG,console
而上面这个命令中,惟一需要变更的仅仅有如下几个部分:网络
mysql-slowquery-30
事实上原先需要手动改动配置文件的部分參数项将在提供的管控台中进行配置,但基于web的表单填写显然要比在server上以命令行的方式来得easy得多。
这里咱们的作法是拆解了flume的配置文件,将其固定不变的部分作成模板,将其可变部分作成表单。在提交以前,经过模板引擎将模板跟配置信息进行合并为完整的配置并推送到Zookeeper中去。
当中需要配置的部分參数有:
相同在以前的文章中我也说起咱们在日志解析上的选择是morphline。
morphline是个在Hadoop生态系统中的ETL Framework。morphline也有一个配置文件用于定义一系列的Commands。而它也有固定部分和可变部分,因为解析主要应用了morphline的Grok
命令,因此针对这个命令,可变部分主要是:
咱们的作法相同类似于日志採集模块。将morphline的配置文件的固定部分作成固定模板。而后将可变部分在管控台上进行配置。终于合并提交到Zookeeper中。
日志解析服务(归属于如下的后台服务
)。在启动时会依据本身的日志类型,从Zookeeper的特定节点下找到该日志类型的morphline的配置,将其获取下来并保存在本地文件系统中,而后构建Mrophline对象(因为morphline眼下仅仅提供基于File对象的构造方式。因此多了一个先保存至本地文件再基于文件构造Morphline对象的步骤)进行解析。
日志解析这边仅仅是给解析任务提供了 元数据 。真正的解析由后台的解析任务来完毕,咱们将类似的这些赞成在后台的所有任务笼统得归结为 后台服务 。
后台服务遵循:任务组
->任务
->工做线程
的层次性的组织方式。依照服务的业务类型(说白了就是同一套处理逻辑)。将其划分为不一样的任务组(比方:ETL组、日志索引组等)。不一样的任务组下会有 至少 一个任务。比方ETL任务组下就会有很是多个任务(比方:nginx訪问日志解析任务、mysql慢查询日志解析任务等)。而某一个任务下又存在至少一个工做线程。
在管控台有一个后台服务模块。专门用于管理任务对象以及它们的元数据。
运行任务的工做者线程自己是无状态的。它们在启动的时候会去Zookeeper上下载它们运行任务所需要的元数据。
一般,为了服务的可用性咱们会为每个任务配备很多于一个工做者线程。
固然,这里的 很多于一个 并不只仅是基于一个运行后台服务的JVM进程或一个主机节点来计数的,而是针对由多个节点组成的集群而言。
这经过一个配置来实现:
worker.minimumNumPerTask=2
它的意义是:对每个task而言,启动的最少的worker线程数。假设一个主机节点上启动两个后台服务的JVM进程,那么这个task就会相应4个工做者线程。
正常状况下,每个任务在同一时刻仅仅有一个处于active状态的工做者线程,而其它抢占失败的都会将本身切换为standby模式。做为备援随时待命。
这个机制是怎样实现的?这得益于Zookeeper提供的 暂时顺序 节点。
当多个工做者线程去竞争一个任务的时候,它们首先去该任务的path下建立一个子path,并注冊本身的主机等信息。
注意这里建立的子path的类型不一样于其它Zookeeper使用场景的path类型(其它path一般都是持久型的),它是暂时、顺序的。这两个属性很是重要:
各个工做者线程建立暂时顺序的path后,因为具备 顺序 性。Zookeeper会依照它们建立的顺序在path后追加带有从1開始递增的编号。各个工做者建立完毕后会获得各自的编号,而后它们做一个顺序推断,谁是最小的谁就会得到任务的运行机会并成为active工做者,而其它抢占失败的将默认切换到standby模式,这些抢占失败的工做者线程会注冊成为它们抢占task的 子节点变动 watcher。这时 暂时 属性就派上用场了,当处于active模式的工做者线程丢失会话以后,这些standby将会收到通知,而这时它们会再次去推断本身的编号是否是最小的。假设是那么就可以接替以前的工做者线程成为active的了。这时假设有新加入的工做者线程也会触发变动通知,但这并不会影响正常的逻辑。
固然这里还存在一些问题有待无缺:
每个任务组都会有一个watcher来监控是否有新的任务被建立(比方一种新的日志类型被提交)。假设有新任务则会在其所属的线程池中新建新的工做者来运行新任务。
眼下暂时这个watcher默认仅仅关注新增任务,而针对任务被移除或者任务的元数据变动,watcher暂时尚未相应的响应机制。这也是兴许需要考虑和无缺的部分。
这样的机制以前已经分别应用于日志採集
、日志解析
模块了,在这里也是为了简化后台服务启动时配置的问题。
综上,整个设计的Zookeeper 拓扑图大体例如如下:
从上面的分析可以看到。咱们最大程度地将各类可变的參数配置到Zookeeper中。使其成为串联起整个分布式系统的配置中心,而咱们的管控台某种意义上退化成了“配置系统”——将配置界面化、持久化。
同一时候。咱们也利用了Zookeeper的实时事件Push机制,来进行分布式协调。