ZooKeeper

ZooKeeper

服务端高并发分布式架构演进之路

翔宇 发表了文章 • 0 个评论 • 215 次浏览 • 2019-06-04 11:16 • 来自相关话题

【编者的话】本文以淘宝作为例子,介绍从一百个并发到千万级并发情况下服务端的架构的演进过程,同时列举出每个演进阶段会遇到的相关技术,让大家对架构的演进有一个整体的认知,文章最后汇总了一些架构设计的原则。 #基本概念 在介绍架构之前,为 ...查看全部
【编者的话】本文以淘宝作为例子,介绍从一百个并发到千万级并发情况下服务端的架构的演进过程,同时列举出每个演进阶段会遇到的相关技术,让大家对架构的演进有一个整体的认知,文章最后汇总了一些架构设计的原则。
#基本概念

在介绍架构之前,为了避免部分读者对架构设计中的一些概念不了解,下面对几个最基础的概念进行介绍:
##分布式

系统中的多个模块在不同服务器上部署,即可称为分布式系统,如Tomcat和数据库分别部署在不同的服务器上,或两个相同功能的Tomcat分别部署在不同服务器上。
##高可用

系统中部分节点失效时,其他节点能够接替它继续提供服务,则可认为系统具有高可用性。
##集群

一个特定领域的软件部署在多台服务器上并作为一个整体提供一类服务,这个整体称为集群。如Zookeeper中的Master和Slave分别部署在多台服务器上,共同组成一个整体提供集中配置服务。在常见的集群中,客户端往往能够连接任意一个节点获得服务,并且当集群中一个节点掉线时,其他节点往往能够自动的接替它继续提供服务,这时候说明集群具有高可用性。
##负载均衡

请求发送到系统时,通过某些方式把请求均匀分发到多个节点上,使系统中每个节点能够均匀的处理请求负载,则可认为系统是负载均衡的。
##正向代理和反向代理

系统内部要访问外部网络时,统一通过一个代理服务器把请求转发出去,在外部网络看来就是代理服务器发起的访问,此时代理服务器实现的是正向代理;当外部请求进入系统时,代理服务器把该请求转发到系统中的某台服务器上,对外部请求来说,与之交互的只有代理服务器,此时代理服务器实现的是反向代理。简单来说,正向代理是代理服务器代替系统内部来访问外部网络的过程,反向代理是外部请求访问系统时通过代理服务器转发到内部服务器的过程。
#架构演进

##单机架构

1.png

以淘宝作为例子。在网站最初时,应用数量与用户数都较少,可以把Tomcat和数据库部署在同一台服务器上。浏览器往www.taobao.com发起请求时,首先经过DNS服务器(域名系统)把域名转换为实际IP地址10.102.4.1,浏览器转而访问该IP对应的Tomcat。如果你想和更多Tomcat技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

随着用户数的增长,Tomcat和数据库之间竞争资源,单机性能不足以支撑业务
##第一次演进:Tomcat与数据库分开部署

2.png

Tomcat和数据库分别独占服务器资源,显著提高两者各自性能。

随着用户数的增长,并发读写数据库成为瓶颈
##第二次演进:引入本地缓存和分布式缓存

3.png

在Tomcat同服务器上或同JVM中增加本地缓存,并在外部增加分布式缓存,缓存热门商品信息或热门商品的html页面等。通过缓存能把绝大多数请求在读写数据库前拦截掉,大大降低数据库压力。其中涉及的技术包括:使用memcached作为本地缓存,使用Redis作为分布式缓存,还会涉及缓存一致性、缓存穿透/击穿、缓存雪崩、热点数据集中失效等问题。

缓存抗住了大部分的访问请求,随着用户数的增长,并发压力主要落在单机的Tomcat上,响应逐渐变慢
##第三次演进:引入反向代理实现负载均衡

4.png

在多台服务器上分别部署Tomcat,使用反向代理软件(Nginx)把请求均匀分发到每个Tomcat中。此处假设Tomcat最多支持100个并发,Nginx最多支持50000个并发,那么理论上Nginx把请求分发到500个Tomcat上,就能抗住50000个并发。其中涉及的技术包括:Nginx、HAProxy,两者都是工作在网络第七层的反向代理软件,主要支持http协议,还会涉及session共享、文件上传下载的问题。

反向代理使应用服务器可支持的并发量大大增加,但并发量的增长也意味着更多请求穿透到数据库,单机的数据库最终成为瓶颈
##第四次演进:数据库读写分离

5.png

把数据库划分为读库和写库,读库可以有多个,通过同步机制把写库的数据同步到读库,对于需要查询最新写入数据场景,可通过在缓存中多写一份,通过缓存获得最新数据。其中涉及的技术包括:Mycat,它是数据库中间件,可通过它来组织数据库的分离读写和分库分表,客户端通过它来访问下层数据库,还会涉及数据同步,数据一致性的问题。

业务逐渐变多,不同业务之间的访问量差距较大,不同业务直接竞争数据库,相互影响性能
##第五次演进:数据库按业务分库

6.png

把不同业务的数据保存到不同的数据库中,使业务之间的资源竞争降低,对于访问量大的业务,可以部署更多的服务器来支撑。这样同时导致跨业务的表无法直接做关联分析,需要通过其他途径来解决,但这不是本文讨论的重点,有兴趣的可以自行搜索解决方案。

随着用户数的增长,单机的写库会逐渐会达到性能瓶颈
##第六次演进:把大表拆分为小表

7.png

比如针对评论数据,可按照商品ID进行hash,路由到对应的表中存储;针对支付记录,可按照小时创建表,每个小时表继续拆分为小表,使用用户ID或记录编号来路由数据。只要实时操作的表数据量足够小,请求能够足够均匀的分发到多台服务器上的小表,那数据库就能通过水平扩展的方式来提高性能。其中前面提到的Mycat也支持在大表拆分为小表情况下的访问控制。

这种做法显著的增加了数据库运维的难度,对DBA的要求较高。数据库设计到这种结构时,已经可以称为分布式数据库,但是这只是一个逻辑的数据库整体,数据库里不同的组成部分是由不同的组件单独来实现的,如分库分表的管理和请求分发,由Mycat实现,SQL的解析由单机的数据库实现,读写分离可能由网关和消息队列来实现,查询结果的汇总可能由数据库接口层来实现等等,这种架构其实是MPP(大规模并行处理)架构的一类实现。

目前开源和商用都已经有不少MPP数据库,开源中比较流行的有Greenplum、TiDB、Postgresql XC、HAWQ等,商用的如南大通用的GBase、睿帆科技的雪球DB、华为的LibrA等等,不同的MPP数据库的侧重点也不一样,如TiDB更侧重于分布式OLTP场景,Greenplum更侧重于分布式OLAP场景,这些MPP数据库基本都提供了类似Postgresql、Oracle、MySQL那样的SQL标准支持能力,能把一个查询解析为分布式的执行计划分发到每台机器上并行执行,最终由数据库本身汇总数据进行返回,也提供了诸如权限管理、分库分表、事务、数据副本等能力,并且大多能够支持100个节点以上的集群,大大降低了数据库运维的成本,并且使数据库也能够实现水平扩展。

数据库和Tomcat都能够水平扩展,可支撑的并发大幅提高,随着用户数的增长,最终单机的Nginx会成为瓶颈
##第七次演进:使用LVS或F5来使多个Nginx负载均衡

8.png

由于瓶颈在Nginx,因此无法通过两层的Nginx来实现多个Nginx的负载均衡。图中的LVS和F5是工作在网络第四层的负载均衡解决方案,其中LVS是软件,运行在操作系统内核态,可对TCP请求或更高层级的网络协议进行转发,因此支持的协议更丰富,并且性能也远高于Nginx,可假设单机的LVS可支持几十万个并发的请求转发;F5是一种负载均衡硬件,与LVS提供的能力类似,性能比LVS更高,但价格昂贵。由于LVS是单机版的软件,若LVS所在服务器宕机则会导致整个后端系统都无法访问,因此需要有备用节点。可使用keepalived软件模拟出虚拟IP,然后把虚拟IP绑定到多台LVS服务器上,浏览器访问虚拟IP时,会被路由器重定向到真实的LVS服务器,当主LVS服务器宕机时,keepalived软件会自动更新路由器中的路由表,把虚拟IP重定向到另外一台正常的LVS服务器,从而达到LVS服务器高可用的效果。

此处需要注意的是,上图中从Nginx层到Tomcat层这样画并不代表全部Nginx都转发请求到全部的Tomcat,在实际使用时,可能会是几个Nginx下面接一部分的Tomcat,这些Nginx之间通过keepalived实现高可用,其他的Nginx接另外的Tomcat,这样可接入的Tomcat数量就能成倍的增加。

由于LVS也是单机的,随着并发数增长到几十万时,LVS服务器最终会达到瓶颈,此时用户数达到千万甚至上亿级别,用户分布在不同的地区,与服务器机房距离不同,导致了访问的延迟会明显不同
##第八次演进:通过DNS轮询实现机房间的负载均衡

9.png

在DNS服务器中可配置一个域名对应多个IP地址,每个IP地址对应到不同的机房里的虚拟IP。当用户访问www.taobao.com时,DNS服务器会使用轮询策略或其他策略,来选择某个IP供用户访问。此方式能实现机房间的负载均衡,至此,系统可做到机房级别的水平扩展,千万级到亿级的并发量都可通过增加机房来解决,系统入口处的请求并发量不再是问题。

随着数据的丰富程度和业务的发展,检索、分析等需求越来越丰富,单单依靠数据库无法解决如此丰富的需求
##第九次演进:引入NoSQL数据库和搜索引擎等技术

10.png

当数据库中的数据多到一定规模时,数据库就不适用于复杂的查询了,往往只能满足普通查询的场景。对于统计报表场景,在数据量大时不一定能跑出结果,而且在跑复杂查询时会导致其他查询变慢,对于全文检索、可变数据结构等场景,数据库天生不适用。因此需要针对特定的场景,引入合适的解决方案。如对于海量文件存储,可通过分布式文件系统HDFS解决,对于key value类型的数据,可通过HBase和Redis等方案解决,对于全文检索场景,可通过搜索引擎如ElasticSearch解决,对于多维分析场景,可通过Kylin或Druid等方案解决。

当然,引入更多组件同时会提高系统的复杂度,不同的组件保存的数据需要同步,需要考虑一致性的问题,需要有更多的运维手段来管理这些组件等。

引入更多组件解决了丰富的需求,业务维度能够极大扩充,随之而来的是一个应用中包含了太多的业务代码,业务的升级迭代变得困难
##第十次演进:大应用拆分为小应用

11.png

按照业务板块来划分应用代码,使单个应用的职责更清晰,相互之间可以做到独立升级迭代。这时候应用之间可能会涉及到一些公共配置,可以通过分布式配置中心Zookeeper来解决。

不同应用之间存在共用的模块,由应用单独管理会导致相同代码存在多份,导致公共功能升级时全部应用代码都要跟着升级
##第十一次演进:复用的功能抽离成微服务

12.png

如用户管理、订单、支付、鉴权等功能在多个应用中都存在,那么可以把这些功能的代码单独抽取出来形成一个单独的服务来管理,这样的服务就是所谓的微服务,应用和服务之间通过HTTP、TCP或RPC请求等多种方式来访问公共服务,每个单独的服务都可以由单独的团队来管理。此外,可以通过Dubbo、SpringCloud等框架实现服务治理、限流、熔断、降级等功能,提高服务的稳定性和可用性。

不同服务的接口访问方式不同,应用代码需要适配多种访问方式才能使用服务,此外,应用访问服务,服务之间也可能相互访问,调用链将会变得非常复杂,逻辑变得混乱
##第十二次演进:引入企业服务总线ESB屏蔽服务接口的访问差异

13.png

通过ESB统一进行访问协议转换,应用统一通过ESB来访问后端服务,服务与服务之间也通过ESB来相互调用,以此降低系统的耦合程度。这种单个应用拆分为多个应用,公共服务单独抽取出来来管理,并使用企业消息总线来解除服务之间耦合问题的架构,就是所谓的SOA(面向服务)架构,这种架构与微服务架构容易混淆,因为表现形式十分相似。个人理解,微服务架构更多是指把系统里的公共服务抽取出来单独运维管理的思想,而SOA架构则是指一种拆分服务并使服务接口访问变得统一的架构思想,SOA架构中包含了微服务的思想。

业务不断发展,应用和服务都会不断变多,应用和服务的部署变得复杂,同一台服务器上部署多个服务还要解决运行环境冲突的问题,此外,对于如大促这类需要动态扩缩容的场景,需要水平扩展服务的性能,就需要在新增的服务上准备运行环境,部署服务等,运维将变得十分困难。
##第十三次演进:引入容器化技术实现运行环境隔离与动态服务管理

14.png

目前最流行的容器化技术是Docker,最流行的容器管理服务是Kubernetes(K8S),应用/服务可以打包为Docker镜像,通过K8S来动态分发和部署镜像。Docker镜像可理解为一个能运行你的应用/服务的最小的操作系统,里面放着应用/服务的运行代码,运行环境根据实际的需要设置好。把整个“操作系统”打包为一个镜像后,就可以分发到需要部署相关服务的机器上,直接启动Docker镜像就可以把服务起起来,使服务的部署和运维变得简单。

在大促的之前,可以在现有的机器集群上划分出服务器来启动Docker镜像,增强服务的性能,大促过后就可以关闭镜像,对机器上的其他服务不造成影响(在3.14节之前,服务运行在新增机器上需要修改系统配置来适配服务,这会导致机器上其他服务需要的运行环境被破坏)。

使用容器化技术后服务动态扩缩容问题得以解决,但是机器还是需要公司自身来管理,在非大促的时候,还是需要闲置着大量的机器资源来应对大促,机器自身成本和运维成本都极高,资源利用率低
##第十四次演进:以云平台承载系统

15.png

系统可部署到公有云上,利用公有云的海量机器资源,解决动态硬件资源的问题,在大促的时间段里,在云平台中临时申请更多的资源,结合Docker和K8S来快速部署服务,在大促结束后释放资源,真正做到按需付费,资源利用率大大提高,同时大大降低了运维成本。

所谓的云平台,就是把海量机器资源,通过统一的资源管理,抽象为一个资源整体,在之上可按需动态申请硬件资源(如CPU、内存、网络等),并且之上提供通用的操作系统,提供常用的技术组件(如Hadoop技术栈,MPP数据库等)供用户使用,甚至提供开发好的应用,用户不需要关系应用内部使用了什么技术,就能够解决需求(如音视频转码服务、邮件服务、个人博客等)。在云平台中会涉及如下几个概念:

* IaaS:基础设施即服务。对应于上面所说的机器资源统一为资源整体,可动态申请硬件资源的层面;
* PaaS:平台即服务。对应于上面所说的提供常用的技术组件方便系统的开发和维护;
* SaaS:软件即服务。对应于上面所说的提供开发好的应用或服务,按功能或性能要求付费。

至此,以上所提到的从高并发访问问题,到服务的架构和系统实施的层面都有了各自的解决方案,但同时也应该意识到,在上面的介绍中,其实是有意忽略了诸如跨机房数据同步、分布式事务实现等等的实际问题,这些问题以后有机会再拿出来单独讨论。
#架构设计总结

##架构的调整是否必须按照上述演变路径进行?

不是的,以上所说的架构演变顺序只是针对某个侧面进行单独的改进,在实际场景中,可能同一时间会有几个问题需要解决,或者可能先达到瓶颈的是另外的方面,这时候就应该按照实际问题实际解决。如在政府类的并发量可能不大,但业务可能很丰富的场景,高并发就不是重点解决的问题,此时优先需要的可能会是丰富需求的解决方案。
##对于将要实施的系统,架构应该设计到什么程度?

对于单次实施并且性能指标明确的系统,架构设计到能够支持系统的性能指标要求就足够了,但要留有扩展架构的接口以便不备之需。对于不断发展的系统,如电商平台,应设计到能满足下一阶段用户量和性能指标要求的程度,并根据业务的增长不断的迭代升级架构,以支持更高的并发和更丰富的业务。
##服务端架构和大数据架构有什么区别?

所谓的“大数据”其实是海量数据采集清洗转换、数据存储、数据分析、数据服务等场景解决方案的一个统称,在每一个场景都包含了多种可选的技术,如数据采集有Flume、Sqoop、Kettle等,数据存储有分布式文件系统HDFS、FastDFS,NoSQL数据库HBase、MongoDB等,数据分析有Spark技术栈、机器学习算法等。总的来说大数据架构就是根据业务的需求,整合各种大数据组件组合而成的架构,一般会提供分布式存储、分布式计算、多维分析、数据仓库、机器学习算法等能力。而服务端架构更多指的是应用组织层面的架构,底层能力往往是由大数据架构来提供。
#有没有一些架构设计的原则?


* N+1设计。系统中的每个组件都应做到没有单点故障;
* 回滚设计。确保系统可以向前兼容,在系统升级时应能有办法回滚版本;
* 禁用设计。应该提供控制具体功能是否可用的配置,在系统出现故障时能够快速下线功能;
* 监控设计。在设计阶段就要考虑监控的手段;
* 多活数据中心设计。若系统需要极高的高可用,应考虑在多地实施数据中心进行多活,至少在一个机房断电的情况下系统依然可用;
* 采用成熟的技术。刚开发的或开源的技术往往存在很多隐藏的bug,出了问题没有商业支持可能会是一个灾难;
* 资源隔离设计。应避免单一业务占用全部资源;
* 架构应能水平扩展。系统只有做到能水平扩展,才能有效避免瓶颈问题;
* 非核心则购买。非核心功能若需要占用大量的研发资源才能解决,则考虑购买成熟的产品;
* 使用商用硬件。商用硬件能有效降低硬件故障的机率;
* 快速迭代。系统应该快速开发小功能模块,尽快上线进行验证,早日发现问题大大降低系统交付的风险;
* 无状态设计。服务接口应该做成无状态的,当前接口的访问不依赖于接口上次访问的状态。

原文链接:https://segmentfault.com/a/1190000018626163

ZooKeeper开发分布式系统,动态服务上下线感知

JetLee 发表了文章 • 0 个评论 • 151 次浏览 • 2019-06-04 10:31 • 来自相关话题

#什么是ZooKeeper ZooKeeper是一个分布式开源框架,提供了协调分布式应用的基本服务,它向外部应用暴露一组通用服务——分布式同步(Distributed Synchronization)、命名服务(Naming Service)、集 ...查看全部
#什么是ZooKeeper

ZooKeeper是一个分布式开源框架,提供了协调分布式应用的基本服务,它向外部应用暴露一组通用服务——分布式同步(Distributed Synchronization)、命名服务(Naming Service)、集群维护(Group Maintenance)等,简化分布式应用协调及其管理的难度,提供高性能的分布式服务。ZooKeeper本身可以以单机模式安装运行,不过它的长处在于通过分布式ZooKeeper集群(一个Leader,多个Follower),基于一定的策略来保证ZooKeeper集群的稳定性和可用性,从而实现分布式应用的可靠性。
#Zookeeper简介


  1. ZooKeeper是为别的分布式程序服务的
  2. ZooKeeper本身就是一个分布式程序(只要有半数以上节点存活,ZooKeeper就能正常服务)
  3. ZooKeeper所提供的服务涵盖:主从协调、服务器节点动态上下线、统一配置管理、分布式共享锁、统> 一名称服务等
  4. 虽然说可以提供各种服务,但是ZooKeeper在底层其实只提供了两个功能:

1. 管理(存储,读取)用户程序提交的数据(类似namenode中存放的metadata)
2. 为用户程序提供数据节点监听服务

#ZooKeeper应用场景图

1.jpg

2.jpg

#ZooKeeper集群机制

ZooKeeper集群的角色: Leader 和 follower

只要集群中有半数以上节点存活,集群就能提供服务。
#ZooKeeper特性


  1. ZooKeeper:一个leader,多个follower组成的集群
  2. 全局数据一致:每个server保存一份相同的数据副本,client无论连接到哪个server,数据都是一致的
  3. 分布式读写,更新请求转发,由leader实施
  4. 更新请求顺序进行,来自同一个client的更新请求按其发送顺序依次执行
  5. 数据更新原子性,一次数据更新要么成功,要么失败
  6. 实时性,在一定时间范围内,client能读到最新数据

#ZooKeeper的数据存储机制

##数据存储形式

ZooKeeper中对用户的数据采用kv形式存储。如果你想和更多ZooKeeper技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

只是ZooKeeper有点特别:

key:是以路径的形式表示的,那就以为着,各key之间有父子关系,比如:

* / 是顶层key
* 用户建的key只能在/ 下作为子节点,比如建一个key: /aa 这个key可以带value数据
* 也可以建一个key: /bb
* 也可以建key: /aa/xx

ZooKeeper中,对每一个数据key,称作一个znode

综上所述,ZooKeeper中的数据存储形式如下:
3.jpg

##znode类型

ZooKeeper中的znode有多种类型:

  1. PERSISTENT 持久的:创建者就算跟集群断开联系,该类节点也会持久存在与zk集群中
  2. EPHEMERAL 短暂的:创建者一旦跟集群断开联系,zk就会将这个节点删除
  3. SEQUENTIAL 带序号的:这类节点,zk会自动拼接上一个序号,而且序号是递增的

组合类型:

* PERSISTENT :持久不带序号
* EPHEMERAL :短暂不带序号
* PERSISTENT 且 SEQUENTIAL :持久且带序号
* EPHEMERAL 且 SEQUENTIAL :短暂且带序号

#ZooKeeper的集群部署

集群选举示意图:
4.jpg

解压ZooKeeper安装包到apps目录下:
tar -zxvf zookeeper-3.4.6.tar.gz -C apps

cd /root/apps/zookeeper-3.4.6/conf
cp zoo_sample.cfg zoo.cfg
vi zoo.cfg

修改dataDir=/root/zkdata

在后面加上集群的机器:2888是leader和follower通讯端口,3888是投票的
server.1=hdp-01:2888:3888
server.2=hdp-02:2888:3888
server.3=hdp-03:2888:3888

对3台节点,都创建目录 mkdir /root/zkdata

对3台节点,在工作目录中生成myid文件,但内容要分别为各自的id: 1,2,3
echo 1 > /root/zkdata/myid
echo 2 > /root/zkdata/myid
echo 3 > /root/zkdata/myid

从hdp20-01上scp安装目录到其他两个节点
cd apps
scp -r zookeeper-3.4.6/ hdp-02:$PWD
scp -r zookeeper-3.4.6/ hdp-03:$PWD

启动ZooKeeper集群

ZooKeeper没有提供自动批量启动脚本,需要手动一台一台地起ZooKeeper进程

在每一台节点上,运行命令:
cd /root/apps/zookeeper-3.4.6
bin/zkServer.sh start

启动后,用jps应该能看到一个进程:QuorumPeerMain

但是,光有进程不代表zk已经正常服务,需要用命令检查状态:
bin/zkServer.sh status

能看到角色模式:为leader或follower,即正常了。

自己写个脚本,一键启动
vi zkmanage.sh

#!/bin/bash
for host in hdp-01 hdp-02 hdp-03
do
echo "${host}:$1ing....."
ssh $host "/root/apps/zookeeper-3.4.6/bin/zkServer.sh $1"
done

停止命令:sh zjmanage.sh stop

加个可执行权限:chmod +zkmanage.sh

启动命令:./zkmanage.sh start

但是出现没有Java环境变量问题,修改配置文件
vi zkmanage.sh

修改配置如下:
#!/bin/bash
for host in hdp-01 hdp-02 hdp-03
do
echo "${host}:$1ing....."
ssh $host "source /etc/profile;/root/apps/zookeeper-3.4.6/bin/zkServer.sh $1"
done

sleep 2
for host in hdp-01 hdp-02 hdp-03
do
ssh $host "source /etc/profile;/root/apps/zookeeper-3.4.6/bin/zkServer.sh status"
done

启动集群结果:
hdp-01:starting.....
JMX enabled by default
Using config: /root/apps/zookeeper-3.4.6/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
hdp-02:starting.....
JMX enabled by default
Using config: /root/apps/zookeeper-3.4.6/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
hdp-03:starting.....
JMX enabled by default
Using config: /root/apps/zookeeper-3.4.6/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
JMX enabled by default
Using config: /root/apps/zookeeper-3.4.6/bin/../conf/zoo.cfg
Mode: follower
JMX enabled by default
Using config: /root/apps/zookeeper-3.4.6/bin/../conf/zoo.cfg
Mode: leader
JMX enabled by default
Using config: /root/apps/zookeeper-3.4.6/bin/../conf/zoo.cfg
Mode: follower

ZooKeeper的Java客户端操作代码:
public class ZookeeperCliDemo {
ZooKeeper zk =null;
@Before
public void init() throws Exception {
zk=new ZooKeeper("hdp-01:2181,hdp-02:2181,hdp-03:2181", 2000, null);
}
@Test
public void testCreate() throws Exception {
//参数1:要创建的节点路径;参数2:数据;参数3:访问权限;参数4:节点类型
String create = zk.create("/eclipse", "hello eclipse".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println(create);
zk.close();
}

@Test
public void testUpdate() throws Exception {
//参数1:节点路径;参数2:数据;参数3:所要修改的版本,-1表示任意版本
zk.setData("/eclipse","我喜欢青青".getBytes(),-1);
zk.close();
}
@Test
public void testGet() throws Exception {
//参数1:节点路径;参数2:事件监听;参数3:所要修改的版本,null表示最新版本
byte[] data = zk.getData("/eclipse", false, null);
System.out.println(new String(data,"UTF-8"));
zk.close();
}

@Test
public void testListChildren() throws KeeperException, InterruptedException {
//参数1:节点路径;参数2:是否要监听
//注意:返回的结果只有子节点的名字,不带全路径
List children = zk.getChildren("/cc", false);
for(String child:children){
System.out.println(child);
}
zk.close();
}

@Test
public void testRm() throws KeeperException, InterruptedException {
zk.delete("/eclipse",-1);
zk.close();
}
}

ZooKeeper监听功能代码:
public class ZookeeperWatchDemo {
ZooKeeper zk =null;
@Before
public void init() throws Exception {
zk=new ZooKeeper("hdp-01:2181,hdp-02:2181,hdp-03:2181", 2000, new Watcher() {
public void process(WatchedEvent watchedEvent) {
if (watchedEvent.getState() == Event.KeeperState.SyncConnected
&& watchedEvent.getType() == Event.EventType.NodeDataChanged) {
System.out.println("收到事件所发生节点的路径" + watchedEvent.getPath());
System.out.println("收到事件所发生节点的状态" + watchedEvent.getState());
System.out.println("收到事件所发生节点的类型" + watchedEvent.getType());
System.out.println("watch事件通知。。换照片");
try {
zk.getData("/mygirls", true, null);
} catch (Exception e) {
e.printStackTrace();
}
}else if(watchedEvent.getState()==Event.KeeperState.SyncConnected &&
watchedEvent.getType()==Event.EventType.NodeChildrenChanged){
System.out.println("收到事件所发生节点的路径" + watchedEvent.getPath());
System.out.println("收到事件所发生节点的状态" + watchedEvent.getState());
System.out.println("收到事件所发生节点的类型" + watchedEvent.getType());

}
}
});
}

@Test
public void testGetWatch() throws Exception {
byte[] data = zk.getData("/mygirls",true, null);
List children = zk.getChildren("/mygirls", true);
System.out.println(new String(data,"UTF-8"));
Thread.sleep(Long.MAX_VALUE);
}
}

ZooKeeper开发分布式系统案例代码,动态上下线感知。

服务代码:
public class TimeQueryServer {
ZooKeeper zk=null;
public void connectZk()throws Exception{
zk=new ZooKeeper("hdp-01:2181,hdp-02:2181,hdp-03:2181", 2000, null);
}

public void registerServerInfo(String hostname,String port)throws Exception{
/**
* 先判断注册节点的父节点是否存在,如果不存在,则创建持久节点
*/
Stat exists = zk.exists("/servers", false);
if(exists==null){
zk.create("/servers",null,ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
}
/**
* 注册服务器数据到zk的约定注册节点下
*/
String create = zk.create("/servers/server", (hostname + ":" + port).getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println(hostname+" 服务器向zk 注册成功,注册节点为:/servers"+create);
}
public static void main(String[] args) throws Exception {
//1.构造zk连接
TimeQueryServer timeQueryServer = new TimeQueryServer();
timeQueryServer.connectZk();
//2.注册服务器信息
timeQueryServer.registerServerInfo("192.168.150.3","44772");
//3.启动业务线程开始处理业务
new TimeQueryService(44772).start();
}
}

public class TimeQueryService extends Thread {
int port=0;
public TimeQueryService(int port){
this.port=port;
}
@Override
public void run() {
try {
ServerSocket ss = new ServerSocket(port);
System.out.println("业务线程已经绑定端口"+port+"开始接受客户端请求..");
while (true){
Socket sc = ss.accept();
InputStream inputStream = sc.getInputStream();
OutputStream outputStream = sc.getOutputStream();
outputStream.write(new Date().toString().getBytes());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

消费者代码:
public class Consumer {

//定义一个list用于存放在线的服务器列表
private volatile ArrayListonlineServers=new ArrayList();
ZooKeeper zk=null;
public void connectZk()throws Exception{
zk=new ZooKeeper("hdp-01:2181,hdp-02:2181,hdp-03:2181", 2000, new Watcher() {
public void process(WatchedEvent watchedEvent) {
if (watchedEvent.getState()==Event.KeeperState.SyncConnected && watchedEvent.getType()==Event.EventType.NodeChildrenChanged){
try{
//事件回调逻辑中,再次查询zk上在线服务器节点即可,查询逻辑中又再次注册子节点变化事件监听
getOnlineServers();
}catch (Exception e){
e.printStackTrace();
}
}
}
});
}
//查询在线服务器列表
public void getOnlineServers()throws Exception{
List children = zk.getChildren("/servers", true);
ArrayList servers = new ArrayList();
for (String child:children){
byte[] data = zk.getData("/servers/" + child, false, null);
String serverInfo=new String(data);
servers.add(serverInfo);
}
onlineServers=servers;
System.out.println("查询了一次zk,当前在线的服务器有:"+servers);

}

public void setRequest() throws Exception {
Random random = new Random();
while (true){
try {
int nextInt=random.nextInt(onlineServers.size());
String server=onlineServers.get(nextInt);
String hostname=server.split(":")[0];
int port=Integer.parseInt(server.split(":")[1]);
System.out.println("本次请求挑选的服务器为:"+server);

Socket socket = new Socket(hostname, port);
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();
out.write("hahaha".getBytes());
out.flush();

byte[] buf = new byte[256];
int read=in.read(buf);
String s = new String(buf, 0, read);
System.out.println("服务器响应时间为:"+s);
out.close();
in.close();
socket.close();
Thread.sleep(2000);
}catch (Exception e){

}

}
}
public static void main(String[] args) throws Exception {
//构造zk连接对象
Consumer consumer = new Consumer();
consumer.connectZk();
//查询在线服务器列表
consumer.getOnlineServers();
//处理业务
consumer.setRequest();
}
}

pom


junit
junit
RELEASE


org.apache.logging.log4j
log4j-core
2.8.2



org.apache.zookeeper
zookeeper
3.4.10


启动多个服务。

控制台输出:

192.168.150.3 服务器向zk 注册成功,注册节点为:/servers/servers/server0000000018
业务线程已经绑定端口44772开始接受客户端请求..

192.168.150.3 服务器向zk 注册成功,注册节点为:/servers/servers/server0000000019
业务线程已经绑定端口44773开始接受客户端请求..

192.168.150.3 服务器向zk 注册成功,注册节点为:/servers/servers/server0000000020
业务线程已经绑定端口44774开始接受客户端请求..

消费者启动

控制台输出:

查询了一次zk,当前在线的服务器有:[192.168.150.3:44773, 192.168.150.3:44772, 192.168.150.3:44774]
本次请求挑选的服务器为:192.168.150.3:44772
服务器响应时间为:Mon Jun 03 20:03:21 CST 2019
本次请求挑选的服务器为:192.168.150.3:44773
服务器响应时间为:Mon Jun 03 20:03:23 CST 2019
本次请求挑选的服务器为:192.168.150.3:44773
服务器响应时间为:Mon Jun 03 20:03:25 CST 2019
本次请求挑选的服务器为:192.168.150.3:44772
服务器响应时间为:Mon Jun 03 20:03:27 CST 2019

下线一个服务后,控制台输出:

查询了一次zk,当前在线的服务器有:[192.168.150.3:44773, 192.168.150.3:44772]
本次请求挑选的服务器为:192.168.150.3:44773
服务器响应时间为:Mon Jun 03 20:04:19 CST 2019
本次请求挑选的服务器为:192.168.150.3:44773
服务器响应时间为:Mon Jun 03 20:04:21 CST 2019
本次请求挑选的服务器为:192.168.150.3:44773
服务器响应时间为:Mon Jun 03 20:04:23 CST 2019
本次请求挑选的服务器为:192.168.150.3:44773

原文链接:https://my.oschina.net/u/3995125/blog/3057475

基于 ZooKeeper 实现爬虫集群的监控

阿娇 发表了文章 • 0 个评论 • 154 次浏览 • 2019-05-29 13:03 • 来自相关话题

Zookeeper 扩展之殇

齐达内 发表了文章 • 0 个评论 • 156 次浏览 • 2019-05-28 12:27 • 来自相关话题

#一、背景 基于公司发展硬性需求,生产VM服务器要统一迁移到ZStack虚拟化服务器。检查自己项目使用的服务器,其中ZooKeeper集群中招,所以需要进行迁移。如果你想和更多ZooKeeper技术专家交流,可以加我微信liyingjiese,备 ...查看全部
#一、背景

基于公司发展硬性需求,生产VM服务器要统一迁移到ZStack虚拟化服务器。检查自己项目使用的服务器,其中ZooKeeper集群中招,所以需要进行迁移。如果你想和更多ZooKeeper技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态
#二、迁移计划

为了使迁移不对业务产生影响,所以最好是采用扩容 -> 缩容 的方式进行。
1.png

说明:

  1. 原生产集群为VM-1,VM-2,VM-3组成一个3节点的ZooKeeper集群;
  2. 对该集群扩容,增加至6节点(新增ZS-1,ZS-2,ZS-3),进行数据同步完成;
  3. 进行缩容,下掉原先来的三个节点(VM-1,VM-2,VM-3);
  4. 替换Nginx解析地址。

OK! 目标很明确,过程也很清晰,然后开干。
#三、步骤(过程已在测试环境验证无问题)

对新增的三台服务器进行zk环境配置,和老集群配置一样即可,最好使用同一版本(版主使用的是3.4.6);

对老节点的zoo.cfg增加新集群的地址(逐一增加),然后对新增加节点逐一重启。
2.png

#四、问题

ZS-1 启动成功,zkServer.sh status报错,用zkServer.sh status查看,反馈如下异常:
[root@localhost bin]# ./zkServer.sh  status
ZooKeeper JMX enabled by default
Using config: /usr/zookeeper/zookeeper-3.4.6/bin/../conf/zoo.cfg
Error contacting service. It is probably not running.

此时查看数据,数据同步正常。

ZS-1数据同步正常,但是无法查看节点的状态信息;

怀疑是因为老节点没有重启的原因;此时去查看原集群节点信息,发现原集群节点状态异常。经排查定位,原集群的状态一直处于异常状态。

初步定位原因可能是原集群的选举存在异常,导致新节点无法正常纳入,继续排查。

恢复集群初始状态,如果集群节点的状态一直没法正常查看。OK 继续定位……
#五、排查过程

以下方法来自于网络:

可能有以下几个原因:

第一、zoo.cfg文件配置:dataLogDir指定的目录未被创建。
1.zoo.cfg
[root@SIA-215 conf]# cat zoo.cfg
...
dataDir=/app/zookeeperdata/data
dataLogDir=/app/zookeeperdata/log
...

2.路径
[root@SIA-215 conf]# cd /app/zookeeperdata/
[root@SIA-215 zookeeperdata]# ll
total 8
drwxr-xr-x 3 root root 4096 Apr 23 19:59 data
drwxr-xr-x 3 root root 4096 Aug 29 2015 log

经排查 排除该因素。

第二、myid文件中的整数格式不对,或者与zoo.cfg中的server整数不对应。
[root@SIA-215 data]# cd /app/zookeeperdata/data
[root@SIA-215 data]# cat myid
2[root@SIA-215 data]#

定位排查后排除不是该原因。

第三、防火墙未关闭。

使用service iptables stop关闭防火墙;使用service iptables status确认;使用chkconfig iptables off禁用防火墙。

确认防火墙是关闭的。
[root@localhost ~]# service iptables status
iptables: Firewall is not running.
确认防火墙是关闭的

第四、端口被占用。
[root@localhost bin]# netstat -tunlp | grep 2181
tcp 0 0 :::12181 :::* LISTEN 30035/java
tcp 0 0 :::22181 :::* LISTEN 30307/java

确认端口没有被占用

第五、zoo.cfg文件中主机名出错。

经测试环境测试,主机名正确,多域名解析也正常,不存在此问题。

第六、hosts文件中,本机的主机名有两个对应,只需保留主机名和IP地址的映射。

经测试环境测试,主机名正确,多域名解析也正常,不存在此问题 排除。

第七、zkServer.sh里的nc命令有问题。

可能是机器上没有安装nc命令,还有种说法是在zkServer.sh里找到这句:

STAT=`echo stat | nc localhost $(grep clientPort “$ZOOCFG” | sed -e ‘s/.*=//’) 2> /dev/null| grep Mode`
在nc与localhost之间加上 -q 1 (是数字1而不是字母l)。

ZooKeeper版本是3.4.6,zkServer.sh里根本没有这一句(获取状态的语句没有用nc命令)
 # -q is necessary on some versions of linux where nc returns too quickly, and no stat result is output
clientPortAddress=`grep "^[[:space:]][i]clientPortAddress[^[:alpha:]]" "$ZOOCFG" | sed -e 's/.[/i]=//'`
if ! [ $clientPortAddress ]
then
clientPortAddress="localhost"
fi
clientPort=`grep "^[[:space:]][i]clientPort[^[:alpha:]]" "$ZOOCFG" | sed -e 's/.[/i]=//'`
STAT=`"$JAVA" "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \
-cp "$CLASSPATH" $JVMFLAGS org.apache.zookeeper.client.FourLetterWordMain \
$clientPortAddress $clientPort srvr 2> /dev/null \
| grep Mode`
if [ "x$STAT" = "x" ]
then
echo "Error contacting service. It is probably not running."
exit 1
else
echo $STAT
exit 0
fi
;;

#六、以下是自己排查的方式

目前现象老集群数据同步正常,也能进行leader选举(从日志获取),但是无法查看节点状态,同异常信息;进行集群扩容,数据不能同步。

解决方法:

1、尝试进行foreground 模式启动,选择一台非主节点进行重启,可以前台查看启动日志。
zkserver.sh start-foreground

节点启动正常,无异常输出。
2、查看shell脚本:分析zkServer.sh。

"Error contacting service. It is probably not running." 这块日志出现以下脚本中。
STAT=`"$JAVA" "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \
-cp "$CLASSPATH" $JVMFLAGS org.apache.zookeeper.client.FourLetterWordMain \
$clientPortAddress $clientPort srvr 2> /dev/null \
| grep Mode`
if [ "x$STAT" = "x" ]
then
echo "Error contacting service. It is probably not running."
exit 1
else
echo $STAT
exit 0
fi
;;

截取其中一部分脚本内容:我们可以初步定为应该是 $STAT 获取存在异常 如果STAT变量为空,则会显示Error contacting service. It is probably not running.: OK,那就分析下这个$STAT到底是什么鬼?
 if [ “x$STAT” = “x” ]
then
echo “Error contacting service. It is probably not running.”
exit 1
else
echo $STAT
exit 0
fi

3、尝试用shell的debug模式 看下执行过程:

截取片段执行日志如下 :果然STAT变量确实为空,导致输出Error contacting service. It is probably not running.并且退出。
++ grep '^[[:space:]]*clientPort[^[:alpha:]]' /app/zookeeper-3.4.6/bin/../conf/zoo.cfg
+ clientPort=5181
++ grep Mode
++ /opt/jdk1.8.0_131/bin/java -Dzookeeper.log.dir=. -Dzookeeper.root.logger=INFO,CONSOLE -cp '/app/zookeeper-3.4.6/bin/../build/classes:/app/zookeeper-3.4.6/bin/../build/lib/[i].jar:/app/zookeeper-3.4.6/bin/../lib/slf4j-log4j12-1.6.1.jar:/app/zookeeper-3.4.6/bin/../lib/slf4j-api-1.6.1.jar:/app/zookeeper-3.4.6/bin/../lib/netty-3.7.0.Final.jar:/app/zookeeper-3.4.6/bin/../lib/log4j-1.2.16.jar:/app/zookeeper-3.4.6/bin/../lib/jline-0.9.94.jar:/app/zookeeper-3.4.6/bin/../zookeeper-3.4.6.jar:/app/zookeeper-3.4.6/bin/../src/java/lib/[/i].jar:/app/zookeeper-3.4.6/bin/../conf:.:/opt/jdk1.8.0_131/lib/dt.jar:/opt/jdk1.8.0_131/lib/tools.jar' org.apache.zookeeper.client.FourLetterWordMain localhost 5181 srvr
+ STAT=
+ ‘[‘ x = x ‘]’
+ echo ‘Error contacting service. It is probably not running.’
Error contacting service. It is probably not running.
+ exit 1

4、修改shell脚本:分析zkServer.sh 在脚本总增加输出STAT 内容,这次我们不进行过滤。
STAT1=`"$JAVA" "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \
-cp "$CLASSPATH" $JVMFLAGS org.apache.zookeeper.client.FourLetterWordMain \
$clientPortAddress $clientPort srvr 2> test.log \ `

echo "$STAT1"

最好的方式是copy一个新脚本,以免污染原本的脚本。我是这么做的;然后运行该脚本。
[root@localhost bin]# ./zkServer.sh  status
ZooKeeper JMX enabled by default
Using config: /usr/zookeeper/zookeeper-3.4.10/bin/../conf/zoo.cfg
Error contacting service. It is probably not running.

然后查看生成的test.log 文件:果然存在异常内容。
in thread “main” java.lang.NumberFormatException: For input string: “2181
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:492)
at java.lang.Integer.parseInt(Integer.java:527)
at org.apache.zookeeper.client.FourLetterWordMain.main(FourLetterWordMain.java:76)

从日志信息来看,提示说2181这个端口号造成的。 不是一个合法的数字。

zkServer.sh里有这么一句:
clientPort=`grep “^[[:space:]][i]clientPort[^[:alpha:]]” “$ZOOCFG” | sed -e ‘s/.[/i]=//’`
grep “^[[:space:]][i]clientPort[^[:alpha:]]” “$ZOOCFG” | sed -e ‘s/.[/i]=//’在执行过程中,实际命令如下:
grep ‘^[[:space:]][i]clientPort[^[:alpha:]]’ /app/zookeeper-3.4.6/bin/../conf/zoo.cfg | sed -e ‘s/.[/i]=//’

最终可以基本确认配置文件存在问题。

替换配置文件:重启,问题解决。

存在原因可能是编辑zoo.cfg编码格式等等引起文件内容解析异常。

原文链接:http://college.creditease.cn/detail/252

从零开始搭建创业公司后台技术栈

Andy_Lee 发表了文章 • 0 个评论 • 988 次浏览 • 2019-04-24 22:09 • 来自相关话题

说到后台技术栈,脑海中是不是浮现的是这样一幅图?有点眼晕,以下只是我们会用到的一些语言的合集,而且只是语言层面的一部分,就整个后台技术栈来说,这只是一个开始,从语言开始,还有很多很多的内容。今天要说的后台是大后台的概念,放在服务器上的东西都属于后台的东西,比如 ...查看全部
说到后台技术栈,脑海中是不是浮现的是这样一幅图?有点眼晕,以下只是我们会用到的一些语言的合集,而且只是语言层面的一部分,就整个后台技术栈来说,这只是一个开始,从语言开始,还有很多很多的内容。今天要说的后台是大后台的概念,放在服务器上的东西都属于后台的东西,比如使用的框架,语言,数据库,服务,操作系统等等。
svr_stack1.png

整个后台技术栈我的理解包括 4 个层面的内容:

* 语言: 用了哪些开发语言,如:C++/Java/Go/PHP/Python/Ruby 等等;
* 组件:用了哪些组件,如:MQ 组件,数据库组件等等;
* 流程:怎样的流程和规范,如:开发流程,项目流程,发布流程,监控告警流程,代码规范等等;
* 系统:系统化建设,上面的流程需要有系统来保证,如:规范发布流程的发布系统,代码管理系统等等;

结合以上的的 4 个层面的内容,整个后台技术栈的结构如图 2 所示:
svr_stack2.png

图2 后台技术栈结构

以上的这些内容都需要我们从零开始搭建,在创业公司,没有大公司那些完善的基础设施,需要我们从开源界,从云服务商甚至有些需要自己去组合,去拼装,去开发一个适合自己的组件或系统以达成我们的目标。咱们一个个系统和组件的做选型,最终形成我们的后台技术栈。
#各系统组件选型
##项目管理/Bug管理/问题管理
项目管理软件是整个业务的需求,问题,流程等等的集中地,大家的跨部门沟通协同大多依赖于项目管理工具。有一些 SaaS 的项目管理服务可以使用,但是很多时间不满足需求,此时我们可以选择一些开源的项目,这些项目本身有一定的定制能力,有丰富的插件可以使用,一般的创业公司需求基本上都能得到满足,常用的项目如下:

* Redmine: 用 Ruby 开发的,有较多的插件可以使用,能自定义字段,集成了项目管理,Bug 问题跟踪,WIKI 等功能,不过好多插件 N 年没有更新了;
* Phabricator:用 PHP 开发的,Facebook 之前的内部工具,开发这工具的哥们离职后自己搞了一个公司专门做这个软件,集成了代码托管, Code Review,任务管理,文档管理,问题跟踪等功能,强烈推荐较敏捷的团队使用;
* Jira:用 Java 开发的,有用户故事,task 拆分,燃尽图等等,可以做项目管理,也可以应用于跨部门沟通场景,较强大;
* 悟空 CRM :这个不是项目管理,这个是客户管理,之所以在这里提出来,是因为在 To B 的创业公司里面,往往是以客户为核心来做事情的,可以将项目管理和问题跟进的在悟空 CRM 上面来做,他的开源版本已经基本实现了 CR< 的核心 功能,还带有一个任务管理功能,用于问题跟进,不过用这个的话,还是需要另一个项目管理的软件协助,顺便说一嘴,这个系统的代码写得很难维护,只能适用于客户规模小(1万以内)时。

##DNS
DNS 是一个很通用的服务,创业公司基本上选择一个合适的云厂商就行了,国内主要是两家:

* 阿里万网:阿里 2014 年收购了万网,整合了其域名服务,最终形成了现在的阿里万网,其中就包含 DNS 这块的服务;
* 腾讯 DNSPod:腾讯 2012 年以 4000 万收购 DNSPod 100% 股份,主要提供域名解析和一些防护功能;

如果你的业务是在国内,主要就是这两家,选 一个就好,像今日头条这样的企业用的也是 DNSPod 的服务,除非一些特殊的原因才需要自建,比如一些 CDN 厂商,或者对区域有特殊限制的。要实惠一点用阿里最便宜的基础版就好了,要成功率高一些,还是用 DNSPod 的贵的那种。

在国外还是选择亚马逊吧,阿里的 DNS 服务只有在日本和美国有节点,东南亚最近才开始部点, DNSPod 也只有美国和日本,像一些出海的企业,其选择的云服务基本都是亚马逊。

如果是线上产品,DNS 强烈建议用付费版,阿里的那几十块钱的付费版基本可以满足需求。如果还需要一些按省份或按区域调试的逻辑,则需要加钱,一年也就几百块,省钱省力。

如果是国外,优先选择亚马逊,如果需要国内外互通并且有自己的 APP 的话,建议还是自己实现一些容灾逻辑或者智能调度,因为没有一个现成的 DNS 服务能同时较好的满足国内外场景,或者用多个域名,不同的域名走不同的 DNS 。
##LB(负载均衡)
LB(负载均衡)是一个通用服务,一般云厂商的 LB 服务基本都会如下功能:

* 支持四层协议请求(包括 TCP、UDP 协议);
* 支持七层协议请求(包括 HTTP、HTTPS 协议);
* 集中化的证书管理系统支持 HTTPS 协议;
* 健康检查;

如果你线上的服务机器都是用的云服务,并且是在同一个云服务商的话,可以直接使用云服务商提供的 LB 服务,如阿里云的 SLB,腾讯云的 CLB,亚马逊的 ELB 等等。如果是自建机房基本都是 LVS + Nginx。
##CDN
CDN 现在已经是一个很红很红的市场,基本上只能挣一些辛苦钱,都是贴着成本在卖。国内以网宿为龙头,他们家占据整个国内市场份额的 40% 以上,后面就是腾讯,阿里。网宿有很大一部分是因为直播的兴起而崛起。

国外,Amazon 和 Akamai 合起来占比大概在 50%,曾经的国际市场老大 Akamai 拥有全球超一半的份额,在 Amazon CDN入局后,份额跌去了将近 20%,众多中小企业都转向后者,Akamai 也是无能为力。

国内出海的 CDN 厂商,更多的是为国内的出海企业服务,三家大一点的 CDN 服务商里面也就网宿的节点多一些,但是也多不了多少。阿里和腾讯还处于前期阶段,仅少部分国家有节点。

就创业公司来说,CDN 用腾讯云或阿里云即可,其相关系统较完善,能轻松接入,网宿在系统支持层面相对较弱一些,而且还贵一些。并且,当流量上来后,CDN 不能只用一家,需要用多家,不同的 CDN 在全国的节点覆盖不一样,而且针对不同的客户云厂商内部有些区分客户集群,并不是全节点覆盖(但有些云厂商说自己是全网节点),除了节点覆盖的问题,多 CDN 也在一定程度上起到容灾的作用。
##RPC 框架
维基百科对 RPC 的定义是:远程过程调用(Remote Procedure Call,RPC)是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。

通俗来讲,一个完整的 RPC 调用过程,就是 Server 端实现了一个函数,客户端使用 RPC 框架提供的接口,调用这个函数的实现,并获取返回值的过程。

业界 RPC 框架大致分为两大流派,一种侧重跨语言调用,另一种是偏重服务治理。

跨语言调用型的 RPC 框架有 Thrift、gRPC、Hessian、Hprose 等。这类 RPC 框架侧重于服务的跨语言调用,能够支持大部分的语言进行语言无关的调用,非常适合多语言调用场景。但这类框架没有服务发现相关机制,实际使用时需要代理层进行请求转发和负载均衡策略控制。

其中,gRPC 是 Google 开发的高性能、通用的开源 RPC 框架,其由 Google 主要面向移动应用开发并基于 HTTP/2 协议标准而设计,基于 ProtoBuf(Protocol Buffers)序列化协议开发,且支持众多开发语言。本身它不是分布式的,所以要实现框架的功能需要进一步的开发。

Hprose(High Performance Remote Object Service Engine)是一个 MIT 开源许可的新型轻量级跨语言跨平台的面向对象的高性能远程动态通讯中间件。

服务治理型的 RPC 框架的特点是功能丰富,提供高性能的远程调用、服务发现及服务治理能力,适用于大型服务的服务解耦及服务治理,对于特定语言(Java)的项目可以实现透明化接入。缺点是语言耦合度较高,跨语言支持难度较大。国内常见的冶理型 RPC 框架如下:

* Dubbo:Dubbo 是阿里巴巴公司开源的一个 Java 高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring 框架无缝集成。当年在淘宝内部,Dubbo 由于跟淘宝另一个类似的框架 HSF 有竞争关系,导致 Dubbo 团队解散,最近又活过来了,有专职同学投入。
* DubboX:DubboX 是由当当在基于 Dubbo 框架扩展的一个 RPC 框架,支持 REST 风格的远程调用、Kryo/FST 序列化,增加了一些新的feature。
Motan:Motan 是新浪微博开源的一个 Java 框架。它诞生的比较晚,起于 2013 年,2016 年 5 月开源。Motan 在微博平台中已经广泛应用,每天为数百个服务完成近千亿次的调用。
* rpcx:rpcx 是一个类似阿里巴巴 Dubbo 和微博 Motan 的分布式的 RPC 服务框架,基于 Golang net/rpc 实现。但是 rpcx 基本只有一个人在维护,没有完善的社区,使用前要慎重,之前做 Golang 的 RPC 选型时也有考虑这个,最终还是放弃了,选择了 gRPC,如果想自己自研一个 RPC 框架,可以参考学习一下。

##名字发现/服务发现
名字发现和服务发现分为两种模式,一个是客户端发现模式,一种是服务端发现模式。

框架中常用的服务发现是客户端发现模式。

所谓服务端发现模式是指客户端通过一个负载均衡器向服务发送请求,负载均衡器查询服务注册表并把请求路由到一台可用的服务实例上。现在常用的负载均衡器都是此类模式,常用于微服务中。

所有的名字发现和服务发现都要依赖于一个可用性非常高的服务注册表,业界常用的服务注册表有如下三个:

* etcd,一个高可用、分布式、一致性、key-value 方式的存储,被用在分享配置和服务发现中。两个著名的项目使用了它:Kubernetes 和 Cloud Foundry。
* Consul,一个发现和配置服务的工具,为客户端注册和发现服务提供了API,Consul还可以通过执行健康检查决定服务的可用性。
* Apache ZooKeeper,是一个广泛使用、高性能的针对分布式应用的协调服务。Apache ZooKeeper 本来是 Hadoop 的子工程,现在已经是顶级工程了。

除此之外也可以自己实现服务实现,或者用 Redis 也行,只是需要自己实现高可用性。
##关系数据库
关系数据库分为两种,一种是传统关系数据,如 Oracle,MySQL,Maria,DB2,PostgreSQL 等等,另一种是 NewSQL,即至少要满足以下五点的新型关系数据库:

  1. 完整地支持 SQL,支持 JOIN / GROUP BY /子查询等复杂 SQL 查询。
  2. 支持传统数据标配的 ACID 事务,支持强隔离级别。
  3. 具有弹性伸缩的能力,扩容缩容对于业务层完全透明。
  4. 真正的高可用,异地多活、故障恢复的过程不需要人为的接入,系统能够自动地容灾和进行强一致的数据恢复。
  5. 具备一定的大数据分析能力。

传统关系数据库用得最多的是 MySQL,成熟,稳定,一些基本的需求都能满足,在一定数据量级之前基本单机传统数据库都可以搞定,而且现在较多的开源系统都是基于 MySQL,开箱即用,再加上主从同步和前端缓存,百万 pv 的应用都可以搞定了。不过 CentOS 7 已经放弃了 MySQL,而改使用 MariaDB。MariaDB 数据库管理系统是 MySQ L的一个分支,主要由开源社区在维护,采用 GPL 授权许可。开发这个分支的原因之一是:甲骨文公司收购了 MySQL 后,有将 MySQL 闭源的潜在风险,因此社区采用分支的方式来避开这个风险。

在 Google 发布了 F1: A Distributed SQL Database That Scales 和 Spanner: Google’s Globally-Distributed Databasa 之后,业界开始流行起 NewSQL。于是有了 CockroachDB,于是有了奇叔公司的 TiDB。国内已经有比较多的公司使用 TiDB,之前在创业公司时在大数据分析时已经开始应用 TiDB,当时应用的主要原因是 MySQL 要使用分库分表,逻辑开发比较复杂,扩展性不够。
##NoSQL
NoSQL 顾名思义就是 Not-Only SQL,也有人说是 No – SQL,个人偏向于 Not-Only SQL,它并不是用来替代关系库,而是作为关系型数据库的补充而存在。

常见 NoSQL 有4个类型:

* 键值,适用于内容缓存,适合混合工作负载并发高扩展要求大的数据集,其优点是简单,查询速度快,缺点是缺少结构化数据,常见的有 Redis,Memcache,BerkeleyDB 和 Voldemort 等等;
* 列式,以列簇式存储,将同一列数据存在一起,常见于分布式的文件系统,其中以 Hbase,Cassandra 为代表。Cassandra 多用于写多读少的场景,国内用得比较多的有 360,大概 1500 台机器的集群,国外大规模使用的公司比较多,如 eBay,Instagram,Apple 和沃尔玛等等;
* 文档,数据存储方案非常适用承载大量不相关且结构差别很大的复杂信息。性能介于 kv 和关系数据库之间,它的灵感来于 lotus notes,常见的有 MongoDB,CouchDB 等等;
* 图形,图形数据库擅长处理任何涉及关系的状况。社交网络,推荐系统等。专注于构建关系图谱,需要对整个图做计算才能得出结果,不容易做分布式的集群方案,常见的有 Neo4J,InfoGrid 等。

除了以上4种类型,还有一些特种的数据库,如对象数据库,XML 数据库,这些都有针对性对某些存储类型做了优化的数据库。

在实际应用场景中,何时使用关系数据库,何时使用 NoSQL,使用哪种类型的数据库,这是我们在做架构选型时一个非常重要的考量,甚至会影响整个架构的方案。
##消息中间件
消息中间件在后台系统中是必不可少的一个组件,一般我们会在以下场景中使用消息中间件:

* 异步处理:异步处理是使用消息中间件的一个主要原因,在工作中最常见的异步场景有用户注册成功后需要发送注册成功邮件、缓存过期时先返回老的数据,然后异步更新缓存、异步写日志等等;通过异步处理,可以减少主流程的等待响应时间,让非主流程或者非重要业务通过消息中间件做集中的异步处理。
* 系统解耦:比如在电商系统中,当用户成功支付完成订单后,需要将支付结果给通知ERP系统、发票系统、WMS、推荐系统、搜索系统、风控系统等进行业务处理;这些业务处理不需要实时处理、不需要强一致,只需要最终一致性即可,因此可以通过消息中间件进行系统解耦。通过这种系统解耦还可以应对未来不明确的系统需求。
* 削峰填谷:当系统遇到大流量时,监控图上会看到一个一个的山峰样的流量图,通过使用消息中间件将大流量的请求放入队列,通过消费者程序将队列中的处理请求慢慢消化,达到消峰填谷的效果。最典型的场景是秒杀系统,在电商的秒杀系统中下单服务往往会是系统的瓶颈,因为下单需要对库存等做数据库操作,需要保证强一致性,此时使用消息中间件进行下单排队和流控,让下单服务慢慢把队列中的单处理完,保护下单服务,以达到削峰填谷的作用。

业界消息中间件是一个非常通用的东西,大家在做选型时有使用开源的,也有自己造轮子的,甚至有直接用 MySQL 或 Redis 做队列的,关键看是否满足你的需求,如果是使用开源的项目,以下的表格在选型时可以参考:
svr_stack3.png

图 3

以上图的纬度为:名字、成熟度、所属社区/公司、文档、授权方式、开发语言、支持的协议、客户端支持的语言、性能、持久化、事务、集群、负载均衡、管理界面、部署方式、评价。
##代码管理
代码是互联网创业公司的命脉之一,代码管理很重要,常见的考量点包括两块:

* 安全和权限管理,将代码放到内网并且对于关系公司命脉的核心代码做严格的代码控制和机器的物理隔离;
* 代码管理工具,Git 作为代码管理的不二之选,你值得拥有。GitLab 是当今最火的开源 Git 托管服务端,没有之一,虽然有企业版,但是其社区版基本能满足我们大部分需求,结合 Gerrit 做 Code review,基本就完美了。当然 GitLab 也有代码对比,但没 Gerrit 直观。Gerrit 比 GitLab 提供了更好的代码检查界面与主线管理体验,更适合在对代码质量有高要求的文化下使用。

##持续集成
持续集成简,称 CI(continuous integration),是一种软件开发实践,即团队开发成员经常集成他们的工作,每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。持续集成为研发流程提供了代码分支管理/比对、编译、检查、发布物输出等基础工作,为测试的覆盖率版本编译、生成等提供统一支持。

业界免费的持续集成工具中系统我们有如下一些选择:

* Jenkins:Java 写的有强大的插件机制,MIT 协议开源 (免费,定制化程度高,它可以在多台机器上进行分布式地构建和负载测试)。Jenkins 可以算是无所不能,基本没有 Jenkins 做不了的,无论从小型团队到大型团队 Jenkins 都可以搞定。不过如果要大规模使用,还是需要有人力来学习和维护。
* TeamCity:TeamCity 与 Jenkins 相比使用更加友好,也是一个高度可定制化的平台。但是用的人多了,TeamCity就要收费了。
* Strider:Strider 是一个开源的持续集成和部署平台,使用 Node.js 实现,存储使用的是 MongoDB,BSD 许可证,概念上类似 Travis 和Jenkins。
* GitLab CI:从GitLab 8.0开始,GitLab CI 就已经集成在 GitLab,我们只要在项目中添加一个 .gitlab-ci.yml 文件,然后添加一个 Runner,即可进行持续集成。并且 GitLab 与 Docker 有着非常好的相互协作的能力。免费版与付费版本不同可以参见这里:https://about.gitlab.com/products/feature-comparison/。
* Travis:Travis 和 GitHub 强关联;闭源代码使用 SaaS 还需考虑安全问题;不可定制;开源项目免费,其它收费。
* Go:Go 是 ThoughtWorks 公司最新的 Cruise Control 的化身。除了 ThoughtWorks 提供的商业支持,Go 是免费的。它适用于 Windows,Mac 和各种 Linux 发行版。

##日志系统
日志系统一般包括打日志,采集,中转,收集,存储,分析,呈现,搜索还有分发等。一些特殊的如染色,全链条跟踪或者监控都可能需要依赖于日志系统实现。日志系统的建设不仅仅是工具的建设,还有规范和组件的建设,最好一些基本的日志在框架和组件层面加就行了,比如全链接跟踪之类的。

对于常规日志系统ELK能满足大部分的需求,ELK 包括如下组件:

* ElasticSearch 是个开源分布式搜索引擎,它的特点有:分布式,零配置,自动发现,索引自动分片,索引副本机制,RESTful 风格接口,多数据源,自动搜索负载等。
* Logstash 是一个完全开源的工具,它可以对你的日志进行收集、分析,并将其存储供以后使用。
* Kibana 是一个开源和免费的工具,它可以为 Logstash 和 ElasticSearch 提供的日志分析友好的 Web 界面,可以帮助汇总、分析和搜索重要数据日志。
* Filebeat 已经完全替代了 Logstash-Forwarder 成为新一代的日志采集器,同时鉴于它轻量、安全等特点,越来越多人开始使用它。

因为免费的 ELK 没有任何安全机制,所以这里使用了 Nginx 作反向代理,避免用户直接访问 Kibana 服务器。加上配置 Nginx 实现简单的用户认证,一定程度上提高安全性。另外,Nginx 本身具有负载均衡的作用,能够提高系统访问性能。ELK 架构如图4所示:
svr_stack4_elk.png

图 4,ELK 流程图

对于有实时计算的需求,可以使用 Flume + Kafka + Storm + MySQL 方案,一 般架构如图 5 所示:
svr_stack5.png

图 5,实时分析系统架构图

其中:

* Flume 是一个分布式、可靠、和高可用的海量日志采集、聚合和传输的日志收集系统,支持在日志系统中定制各类数据发送方,用于收集数据;同时,Flume 提供对数据进行简单处理,并写到各种数据接受方(可定制)的能力。
* Kafka 是由 Apache 软件基金会开发的一个开源流处理平台,由 Scala 和 Java 编写。其本质上是一个“按照分布式事务日志架构的大规模发布/订阅消息队列”,它以可水平扩展和高吞吐率而被广泛使用。

Kafka 追求的是高吞吐量、高负载,Flume 追求的是数据的多样性,二者结合起来简直完美。
##监控系统
监控系统只包含与后台相关的,这里主要是两块,一个是操作系统层的监控,比如机器负载,IO,网络流量,CPU,内存等操作系统指标的监控。另一个是服务质量和业务质量的监控,比如服务的可用性,成功率,失败率,容量,QPS 等等。常见业务的监控系统先有操作系统层面的监控(这部分较成熟),然后扩展出其它监控,如 Zabbix,小米的 Open-Falcon,也有一出来就是两者都支持的,如 Prometheus。如果对业务监控要求比较高一些,在创业选型中建议可以优先考虑 Prometheus。这里有一个有趣的分布,如图6所示。
svr_stack6.png

图 6,监控系统分布

亚洲区域使用 Zabbix 较多,而美洲和欧洲,以及澳大利亚使用 Prometheus 居多,换句话说,英文国家地区(发达国家?)使用 Prometheus 较多。

Prometheus 是由 SoundCloud 开发的开源监控报警系统和时序列数据库(TSDB)。Prometheus 使用 Go 语言开发,是 Google BorgMon 监控系统的开源版本。相对于其它监控系统使用的 push 数据的方式,Prometheus 使用的是 pull 的方式,其架构如图 7 所示:
svr_stack7.png

图 7,Prometheus 架构图

如上图所示,Prometheus 包含的主要组件如下:

* Prometheus Server 主要负责数据采集和存储,提供 PromQL 查询语言的支持。Server 通过配置文件、文本文件、ZooKeeper、Consul、DNS SRV Lookup 等方式指定抓取目标。根据这些目标会,Server 定时去抓取 metrics 数据,每个抓取目标需要暴露一个 http 服务的接口给它定时抓取。
* 客户端 SDK:官方提供的客户端类库有 Go、Java、Scala、Python、Ruby,其他还有很多第三方开发的类库,支持 Nodejs、PHP、Erlang 等。
* Push Gateway 支持临时性 Job 主动推送指标的中间网关。
* Exporter Exporter 是 Prometheus 的一类数据采集组件的总称。它负责从目标处搜集数据,并将其转化为 Prometheus 支持的格式。与传统的数据采集组件不同的是,它并不向中央服务器发送数据,而是等待中央服务器主动前来抓取。Prometheus 提供多种类型的 Exporter 用于采集各种不同服务的运行状态。目前支持的有数据库、硬件、消息中间件、存储系统、HTTP 服务器、JMX 等。
* Alertmanager:是一个单独的服务,可以支持 Prometheus 的查询语句,提供十分灵活的报警方式。
* Prometheus HTTP API 的查询方式,自定义所需要的输出。
* Grafana 是一套开源的分析监视平台,支持 Graphite,InfluxDB,OpenTSDB,Prometheus,Elasticsearch,CloudWatch 等数据源,其 UI 非常漂亮且高度定制化。

创业公司选择 Prometheus + Grafana 的方案,再加上统一的服务框架(如 gRPC),可以满足大部分中小团队的监控需求。
##配置系统
随着程序功能的日益复杂,程序的配置日益增多:各种功能的开关、降级开关,灰度开关,参数的配置、服务器的地址、数据库配置等等,除此之外,对后台程序配置的要求也越来越高:配置修改后实时生效,灰度发布,分环境、分用户,分集群管理配置,完善的权限、审核机制等等,在这样的大环境下,传统的通过配置文件、数据库等方式已经越来越无法满足开发人员对配置管理的需求,业界有如下两种方案:

* 基于 zk 和 etcd,支持界面和 api ,用数据库来保存版本历史,预案,走审核流程,最后下发到 zk 或 etcd 这种有推送能力的存储里(服务注册本身也是用 zk 或 etcd,选型就一块了)。客户端都直接和 zk 或 etcd 打交道。至于灰度发布,各家不同,有一种实现是同时发布一个需要灰度的 IP 列表,客户端监听到配置节点变化时,对比一下自己是否属于该列表。PHP 这种无状态的语言和其他 zk/etcd 不支持的语言,只好自己在客户端的机器上起一个 Agent 来监听变化,再写到配置文件或共享内存,如 360 的 Qconf。
* 基于运维自动化的配置文件的推送,审核流程,配置数据管理和方案一类似,下发时生成配置文件,基于运维自动化工具如 Puppet,Ansible 推送到每个客户端,而应用则定时重新读取这个外部的配置文件,灰度发布在下发配置时指定IP列表。

创业公司前期不需要这种复杂,直接上 zk,弄一个界面管理 zk 的内容,记录一下所有人的操作日志,程序直连 zk,或者或者用 Qconf 等基于 zk 优化后的方案。
##发布系统/部署系统
从软件生产的层面看,代码到最终服务的典型流程如图 8 所示:
svr_stack8.png

图 8,流程图

从上图中可以看出,从开发人员写下代码到服务最终用户是一个漫长过程,整体可以分成三个阶段:

* 从代码(Code)到成品库(Artifact)这个阶段主要对开发人员的代码做持续构建并把构建产生的制品集中管理,是为部署系统准备输入内容的阶段。
* 从制品到可运行服务 这个阶段主要完成制品部署到指定环境,是部署系统的最基本工作内容。
* 从开发环境到最终生产环境 这个阶段主要完成一次变更在不同环境的迁移,是部署系统上线最终服务的核心能力。

发布系统集成了制品管理,发布流程,权限控制,线上环境版本变更,灰度发布,线上服务回滚等几方面的内容,是开发人员工作结晶最终呈现的重要通道。开源的项目中没有完全满足的项目,如果只是 Web 类项目,Walle、Piplin 都是可用的,但是功能不太满足,创业初期可以集成 Jenkins + Gitlab + Walle(可以考虑两天时间完善一下),以上方案基本包括制品管理,发布流程,权限控制,线上环境版本变更,灰度发布(需要自己实现),线上服务回滚等功能。
##跳板机
跳板机面对的是需求是要有一种能满足角色管理与授权审批、信息资源访问控制、操作记录和审计、系统变更和维护控制要求,并生成一些统计报表配合管理规范来不断提升IT内控的合规性,能对运维人员操作行为的进行控制和审计,对误操作、违规操作导致的操作事故,快速定位原因和责任人。其功能模块一般包括:帐户管理、认证管理、授权管理、审计管理等等。

开源项目中,Jumpserver 能够实现跳板机常见需求,如授权、用户管理、服务器基本信息记录等,同时又可批量执行脚本等功能;其中录像回放、命令搜索、实时监控等特点,又能帮助运维人员回溯操作历史,方便查找操作痕迹,便于管理其他人员对服务器的操作控制。
##机器管理
机器管理的工具选择的考量可以包含以下三个方面:

  1. 是否简单,是否需要每台机器部署 Agent(客户端)
  2. 语言的选择(Puppet/Chef vs Ansible/SaltStack )开源技术,不看官网不足以熟练,不懂源码不足以精通;Puppet、Chef 基于 Ruby 开发,Ansible、SaltStack 基于 Python 开发的
  3. 速度的选择(Ansible vs SaltStack)Ansible 基于 SSH 协议传输数据,SaltStack 使用消息队列 zeroMQ 传输数据;大规模并发的能力对于几十台-200 台规模的兄弟来讲,Ansible的性能也可接受,如果一次操作上千台,用 salt 好一些。

如图9所示:
svr_statck9.png

图 9,机器管理软件对比

一般创业公司选择 Ansible 能解决大部问题,其简单,不需要安装额外的客户端,可以从命令行来运行,不需要使用配置文件。至于比较复杂的任务,Ansible 配置通过名为 Playbook 的配置文件中的 YAML 语法来加以处理。Playbook 还可以使用模板来扩展其功能。
#创业公司的选择
##选择合适的语言

* 选择团队熟悉的/能掌控的,创业公司人少事多,无太多冗余让研发团队熟悉新的语言,能快速上手,能快速出活,出了问题能快速解决的问题的语言才是好的选择。
* 选择更现代一些的,这里的现代是指语言本身已经完成一些之前需要特殊处理的特性,比如内存管理,线程等等。
* 选择开源轮子多的或者社区活跃度高的,这个原则是为了保证在开发过程中减少投入,有稳定可靠的轮子可以使用,遇到问题可以在网上快速搜索到答案。
* 选择好招人的 一门合适的语言会让创业团队减少招聘的成本,快速招到合适的人。
* 选择能让人有兴趣的 与上面一点相关,让人感兴趣,在后面留人时有用。

##选择合适的组件和云服务商

* 选择靠谱的云服务商;
* 选择云服务商的组件;
* 选择成熟的开源组件,而不是最新出的组件;
* 选择采用在一线互联网公司落地并且开源的,且在社区内形成良好口碑的产品;
* 开源社区活跃度;

选择靠谱的云服务商,其实这是一个伪命题,因为哪个服务商都不靠谱,他们所承诺的那些可用性问题基本上都会在你的身上发生,这里我们还是需要自己做一些工作,比如多服务商备份,如用 CDN,你一定不要只选一家,至少选两家,一个是灾备,保持后台切换的能力,另一个是多点覆盖,不同的服务商在 CDN 节点上的资源是不一样的。

选择了云服务商以后,就会有很多的产品你可以选择了,比较存储,队列这些都会有现成的产品,这个时候就纠结了,是用呢?还是自己在云主机上搭呢?在这里我的建议是前期先用云服务商的,大了后再自己搞,这样会少掉很多运维的事情,但是这里要多了解一下云服务商的组件特性以及一些坑,比如他们内网会经常断开,他们升级也会闪断,所以在业务侧要做好容错和规避。

关于开源组件,尽可能选择成熟的,成熟的组件经历了时间的考验,基本不会出大的问题,并且有成套的配套工具,出了问题在网上也可以很快的找到答案,你所遇到的坑基本上都有人踩过了。
##制定流程和规范

* 制定开发的规范,代码及代码分支管理规范,关键性代码仅少数人有权限;
* 制定发布流程规范,从发布系统落地;
* 制定运维规范;
* 制定数据库操作规范,收拢数据库操作权限;
* 制定告警处理流程,做到告警有人看有人处理;
* 制定汇报机制,晨会/周报;

##自研和选型合适的辅助系统
所有的流程和规范都需要用系统来固化,否则就是空中楼阁,如何选择这些系统呢?参照上个章节咱们那些开源的,对比一下选择的语言,组件之类的,选择一个最合适的即可。

比如项目管理的,看下自己是什么类型的公司,开发的节奏是怎样的,瀑布,敏捷的 按项目划分,还是按客户划分等等,平时是按项目组织还是按任务组织等等

比如日志系统,之前是打的文本,那么上一个 ELK,规范化一些日志组件,基本上很长一段时间内不用考虑日志系统的问题,最多拆分一下或者扩容一下。等到组织大了,自己搞一个日志系统。

比如代码管理,项目管理系统这些都放内网,安全,在互联网公司来说,属于命脉了,命脉的东西还是放在别人拿不到或很难拿到的地方会比较靠谱一些。
##选择过程中需要思考的问题
技术栈的选择有点像做出了某种承诺,在一定的时间内这种承诺没法改变,于是我们需要在选择的时候有一些思考。

看前面内容,有一个词出现了三次,合适,选择是合适的,不是最好,也不是最新,是最合适,适合是针对当下,这种选择是最合适的吗?比如用 Go 这条线的东西,技术比较新,业界组件储备够吗?组织内的人员储备够吗?学习成本多少?写出来的东西能满足业务性能要求吗?能满足时间要求吗?

向未来看一眼,在一年到三年内,我们需要做出改变吗?技术栈要做根本性的改变吗?如果组织发展很快,在 200 人,500 人时,现有的技术栈是否需要大动?

创业过程中需要考虑成本,这里的成本不仅仅是花费多少钱,付出多少工资,有时更重要的是时间成本,很多业务在创业时大家拼的就是时间,就是一个时间窗,过了就没你什么事儿了。
#基于云的创业公司后台技术架构
结合上面内容的考量,在对一个个系统和组件的做选型之后,以云服务为基础,一个创业公司的后台技术架构如图10所示:
svr_stack10.png

图 10,后台技术架构

参考资料:

* http://database.51cto.com/art/201109/291781.htm
* https://zh.wikipedia.org/wiki/Kafka
* https://prometheus.io/docs/introduction/overview/
* http://deadline.top/2016/11/23/配置中心那点事/
* http://blog.fit2cloud.com/2016/01/26/deployment-system.html

原文链接:从零开始搭建创业公司后台技术栈(作者:潘锦)

k8s搭建zookeeper 失败,

回复

lincoln_alex 回复了问题 • 1 人关注 • 1 个回复 • 643 次浏览 • 2019-04-22 11:02 • 来自相关话题

分布式协调神器 ZooKeeper 之整体概述

大卫 发表了文章 • 0 个评论 • 696 次浏览 • 2019-03-04 20:50 • 来自相关话题

ZooKeeper 最早起源于雅虎研究院的一个研究小组。当时,雅虎内部很多大型系统基本都需要依赖一个类似的系统来进行分布式协调,但是这些系统往往都存在分布式单点问题。所以,雅虎的开发人员就试图开发一个通用的无单点问题的分布式协调框架,以便让开发人员将精力集中在 ...查看全部
ZooKeeper 最早起源于雅虎研究院的一个研究小组。当时,雅虎内部很多大型系统基本都需要依赖一个类似的系统来进行分布式协调,但是这些系统往往都存在分布式单点问题。所以,雅虎的开发人员就试图开发一个通用的无单点问题的分布式协调框架,以便让开发人员将精力集中在处理业务逻辑上。

立项初期,考虑到之前内部很多项目都是使用动物的名字来命名的(例如著名的 Pig 项目),雅虎的工程师希望给这个项目也取一个动物的名字。当时研究院的首席科学家 RaghuRamakrishnan 开玩笑说:“再这样下去,我们这儿就变成动物园了!”是不是很有趣,顺势大家就表示既然已经是动物园了,它就叫动物园管理员吧!各个以动物命名的分布式组件放在一起,雅虎的整个分布式系统看上去就像一个大型的动物园了,而 ZooKeeper 正好要用来进行分布式环境的协调一一于是,ZooKeeper 的名字也就由此诞生了!
#ZooKeeper概述

ZooKeeper 是一种用于分布式应用程序的分布式开源协调服务。它公开了一组简单的原语,分布式应用程序可以构建这些原语,以实现更高级别的服务,以实现同步,配置维护以及组和命名。它被设计为易于编程,并使用在熟悉的文件系统目录树结构之后设计的数据模型。它在Java中运行,并且具有Java和C的绑定。

众所周知,协调服务很难做到。他们特别容易出现诸如竞争条件和死锁等错误。ZooKeeper 背后的动机是减轻分布式应用程序从头开始实施协调服务的责任。
#集群模型

1.png

Leader 服务器是整个 ZooKeeper 集群工作机制中的核心,其主要工作有以下两个:

  1. 事务请求的唯一调度和处理者,保证集群事务处理的顺序性。
  2. 集群内部各服务器的调度者。

从角色名字上可以看出,Follewer 服务器是 ZooKeeper 集群状态的跟随者,其主要工作有以下三个:

  1. 处理客户端非事务请求,转发事务请求给 Leader 服务器。
  2. 参与事务请求 Proposal 的投票。
  3. 参与 Leader 选举投票。

Observer 充当了一个观察者的角色,在工作原理上基本和 Follower 一致,唯一的区别在于,它不参与任何形式的投票。
#数据结构

2.png

#树形结构

首先我们来看上述数据节点示意图,从而对 ZooKeeper 上的数据节点有一个大体上的认识,在 ZooKeeper 中,每一个节点都被称为一个 ZNode,所有 ZNode 按层次化机构进行组织,形成一棵树。ZNode 节点路径标识方式和 Unix 文件系统路径非常相似,都是由一系列使用斜杠(/)进行分割的路径表示,开发人员可以向这个节点中写入数据,也可以在节点下面创建子节点。
#节点操作流程

3.png


  1. 在 Client 向 Follower 发出一个写请求。
  2. Follower 把请求转发给 Leader。
  3. Leader 接收到以后开始发起投票并通知 Follower 进行投票。
  4. Follower 把投票结果发送给 Leader。
  5. Leader 将结果汇总后,如果需要写入,则开始写入,同时把写入操作通知给 Follower,然后 commit。
  6. Follower 把请求结果返回给 Client。

#设计目标


  1. 顺序一致性,来自任意特定客户端的更新都会按其发送顺序被提交。也就是说,如果一个客户端将 Znode z 的值更新为 a,在之后的操作中,它又将 z 的值更新为 b,则没有客户端能够在看到z的值是b之后再看到值 a(如果没有其他对z的更新)。
  2. 原子性,每个更新要么成功,要么失败。这意味着如果一个更新失败,则不会有客户端会看到这个更新的结果。
  3. 单一系统映像,一个客户端无论连接到哪一台服务器,它看到的都是同样的系统视图。这意味着,如果一个客户端在同一个会话中连接到一台新的服务器,它所看到的系统状态不会比 在之前服务器上所看到的更老。当一台服务器出现故障,导致它的一个客户端需要尝试连接集合体中其他的服务器时,所有滞后于故障服务器的服务器都不会接受该 连接请求,除非这些服务器赶上故障服务器。
  4. 持久性,一个更新一旦成功,其结果就会持久存在并且不会被撤销。这表明更新不会受到服务器故障的影响。

#整体架构

4.png


* ServerCnxnFactory,ZooKeeper服务端网络连接工厂。在早期版本中,ZooKeeper 都是自己实现 NIO 框架,从 3.4.0 版本开始,引入了 Netty。可以通过 zookeeper.serverCnxnFactory 来指定使用具体的实现。
* SessionTracker,ZooKeeper 服务端会话管理器。创建时,会初始化 expirationInterval、nextExpirationTime、sessionsWithTimeout(用于保存每个会话的超时时间),同时还会计算出一个初始化的sessionID。
* RequestProcessor,ZooKeeper的请求处理方式是典型的责任链模式,在服务端,会有多个请求处理器依次来处理一个客户的请求。在服务器启动的时候,会将这些请求处理器串联起来形成一个请求处理链。基本的请求处理链如下:
5.png

* LearnerCnxAcceptor,Learner服务器(等于 Follower 服务器)连接请求接收器。负责 Leader 服务器和 Follower 服务器保持连接,以确定集群机器存活情况,并处理连接请求。
* LearnerHandler,Leader 接收来自其他机器的连接创建请求后,会创建一个 LearnerHandler 实例。每个 LearnerHandler 实例都对应了一个 Leader 和 Learner 服务器之间的连接,其负责 Leader 和 Learner 服务器之间几乎所有的消息通信和数据同步。
* ZKDatabase,ZooKeeper 内存数据库,负责管理 ZooKeeper 的所有会话记录以及 DataTree 和事务日志的存储。
* FileTxnSnapLog,ZooKeeper 上层服务和底层数据存储之间的对接层,提供了一系列的操作数据文件的接口,包括事务文件和快照数据文件。ZooKeeper 根据 zoo.cfg 文件中解析出的快照数据目录 dataDir 和事务日志目录 dataLogDir 来创建 FileTxnSnapLog。
* LeaderElection,ZooKeeper 会根据 zoo.cfg 中的配置,创建相应的 Leader 选举算法实现。在 ZooKeeper 中,默认提供了三种 Leader 选举算法的实现,分别是 LeaderElection、AuthFastLeaderElection、FastLeaderElection,可以通过配置文件中 electionAlg 属性来指定,分别用 0 ~ 3 来表示。从 3.4.0 版本开始,ZooKeeper 废弃了前两种算法,只支持 FastLeaderEletion 选举算法。

原文链接:http://rdc.hundsun.com/portal/article/952.html

详解分布式协调服务 ZooKeeper

JetLee 发表了文章 • 0 个评论 • 1424 次浏览 • 2018-11-24 17:30 • 来自相关话题

解析分布式锁之Zookeeper实现(一)

老李 发表了文章 • 0 个评论 • 1783 次浏览 • 2018-03-27 22:20 • 来自相关话题

实现分布式锁目前有三种流行方案,分别为基于数据库、Redis、Zookeeper的方案,本文主要阐述基于Zookeeper的分布式锁,其他两种会在后文中一起探讨。现在我们来看下使用Zookeeper如何实现分布式锁。 什么是Zooke ...查看全部
实现分布式锁目前有三种流行方案,分别为基于数据库、Redis、Zookeeper的方案,本文主要阐述基于Zookeeper的分布式锁,其他两种会在后文中一起探讨。现在我们来看下使用Zookeeper如何实现分布式锁。

什么是Zookeeper?

Zookeeper(业界简称zk)是一种提供配置管理、分布式协同以及命名的中心化服务,这些提供的功能都是分布式系统中非常底层且必不可少的基本功能,但是如果自己实现这些功能而且要达到高吞吐、低延迟同时还要保持一致性和可用性,实际上非常困难。因此zookeeper提供了这些功能,开发者在zookeeper之上构建自己的各种分布式系统。

虽然zookeeper的实现比较复杂,但是它提供的模型抽象却是非常简单的。Zookeeper提供一个多层级的节点命名空间(节点称为znode),每个节点都用一个以斜杠(/)分隔的路径表示,而且每个节点都有父节点(根节点除外),非常类似于文件系统。例如,/foo/doo这个表示一个znode,它的父节点为/foo,父父节点为/,而/为根节点没有父节点。与文件系统不同的是,这些节点都可以设置关联的数据,而文件系统中只有文件节点可以存放数据而目录节点不行。Zookeeper为了保证高吞吐和低延迟,在内存中维护了这个树状的目录结构,这种特性使得Zookeeper不能用于存放大量的数据,每个节点的存放数据上限为1M。

而为了保证高可用,zookeeper需要以集群形态来部署,这样只要集群中大部分机器是可用的(能够容忍一定的机器故障),那么zookeeper本身仍然是可用的。客户端在使用zookeeper时,需要知道集群机器列表,通过与集群中的某一台机器建立TCP连接来使用服务,客户端使用这个TCP链接来发送请求、获取结果、获取监听事件以及发送心跳包。如果这个连接异常断开了,客户端可以连接到另外的机器上。

架构简图如下所示:


客户端的读请求可以被集群中的任意一台机器处理,如果读请求在节点上注册了监听器,这个监听器也是由所连接的zookeeper机器来处理。对于写请求,这些请求会同时发给其他zookeeper机器并且达成一致后,请求才会返回成功。因此,随着zookeeper的集群机器增多,读请求的吞吐会提高但是写请求的吞吐会下降。

有序性是zookeeper中非常重要的一个特性,所有的更新都是全局有序的,每个更新都有一个唯一的时间戳,这个时间戳称为zxid(Zookeeper Transaction Id)。而读请求只会相对于更新有序,也就是读请求的返回结果中会带有这个zookeeper最新的zxid。

如何使用zookeeper实现分布式锁?

在描述算法流程之前,先看下zookeeper中几个关于节点的有趣的性质:

有序节点:假如当前有一个父节点为/lock,我们可以在这个父节点下面创建子节点;zookeeper提供了一个可选的有序特性,例如我们可以创建子节点“/lock/node-”并且指明有序,那么zookeeper在生成子节点时会根据当前的子节点数量自动添加整数序号,也就是说如果是第一个创建的子节点,那么生成的子节点为/lock/node-0000000000,下一个节点则为/lock/node-0000000001,依次类推。

临时节点:客户端可以建立一个临时节点,在会话结束或者会话超时后,zookeeper会自动删除该节点。

事件监听:在读取数据时,我们可以同时对节点设置事件监听,当节点数据或结构变化时,zookeeper会通知客户端。当前zookeeper有如下四种事件:1)节点创建;2)节点删除;3)节点数据修改;4)子节点变更。

下面描述使用zookeeper实现分布式锁的算法流程,假设锁空间的根节点为/lock:

客户端连接zookeeper,并在/lock下创建临时的且有序的子节点,第一个客户端对应的子节点为/lock/lock-0000000000,第二个为/lock/lock-0000000001,以此类推。

客户端获取/lock下的子节点列表,判断自己创建的子节点是否为当前子节点列表中序号最小的子节点,如果是则认为获得锁,否则监听/lock的子节点变更消息,获得子节点变更通知后重复此步骤直至获得锁;

执行业务代码;

完成业务流程后,删除对应的子节点释放锁。

步骤1中创建的临时节点能够保证在故障的情况下锁也能被释放,考虑这么个场景:假如客户端a当前创建的子节点为序号最小的节点,获得锁之后客户端所在机器宕机了,客户端没有主动删除子节点;如果创建的是永久的节点,那么这个锁永远不会释放,导致死锁;由于创建的是临时节点,客户端宕机后,过了一定时间zookeeper没有收到客户端的心跳包判断会话失效,将临时节点删除从而释放锁。

另外细心的朋友可能会想到,在步骤2中获取子节点列表与设置监听这两步操作的原子性问题,考虑这么个场景:客户端a对应子节点为/lock/lock-0000000000,客户端b对应子节点为/lock/lock-0000000001,客户端b获取子节点列表时发现自己不是序号最小的,但是在设置监听器前客户端a完成业务流程删除了子节点/lock/lock-0000000000,客户端b设置的监听器岂不是丢失了这个事件从而导致永远等待了?这个问题不存在的。因为zookeeper提供的API中设置监听器的操作与读操作是原子执行的,也就是说在读子节点列表时同时设置监听器,保证不会丢失事件。

最后,对于这个算法有个极大的优化点:假如当前有1000个节点在等待锁,如果获得锁的客户端释放锁时,这1000个客户端都会被唤醒,这种情况称为“羊群效应”;在这种羊群效应中,zookeeper需要通知1000个客户端,这会阻塞其他的操作,最好的情况应该只唤醒新的最小节点对应的客户端。应该怎么做呢?在设置事件监听时,每个客户端应该对刚好在它之前的子节点设置事件监听,例如子节点列表为/lock/lock-0000000000、/lock/lock-0000000001、/lock/lock-0000000002,序号为1的客户端监听序号为0的子节点删除消息,序号为2的监听序号为1的子节点删除消息。

所以调整后的分布式锁算法流程如下:

客户端连接zookeeper,并在/lock下创建临时的且有序的子节点,第一个客户端对应的子节点为/lock/lock-0000000000,第二个为/lock/lock-0000000001,以此类推。

客户端获取/lock下的子节点列表,判断自己创建的子节点是否为当前子节点列表中序号最小的子节点,如果是则认为获得锁,否则监听刚好在自己之前一位的子节点删除消息,获得子节点变更通知后重复此步骤直至获得锁;

执行业务代码;

完成业务流程后,删除对应的子节点释放锁。

Curator的源码分析

虽然zookeeper原生客户端暴露的API已经非常简洁了,但是实现一个分布式锁还是比较麻烦的…我们可以直接使用curator这个开源项目提供的zookeeper分布式锁实现。

我们只需要引入下面这个包(基于maven):

org.apache.curator

curator-recipes

4.0.0

然后就可以用啦!代码如下:

publicstaticvoidmain(String[]args)throwsException{

//创建zookeeper的客户端

RetryPolicyretryPolicy=newExponentialBackoffRetry(1000,3);

CuratorFrameworkclient=CuratorFrameworkFactory.newClient("10.21.41.181:2181,10.21.42.47:2181,10.21.49.252:2181",retryPolicy);

client.start();

//创建分布式锁, 锁空间的根节点路径为/curator/lock

InterProcessMutexmutex=newInterProcessMutex(client,"/curator/lock");

mutex.acquire();

//获得了锁, 进行业务流程

System.out.println("Enter mutex");

//完成业务流程, 释放锁

mutex.release();

//关闭客户端

client.close();

}

可以看到关键的核心操作就只有mutex.acquire()和mutex.release(),简直太方便了!

下面来分析下获取锁的源码实现。acquire的方法如下:

/*

* 获取锁,当锁被占用时会阻塞等待,这个操作支持同线程的可重入(也就是重复获取锁),acquire的次数需要与release的次数相同。

* @throws Exception ZK errors, connection interruptions

*/

@Override

publicvoidacquire()throwsException

{

if(!internalLock(-1,null))

{

thrownewIOException("Lost connection while trying to acquire lock: "+basePath);

}

}

这里有个地方需要注意,当与zookeeper通信存在异常时,acquire会直接抛出异常,需要使用者自身做重试策略。代码中调用了internalLock(-1, null),参数表明在锁被占用时永久阻塞等待。internalLock的代码如下:

privatebooleaninternalLock(longtime,TimeUnitunit)throwsException

{

//这里处理同线程的可重入性,如果已经获得锁,那么只是在对应的数据结构中增加acquire的次数统计,直接返回成功

ThreadcurrentThread=Thread.currentThread();

LockDatalockData=threadData.get(currentThread);

if(lockData!=null)

{

// re-entering

lockData.lockCount.incrementAndGet();

returntrue;

}

//这里才真正去zookeeper中获取锁

StringlockPath=internals.attemptLock(time,unit,getLockNodeBytes());

if(lockPath!=null)

{

//获得锁之后,记录当前的线程获得锁的信息,在重入时只需在LockData中增加次数统计即可

LockDatanewLockData=newLockData(currentThread,lockPath);

threadData.put(currentThread,newLockData);

returntrue;

}

//在阻塞返回时仍然获取不到锁,这里上下文的处理隐含的意思为zookeeper通信异常

returnfalse;

}

代码中增加了具体注释,不做展开。看下zookeeper获取锁的具体实现:

StringattemptLock(longtime,TimeUnitunit,byte[]lockNodeBytes)throwsException

{

//参数初始化,此处省略

//...

//自旋获取锁

while(!isDone)

{

isDone=true;

try

{

//在锁空间下创建临时且有序的子节点

ourPath=driver.createsTheLock(client,path,localLockNodeBytes);

//判断是否获得锁(子节点序号最小),获得锁则直接返回,否则阻塞等待前一个子节点删除通知

hasTheLock=internalLockLoop(startMillis,millisToWait,ourPath);

}

catch(KeeperException.NoNodeExceptione)

{

//对于NoNodeException,代码中确保了只有发生session过期才会在这里抛出NoNodeException,因此这里根据重试策略进行重试

if(client.getZookeeperClient().getRetryPolicy().allowRetry(retryCount++,System.currentTimeMillis()-startMillis,RetryLoop.getDefaultRetrySleeper()))

{

isDone=false;

}

else

{

throwe;

}

}

}

//如果获得锁则返回该子节点的路径

if(hasTheLock)

{

returnourPath;

}

returnnull;

}

上面代码中主要有两步操作:

driver.createsTheLock:创建临时且有序的子节点,里面实现比较简单不做展开,主要关注几种节点的模式:1)PERSISTENT(永久);2)PERSISTENT_SEQUENTIAL(永久且有序);3)EPHEMERAL(临时);4)EPHEMERAL_SEQUENTIAL(临时且有序)。

internalLockLoop:阻塞等待直到获得锁。

看下internalLockLoop是怎么判断锁以及阻塞等待的,这里删除了一些无关代码,只保留主流程:

//自旋直至获得锁

while((client.getState()==CuratorFrameworkState.STARTED)&&!haveTheLock)

{

//获取所有的子节点列表,并且按序号从小到大排序

Listchildren=getSortedChildren();

//根据序号判断当前子节点是否为最小子节点

StringsequenceNodeName=ourPath.substring(basePath.length()+1);// +1 to include the slash

PredicateResultspredicateResults=driver.getsTheLock(client,children,sequenceNodeName,maxLeases);

if(predicateResults.getsTheLock())

{

//如果为最小子节点则认为获得锁

haveTheLock=true;

}

else

{

//否则获取前一个子节点

StringpreviousSequencePath=basePath+"/"+predicateResults.getPathToWatch();

//这里使用对象监视器做线程同步,当获取不到锁时监听前一个子节点删除消息并且进行wait(),当前一个子节点删除(也就是锁释放)时,回调会通过notifyAll唤醒此线程,此线程继续自旋判断是否获得锁

synchronized(this)

{

try

{

//这里使用getData()接口而不是checkExists()是因为,如果前一个子节点已经被删除了那么会抛出异常而且不会设置事件监听器,而checkExists虽然也可以获取到节点是否存在的信息但是同时设置了监听器,这个监听器其实永远不会触发,对于zookeeper来说属于资源泄露

client.getData().usingWatcher(watcher).forPath(previousSequencePath);

//如果设置了阻塞等待的时间

if(millisToWait!=null)

{

millisToWait-=(System.currentTimeMillis()-startMillis);

startMillis=System.currentTimeMillis();

if(millisToWait<=0)

{

doDelete=true;// 等待时间到达,删除对应的子节点

break;

}

//等待相应的时间

wait(millisToWait);

}

else

{

//永远等待

wait();

}

}

catch(KeeperException.NoNodeExceptione)

{

//上面使用getData来设置监听器时,如果前一个子节点已经被删除那么会抛出NoNodeException,只需要自旋一次即可,无需额外处理

}

}

}

}

具体逻辑见注释,不再赘述。代码中设置的事件监听器,在事件发生回调时只是简单的notifyAll唤醒当前线程以重新自旋判断,比较简单不再展开。




总结:

以上就是基于Zookeeper的分布式锁内容,在我的下一篇文章里,我会向大家阐述基于Redis的分布式锁,有兴趣的朋友可以点赞关注一下,实时获取最新的资料。

关于分布式任务调度平台,数人云的经验都在这里了

Dataman数人科技 发表了文章 • 0 个评论 • 2682 次浏览 • 2017-09-19 10:06 • 来自相关话题

分布式任务调度平台是目前很多公司研究的方向,今天小数就给大家分享一下数人云分布式任务调度平台(Octopus)的一些思考与实践。 今天主要分享下批量处理平台的技术心得,批量处理从片面的角度讲类似于Linux系统中的Cron Table ...查看全部
分布式任务调度平台是目前很多公司研究的方向,今天小数就给大家分享一下数人云分布式任务调度平台(Octopus)的一些思考与实践。

今天主要分享下批量处理平台的技术心得,批量处理从片面的角度讲类似于Linux系统中的Cron Table,从大方向去看属于批量业务的调度平台,此次依托数人云3年来对容器技术的积累和对批量处理开源项目的整合过程,在这里和大家探讨一下实践分布式任务调度的心路历程。

从理论上来说,做分布式系统并非企图加快单一任务处理速度,而是通过并行的方式合理利用资源,通过加大对任务的同时批量处理业务容量来加快业务的运营速度,举个典型的例子:当前视频直播中需要的视频文件解码,就是一种典型的批量处理业务。

本来,批量处理业务和容器技术的关系并不紧密,我们通过跨领域的交叉设计,希望通过容器技术的封装能力帮助批处理系统快速建立更多的高密度批量处理运行时环境,更高效地利用资源。

定时任务无处不在,在多任务处理时如何进行秒级调度?与容器如何碰撞?这是我比较关注的主题,个人的角度来看,批量处理系统的痛点有四个维度是用户比较关注的:

  • 弹性伸缩
  • 故障处理
  • 运维体系
  • 性能指标
以金融行业为例,在金融行业高速发展的今天,业务规模快速扩展,随着业务的发展,需处理的数据量越来越大,后台批量处理业务占60%以上,如数据接口导入、数据预处理、估值表生成、凭证生成、对账、日终批处理、报表生成等。从用户关注的4个维度切入问题主要表现在:弹性扩缩:批量处理作业只能运行在一个服务节点上,无法适应业务发展,这是目前业务系统的最大性能瓶颈点。故障处理:当作业发生故障时,未实现故障自动转移,严重时影响业务进展。性能指标:[list=1]
  • 作业调度与作业执行线程耦合在一起,随着作业规模的增长,严重影响系统性能,也给开发和运维带来了一定的难度。
  • 联机业务和批量处理业务耦合,当在进行比较消耗资源的批处理或批量处理服务发生严重故障时直接影响到了在线业务。
  • 运维体系:不便于定位作业运行时问题,不便于了解作业运行进展、负载情况、也缺少作业运行时的性能指标,不便于对作业进行调优等。有了这些分析,在设计竖线数人云分布式任务调度平台过程中,采用作业调度与作业执行分离的架构来简化业务系统批量处理的开发和运维工作;采用中间件和平台化的思路提升其应用的范围及价值;采用Java系统作业的调度,执行与管控;最后产生的效果是实现了以分布式调度为理念而设计的——多服务节点协同并行处理能力及运行环境的适用能力。在弹性伸缩方面,数人云采用了基于容器平台目的是快速构建多节点的任务执行节点,容器的好处是封装了一整套完整的任务执行环境,并且可以快速扩容服务节点数量,这个批处理设计模型如何自己实现会需要大量的测试和业务打磨,所以在技术选型上,选用了Mesos作为企业级环境的底座,毕竟Apple、MS、Netflix、Uber等大厂都在基于此技术上构建了自己的业务平台。尤其国内开源项目当当的Elastic-Job批量处理平台(https://github.com/dangdangdotcom/elastic-job)受到很多厂家的采用,为数人云提供了一手的学习实践经验。数人云在此之上又进行了一些优化,从设计理念中,更多考虑了业务实际场景中的一种状况:即每次任务处理完毕后,是重置当前进程环境还是退出,虽然容器的额快速启停确实可以解决这个小问题,但仍然不够快。毕竟一个任务的环境初始化是需要消耗时间的,即使容器启动也有那么几十毫秒的损耗。另外,我们对Mesos集群的使用,在于提供可以FailOver的高容错环境,并没有直接让Mesos来调度批量处理任务,实际上,作业节点注册到ZK后,任务的分发和结果收集都是在JVM里面解决,和Mesos集群本身没有关系,减少了对集群系统的直接依赖,后面,我们还会对Kuberentes集群做支持。在故障处理方面,重要的并不是让任务永远不出错,在创建任务时,能提供立即执行一次的操作,让执行结果能即刻体现出来,这样给任务指定的时间去跑才不会出错,在创建任务的细节上,比如把跑批时间参入如:0/5 ? 预测出固定的时间,让用户看到直接的时间会更好。对于事后的历史结果留存也需要做到详细完整,保证故障拍错,这块使用统计面板来监控即可。以及作业预警面板也是需要的。在性能指标方面,大量的工作主要是定义好性能指标并大量压测并调优系统,比如数人云的产品定义:调度频率:定时作业最快支持5S的时间间隔,对比:公有云如阿里ScheduleX,都是分钟级间隔。支持作业容量:最多支持管理100个Zookeeper集群,作业总量支持到500K+。消息作业并发量:支持单节点100K TPS的并发。通过这个目标,根据本地搭建的作业系统环境就可以压测了,压测数据通过Grafana展现出来,测试样例如下:在运维体系中,能不能通过一个管理平台就囊括所有运维需要管理的事情,将其都考虑进去,比如组织关系的体现,暂停时间的管理,配置数据的备份和查询,ZK元数据的到处备份工作,调用链的跟踪设计实现,各种监控面板的实现,这个难度不大,但需要考虑的细节非常多,我们也是在参考和落地实践中摸索这些问题如何解决。最后,分布式任务调度平台在企业架架构体系里面必不可少少的组件,国内企业在经过这几年云计算的高速发展,开始意识到数字化转型过程中必须要经历架构方面的变革,传统的批处理系统已经不能适应业务发现的需要,不妨参考业内开源领域的最佳实践构建自己的分布式调度平台。 QAQ:是否开源?A: 数人云任务调度基于开源基础构建扩展了企业级功能。目前暂不开源,后续是否开源视社区反馈决定。Q:无中心化设计(由任务执行者获取执行计划,并自己触发执行)和集中式调度设计(由一个调度中心统一通过远程调用的方式触发执行者)有什么考虑,更偏向于哪一个?A:数人云任务调度包括控制台都在内都基于无中心化设计,中心控制是ZooKeeper集群。此种设计从分布式角度考虑,避免单点故障。Q:能否详细介绍下并行执行、分片、日志,终端处理的设计实现思路?A:l 并行基于分片粒度执行,目前支持几种粒度:多机器节点执行、同个节点多个进程执行、同个进程多个线程执行。进程粒度的并行执行基于ZooKeeper进行分布式协调处理。多线程基于Java Executors做线程池调度处理。l 日志支持写入ZK,同时支持对接LogStash将日志放入ELK。由于任务执行控制都在平台侧,所以平台可以获取拦截日志流,并将日志进行查处记录。Java和消息作业是拦截LogBack,Shell作业基于Apache Commons Exec的LogOutputStream进行进程输出流的拦截。Q:任务积压如何考虑?如何标记任务执行中或执行完成,具体如何实现?A:l 任务支持超时告警和Kill的配置控制。任务积压时可以对接告警系统,Dashboard也支持显示。l 当任务开始执行和结束时,会由平台侧将状态写入ZK。Q:选择平台独立调度系统主要基于哪些考虑?支持K8S后会考虑自带的Scheduler吗?另“任务结果收集”是如何实现的?A:任务调度可以立即为容器Scheduler上的另外一层独立调度,而后通过Kubernetes/Marathon等API启动容器,任务结果收集可以考虑ELK方式或对接APM系统。Q:分布式任务系统中,如何根据机器系统负载及当前执行任务列表进行任务动态负载?A:目前做法是给任务设定一个逻辑的负载,在调度的时尽可能做到各个机器的整体负载均衡。Q:若机器挂掉,如何进行有状态任务的故障转移?A: 利用Zookeeper特性,当一台机器掉线后,它上面的作业会被调度到其它机器来完成。Q:任务间可能存在一些依赖,如何解决这些任务间的传递问题?A: 目前通过配置的方式设置任务依赖,当停止、启动作业时会给予提醒。任务间的消息传递可利用消息作业的方式进行。Q:当用户上传的是危险操作,平台如何进行自我保护?A:目前利用容器做隔离,当然也可以通过Chroot的方式将作业运行目录隔离。Q:若任务堵住了Hang 任务进程会表现正常,但实际业务没有处理,如何解决?A:需要平台级别超时控制,超过一定阈值时发送告警,再过一定阀值杀掉任务。Q:消息作业支持哪些平台,消息作业的性能表现如何?A:目前支持Kafka,后续也会支持RabbitMQ。至于性能,内部测试可以达到20W的TPS。机器的配置为Mem:48G,CPU:16C*2.2G。Q:是否支持在容器化的平台运行?A:支持,目前支持发布到Marathon、数人云SWAN、以及Kubernetes平台。Q:开源版本如EJ和Saturn,不支持认证或提供BASIC认证。企业难以使用,如何处理?A:数人云调度平台提供基于角色的标准认证,也支持对接LDAP的方式。Q:针对开源版本,数人云调度平台提供哪些企业级功能?A:提供认证授权、审计、对接Prometheus的Metrics、历史配置与版本恢复。 关于数人云分布式任务调度平台Octopus数人云Octopus基于当当Elastic Job和唯品会的Saturn以及数人云DM/OS和容器云平台,打造的高效易用定时任务调度系统。支持多种任务类型和模式,具有资源动态平衡以及框架、业务隔离的功能,而且无缝融合物理机、虚拟机以及容器,从而实现调度任务统一监控管理,全面高可用。# 〓 应用场景无缝代替 Linux Cron Job:完美代替 Linux Cron Job,做到全局统一配置、统一管理、统一监控。分布式任务调度:分布式并行任务处理,如分库分表的批处理等。本地任务调度:可以根据任务量,任意调整处理资源。如电商商品图片扫描处理等。消息任务调度:通过消息调度作业处理。如日志处理、消息驱动数据库同步等。# 〓 功能特性
    • 资源动态平衡:人工指定运行节点,系统自动平衡负载,灵活的运维配置与部署,高效资源利用,简便的管理。
    • 认证与授权:提供 LDAP 集成,以及多角色权限管理。
    • 框架与业务隔离:框架代码与业务代码隔离,集中化动态增加与删除任务,简化开发,避免冲突,业务无侵入,易于发布与维护,框架升级与业务脱离,框架版本统一升级。
    • 分组以及依赖管理:严谨的管理模式以及灵活的设置。
    • 秒级调度:提供任务秒级触发。
    • 简单易用的 SDK:快速开发业务。
    • 资源混搭:支持物理机、虚拟机以及容器。
    • 优美的监控台:提供多维度 Dashboard 以及监控视图。
    • 多种任务类型与任务模式:
    1)Shell 任务:提供任意脚本语言,无缝迁移 Linux Cron Job;
    2)JAVA 任务:提供灵活编程模式,满足不同的业务需求;
    3)消息任务:提供消息驱动,支持任务间串接;
    4)分布式运行与本地任务模式:提供分片、分区处理模式,以及 Daemon 模式,满足业务灵活并行处理。

    服务端高并发分布式架构演进之路

    翔宇 发表了文章 • 0 个评论 • 215 次浏览 • 2019-06-04 11:16 • 来自相关话题

    【编者的话】本文以淘宝作为例子,介绍从一百个并发到千万级并发情况下服务端的架构的演进过程,同时列举出每个演进阶段会遇到的相关技术,让大家对架构的演进有一个整体的认知,文章最后汇总了一些架构设计的原则。 #基本概念 在介绍架构之前,为 ...查看全部
    【编者的话】本文以淘宝作为例子,介绍从一百个并发到千万级并发情况下服务端的架构的演进过程,同时列举出每个演进阶段会遇到的相关技术,让大家对架构的演进有一个整体的认知,文章最后汇总了一些架构设计的原则。
    #基本概念

    在介绍架构之前,为了避免部分读者对架构设计中的一些概念不了解,下面对几个最基础的概念进行介绍:
    ##分布式

    系统中的多个模块在不同服务器上部署,即可称为分布式系统,如Tomcat和数据库分别部署在不同的服务器上,或两个相同功能的Tomcat分别部署在不同服务器上。
    ##高可用

    系统中部分节点失效时,其他节点能够接替它继续提供服务,则可认为系统具有高可用性。
    ##集群

    一个特定领域的软件部署在多台服务器上并作为一个整体提供一类服务,这个整体称为集群。如Zookeeper中的Master和Slave分别部署在多台服务器上,共同组成一个整体提供集中配置服务。在常见的集群中,客户端往往能够连接任意一个节点获得服务,并且当集群中一个节点掉线时,其他节点往往能够自动的接替它继续提供服务,这时候说明集群具有高可用性。
    ##负载均衡

    请求发送到系统时,通过某些方式把请求均匀分发到多个节点上,使系统中每个节点能够均匀的处理请求负载,则可认为系统是负载均衡的。
    ##正向代理和反向代理

    系统内部要访问外部网络时,统一通过一个代理服务器把请求转发出去,在外部网络看来就是代理服务器发起的访问,此时代理服务器实现的是正向代理;当外部请求进入系统时,代理服务器把该请求转发到系统中的某台服务器上,对外部请求来说,与之交互的只有代理服务器,此时代理服务器实现的是反向代理。简单来说,正向代理是代理服务器代替系统内部来访问外部网络的过程,反向代理是外部请求访问系统时通过代理服务器转发到内部服务器的过程。
    #架构演进

    ##单机架构

    1.png

    以淘宝作为例子。在网站最初时,应用数量与用户数都较少,可以把Tomcat和数据库部署在同一台服务器上。浏览器往www.taobao.com发起请求时,首先经过DNS服务器(域名系统)把域名转换为实际IP地址10.102.4.1,浏览器转而访问该IP对应的Tomcat。如果你想和更多Tomcat技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

    随着用户数的增长,Tomcat和数据库之间竞争资源,单机性能不足以支撑业务
    ##第一次演进:Tomcat与数据库分开部署

    2.png

    Tomcat和数据库分别独占服务器资源,显著提高两者各自性能。

    随着用户数的增长,并发读写数据库成为瓶颈
    ##第二次演进:引入本地缓存和分布式缓存

    3.png

    在Tomcat同服务器上或同JVM中增加本地缓存,并在外部增加分布式缓存,缓存热门商品信息或热门商品的html页面等。通过缓存能把绝大多数请求在读写数据库前拦截掉,大大降低数据库压力。其中涉及的技术包括:使用memcached作为本地缓存,使用Redis作为分布式缓存,还会涉及缓存一致性、缓存穿透/击穿、缓存雪崩、热点数据集中失效等问题。

    缓存抗住了大部分的访问请求,随着用户数的增长,并发压力主要落在单机的Tomcat上,响应逐渐变慢
    ##第三次演进:引入反向代理实现负载均衡

    4.png

    在多台服务器上分别部署Tomcat,使用反向代理软件(Nginx)把请求均匀分发到每个Tomcat中。此处假设Tomcat最多支持100个并发,Nginx最多支持50000个并发,那么理论上Nginx把请求分发到500个Tomcat上,就能抗住50000个并发。其中涉及的技术包括:Nginx、HAProxy,两者都是工作在网络第七层的反向代理软件,主要支持http协议,还会涉及session共享、文件上传下载的问题。

    反向代理使应用服务器可支持的并发量大大增加,但并发量的增长也意味着更多请求穿透到数据库,单机的数据库最终成为瓶颈
    ##第四次演进:数据库读写分离

    5.png

    把数据库划分为读库和写库,读库可以有多个,通过同步机制把写库的数据同步到读库,对于需要查询最新写入数据场景,可通过在缓存中多写一份,通过缓存获得最新数据。其中涉及的技术包括:Mycat,它是数据库中间件,可通过它来组织数据库的分离读写和分库分表,客户端通过它来访问下层数据库,还会涉及数据同步,数据一致性的问题。

    业务逐渐变多,不同业务之间的访问量差距较大,不同业务直接竞争数据库,相互影响性能
    ##第五次演进:数据库按业务分库

    6.png

    把不同业务的数据保存到不同的数据库中,使业务之间的资源竞争降低,对于访问量大的业务,可以部署更多的服务器来支撑。这样同时导致跨业务的表无法直接做关联分析,需要通过其他途径来解决,但这不是本文讨论的重点,有兴趣的可以自行搜索解决方案。

    随着用户数的增长,单机的写库会逐渐会达到性能瓶颈
    ##第六次演进:把大表拆分为小表

    7.png

    比如针对评论数据,可按照商品ID进行hash,路由到对应的表中存储;针对支付记录,可按照小时创建表,每个小时表继续拆分为小表,使用用户ID或记录编号来路由数据。只要实时操作的表数据量足够小,请求能够足够均匀的分发到多台服务器上的小表,那数据库就能通过水平扩展的方式来提高性能。其中前面提到的Mycat也支持在大表拆分为小表情况下的访问控制。

    这种做法显著的增加了数据库运维的难度,对DBA的要求较高。数据库设计到这种结构时,已经可以称为分布式数据库,但是这只是一个逻辑的数据库整体,数据库里不同的组成部分是由不同的组件单独来实现的,如分库分表的管理和请求分发,由Mycat实现,SQL的解析由单机的数据库实现,读写分离可能由网关和消息队列来实现,查询结果的汇总可能由数据库接口层来实现等等,这种架构其实是MPP(大规模并行处理)架构的一类实现。

    目前开源和商用都已经有不少MPP数据库,开源中比较流行的有Greenplum、TiDB、Postgresql XC、HAWQ等,商用的如南大通用的GBase、睿帆科技的雪球DB、华为的LibrA等等,不同的MPP数据库的侧重点也不一样,如TiDB更侧重于分布式OLTP场景,Greenplum更侧重于分布式OLAP场景,这些MPP数据库基本都提供了类似Postgresql、Oracle、MySQL那样的SQL标准支持能力,能把一个查询解析为分布式的执行计划分发到每台机器上并行执行,最终由数据库本身汇总数据进行返回,也提供了诸如权限管理、分库分表、事务、数据副本等能力,并且大多能够支持100个节点以上的集群,大大降低了数据库运维的成本,并且使数据库也能够实现水平扩展。

    数据库和Tomcat都能够水平扩展,可支撑的并发大幅提高,随着用户数的增长,最终单机的Nginx会成为瓶颈
    ##第七次演进:使用LVS或F5来使多个Nginx负载均衡

    8.png

    由于瓶颈在Nginx,因此无法通过两层的Nginx来实现多个Nginx的负载均衡。图中的LVS和F5是工作在网络第四层的负载均衡解决方案,其中LVS是软件,运行在操作系统内核态,可对TCP请求或更高层级的网络协议进行转发,因此支持的协议更丰富,并且性能也远高于Nginx,可假设单机的LVS可支持几十万个并发的请求转发;F5是一种负载均衡硬件,与LVS提供的能力类似,性能比LVS更高,但价格昂贵。由于LVS是单机版的软件,若LVS所在服务器宕机则会导致整个后端系统都无法访问,因此需要有备用节点。可使用keepalived软件模拟出虚拟IP,然后把虚拟IP绑定到多台LVS服务器上,浏览器访问虚拟IP时,会被路由器重定向到真实的LVS服务器,当主LVS服务器宕机时,keepalived软件会自动更新路由器中的路由表,把虚拟IP重定向到另外一台正常的LVS服务器,从而达到LVS服务器高可用的效果。

    此处需要注意的是,上图中从Nginx层到Tomcat层这样画并不代表全部Nginx都转发请求到全部的Tomcat,在实际使用时,可能会是几个Nginx下面接一部分的Tomcat,这些Nginx之间通过keepalived实现高可用,其他的Nginx接另外的Tomcat,这样可接入的Tomcat数量就能成倍的增加。

    由于LVS也是单机的,随着并发数增长到几十万时,LVS服务器最终会达到瓶颈,此时用户数达到千万甚至上亿级别,用户分布在不同的地区,与服务器机房距离不同,导致了访问的延迟会明显不同
    ##第八次演进:通过DNS轮询实现机房间的负载均衡

    9.png

    在DNS服务器中可配置一个域名对应多个IP地址,每个IP地址对应到不同的机房里的虚拟IP。当用户访问www.taobao.com时,DNS服务器会使用轮询策略或其他策略,来选择某个IP供用户访问。此方式能实现机房间的负载均衡,至此,系统可做到机房级别的水平扩展,千万级到亿级的并发量都可通过增加机房来解决,系统入口处的请求并发量不再是问题。

    随着数据的丰富程度和业务的发展,检索、分析等需求越来越丰富,单单依靠数据库无法解决如此丰富的需求
    ##第九次演进:引入NoSQL数据库和搜索引擎等技术

    10.png

    当数据库中的数据多到一定规模时,数据库就不适用于复杂的查询了,往往只能满足普通查询的场景。对于统计报表场景,在数据量大时不一定能跑出结果,而且在跑复杂查询时会导致其他查询变慢,对于全文检索、可变数据结构等场景,数据库天生不适用。因此需要针对特定的场景,引入合适的解决方案。如对于海量文件存储,可通过分布式文件系统HDFS解决,对于key value类型的数据,可通过HBase和Redis等方案解决,对于全文检索场景,可通过搜索引擎如ElasticSearch解决,对于多维分析场景,可通过Kylin或Druid等方案解决。

    当然,引入更多组件同时会提高系统的复杂度,不同的组件保存的数据需要同步,需要考虑一致性的问题,需要有更多的运维手段来管理这些组件等。

    引入更多组件解决了丰富的需求,业务维度能够极大扩充,随之而来的是一个应用中包含了太多的业务代码,业务的升级迭代变得困难
    ##第十次演进:大应用拆分为小应用

    11.png

    按照业务板块来划分应用代码,使单个应用的职责更清晰,相互之间可以做到独立升级迭代。这时候应用之间可能会涉及到一些公共配置,可以通过分布式配置中心Zookeeper来解决。

    不同应用之间存在共用的模块,由应用单独管理会导致相同代码存在多份,导致公共功能升级时全部应用代码都要跟着升级
    ##第十一次演进:复用的功能抽离成微服务

    12.png

    如用户管理、订单、支付、鉴权等功能在多个应用中都存在,那么可以把这些功能的代码单独抽取出来形成一个单独的服务来管理,这样的服务就是所谓的微服务,应用和服务之间通过HTTP、TCP或RPC请求等多种方式来访问公共服务,每个单独的服务都可以由单独的团队来管理。此外,可以通过Dubbo、SpringCloud等框架实现服务治理、限流、熔断、降级等功能,提高服务的稳定性和可用性。

    不同服务的接口访问方式不同,应用代码需要适配多种访问方式才能使用服务,此外,应用访问服务,服务之间也可能相互访问,调用链将会变得非常复杂,逻辑变得混乱
    ##第十二次演进:引入企业服务总线ESB屏蔽服务接口的访问差异

    13.png

    通过ESB统一进行访问协议转换,应用统一通过ESB来访问后端服务,服务与服务之间也通过ESB来相互调用,以此降低系统的耦合程度。这种单个应用拆分为多个应用,公共服务单独抽取出来来管理,并使用企业消息总线来解除服务之间耦合问题的架构,就是所谓的SOA(面向服务)架构,这种架构与微服务架构容易混淆,因为表现形式十分相似。个人理解,微服务架构更多是指把系统里的公共服务抽取出来单独运维管理的思想,而SOA架构则是指一种拆分服务并使服务接口访问变得统一的架构思想,SOA架构中包含了微服务的思想。

    业务不断发展,应用和服务都会不断变多,应用和服务的部署变得复杂,同一台服务器上部署多个服务还要解决运行环境冲突的问题,此外,对于如大促这类需要动态扩缩容的场景,需要水平扩展服务的性能,就需要在新增的服务上准备运行环境,部署服务等,运维将变得十分困难。
    ##第十三次演进:引入容器化技术实现运行环境隔离与动态服务管理

    14.png

    目前最流行的容器化技术是Docker,最流行的容器管理服务是Kubernetes(K8S),应用/服务可以打包为Docker镜像,通过K8S来动态分发和部署镜像。Docker镜像可理解为一个能运行你的应用/服务的最小的操作系统,里面放着应用/服务的运行代码,运行环境根据实际的需要设置好。把整个“操作系统”打包为一个镜像后,就可以分发到需要部署相关服务的机器上,直接启动Docker镜像就可以把服务起起来,使服务的部署和运维变得简单。

    在大促的之前,可以在现有的机器集群上划分出服务器来启动Docker镜像,增强服务的性能,大促过后就可以关闭镜像,对机器上的其他服务不造成影响(在3.14节之前,服务运行在新增机器上需要修改系统配置来适配服务,这会导致机器上其他服务需要的运行环境被破坏)。

    使用容器化技术后服务动态扩缩容问题得以解决,但是机器还是需要公司自身来管理,在非大促的时候,还是需要闲置着大量的机器资源来应对大促,机器自身成本和运维成本都极高,资源利用率低
    ##第十四次演进:以云平台承载系统

    15.png

    系统可部署到公有云上,利用公有云的海量机器资源,解决动态硬件资源的问题,在大促的时间段里,在云平台中临时申请更多的资源,结合Docker和K8S来快速部署服务,在大促结束后释放资源,真正做到按需付费,资源利用率大大提高,同时大大降低了运维成本。

    所谓的云平台,就是把海量机器资源,通过统一的资源管理,抽象为一个资源整体,在之上可按需动态申请硬件资源(如CPU、内存、网络等),并且之上提供通用的操作系统,提供常用的技术组件(如Hadoop技术栈,MPP数据库等)供用户使用,甚至提供开发好的应用,用户不需要关系应用内部使用了什么技术,就能够解决需求(如音视频转码服务、邮件服务、个人博客等)。在云平台中会涉及如下几个概念:

    * IaaS:基础设施即服务。对应于上面所说的机器资源统一为资源整体,可动态申请硬件资源的层面;
    * PaaS:平台即服务。对应于上面所说的提供常用的技术组件方便系统的开发和维护;
    * SaaS:软件即服务。对应于上面所说的提供开发好的应用或服务,按功能或性能要求付费。

    至此,以上所提到的从高并发访问问题,到服务的架构和系统实施的层面都有了各自的解决方案,但同时也应该意识到,在上面的介绍中,其实是有意忽略了诸如跨机房数据同步、分布式事务实现等等的实际问题,这些问题以后有机会再拿出来单独讨论。
    #架构设计总结

    ##架构的调整是否必须按照上述演变路径进行?

    不是的,以上所说的架构演变顺序只是针对某个侧面进行单独的改进,在实际场景中,可能同一时间会有几个问题需要解决,或者可能先达到瓶颈的是另外的方面,这时候就应该按照实际问题实际解决。如在政府类的并发量可能不大,但业务可能很丰富的场景,高并发就不是重点解决的问题,此时优先需要的可能会是丰富需求的解决方案。
    ##对于将要实施的系统,架构应该设计到什么程度?

    对于单次实施并且性能指标明确的系统,架构设计到能够支持系统的性能指标要求就足够了,但要留有扩展架构的接口以便不备之需。对于不断发展的系统,如电商平台,应设计到能满足下一阶段用户量和性能指标要求的程度,并根据业务的增长不断的迭代升级架构,以支持更高的并发和更丰富的业务。
    ##服务端架构和大数据架构有什么区别?

    所谓的“大数据”其实是海量数据采集清洗转换、数据存储、数据分析、数据服务等场景解决方案的一个统称,在每一个场景都包含了多种可选的技术,如数据采集有Flume、Sqoop、Kettle等,数据存储有分布式文件系统HDFS、FastDFS,NoSQL数据库HBase、MongoDB等,数据分析有Spark技术栈、机器学习算法等。总的来说大数据架构就是根据业务的需求,整合各种大数据组件组合而成的架构,一般会提供分布式存储、分布式计算、多维分析、数据仓库、机器学习算法等能力。而服务端架构更多指的是应用组织层面的架构,底层能力往往是由大数据架构来提供。
    #有没有一些架构设计的原则?


    * N+1设计。系统中的每个组件都应做到没有单点故障;
    * 回滚设计。确保系统可以向前兼容,在系统升级时应能有办法回滚版本;
    * 禁用设计。应该提供控制具体功能是否可用的配置,在系统出现故障时能够快速下线功能;
    * 监控设计。在设计阶段就要考虑监控的手段;
    * 多活数据中心设计。若系统需要极高的高可用,应考虑在多地实施数据中心进行多活,至少在一个机房断电的情况下系统依然可用;
    * 采用成熟的技术。刚开发的或开源的技术往往存在很多隐藏的bug,出了问题没有商业支持可能会是一个灾难;
    * 资源隔离设计。应避免单一业务占用全部资源;
    * 架构应能水平扩展。系统只有做到能水平扩展,才能有效避免瓶颈问题;
    * 非核心则购买。非核心功能若需要占用大量的研发资源才能解决,则考虑购买成熟的产品;
    * 使用商用硬件。商用硬件能有效降低硬件故障的机率;
    * 快速迭代。系统应该快速开发小功能模块,尽快上线进行验证,早日发现问题大大降低系统交付的风险;
    * 无状态设计。服务接口应该做成无状态的,当前接口的访问不依赖于接口上次访问的状态。

    原文链接:https://segmentfault.com/a/1190000018626163

    基于 ZooKeeper 实现爬虫集群的监控

    阿娇 发表了文章 • 0 个评论 • 154 次浏览 • 2019-05-29 13:03 • 来自相关话题

    从零开始搭建创业公司后台技术栈

    Andy_Lee 发表了文章 • 0 个评论 • 988 次浏览 • 2019-04-24 22:09 • 来自相关话题

    说到后台技术栈,脑海中是不是浮现的是这样一幅图?有点眼晕,以下只是我们会用到的一些语言的合集,而且只是语言层面的一部分,就整个后台技术栈来说,这只是一个开始,从语言开始,还有很多很多的内容。今天要说的后台是大后台的概念,放在服务器上的东西都属于后台的东西,比如 ...查看全部
    说到后台技术栈,脑海中是不是浮现的是这样一幅图?有点眼晕,以下只是我们会用到的一些语言的合集,而且只是语言层面的一部分,就整个后台技术栈来说,这只是一个开始,从语言开始,还有很多很多的内容。今天要说的后台是大后台的概念,放在服务器上的东西都属于后台的东西,比如使用的框架,语言,数据库,服务,操作系统等等。
    svr_stack1.png

    整个后台技术栈我的理解包括 4 个层面的内容:

    * 语言: 用了哪些开发语言,如:C++/Java/Go/PHP/Python/Ruby 等等;
    * 组件:用了哪些组件,如:MQ 组件,数据库组件等等;
    * 流程:怎样的流程和规范,如:开发流程,项目流程,发布流程,监控告警流程,代码规范等等;
    * 系统:系统化建设,上面的流程需要有系统来保证,如:规范发布流程的发布系统,代码管理系统等等;

    结合以上的的 4 个层面的内容,整个后台技术栈的结构如图 2 所示:
    svr_stack2.png

    图2 后台技术栈结构

    以上的这些内容都需要我们从零开始搭建,在创业公司,没有大公司那些完善的基础设施,需要我们从开源界,从云服务商甚至有些需要自己去组合,去拼装,去开发一个适合自己的组件或系统以达成我们的目标。咱们一个个系统和组件的做选型,最终形成我们的后台技术栈。
    #各系统组件选型
    ##项目管理/Bug管理/问题管理
    项目管理软件是整个业务的需求,问题,流程等等的集中地,大家的跨部门沟通协同大多依赖于项目管理工具。有一些 SaaS 的项目管理服务可以使用,但是很多时间不满足需求,此时我们可以选择一些开源的项目,这些项目本身有一定的定制能力,有丰富的插件可以使用,一般的创业公司需求基本上都能得到满足,常用的项目如下:

    * Redmine: 用 Ruby 开发的,有较多的插件可以使用,能自定义字段,集成了项目管理,Bug 问题跟踪,WIKI 等功能,不过好多插件 N 年没有更新了;
    * Phabricator:用 PHP 开发的,Facebook 之前的内部工具,开发这工具的哥们离职后自己搞了一个公司专门做这个软件,集成了代码托管, Code Review,任务管理,文档管理,问题跟踪等功能,强烈推荐较敏捷的团队使用;
    * Jira:用 Java 开发的,有用户故事,task 拆分,燃尽图等等,可以做项目管理,也可以应用于跨部门沟通场景,较强大;
    * 悟空 CRM :这个不是项目管理,这个是客户管理,之所以在这里提出来,是因为在 To B 的创业公司里面,往往是以客户为核心来做事情的,可以将项目管理和问题跟进的在悟空 CRM 上面来做,他的开源版本已经基本实现了 CR< 的核心 功能,还带有一个任务管理功能,用于问题跟进,不过用这个的话,还是需要另一个项目管理的软件协助,顺便说一嘴,这个系统的代码写得很难维护,只能适用于客户规模小(1万以内)时。

    ##DNS
    DNS 是一个很通用的服务,创业公司基本上选择一个合适的云厂商就行了,国内主要是两家:

    * 阿里万网:阿里 2014 年收购了万网,整合了其域名服务,最终形成了现在的阿里万网,其中就包含 DNS 这块的服务;
    * 腾讯 DNSPod:腾讯 2012 年以 4000 万收购 DNSPod 100% 股份,主要提供域名解析和一些防护功能;

    如果你的业务是在国内,主要就是这两家,选 一个就好,像今日头条这样的企业用的也是 DNSPod 的服务,除非一些特殊的原因才需要自建,比如一些 CDN 厂商,或者对区域有特殊限制的。要实惠一点用阿里最便宜的基础版就好了,要成功率高一些,还是用 DNSPod 的贵的那种。

    在国外还是选择亚马逊吧,阿里的 DNS 服务只有在日本和美国有节点,东南亚最近才开始部点, DNSPod 也只有美国和日本,像一些出海的企业,其选择的云服务基本都是亚马逊。

    如果是线上产品,DNS 强烈建议用付费版,阿里的那几十块钱的付费版基本可以满足需求。如果还需要一些按省份或按区域调试的逻辑,则需要加钱,一年也就几百块,省钱省力。

    如果是国外,优先选择亚马逊,如果需要国内外互通并且有自己的 APP 的话,建议还是自己实现一些容灾逻辑或者智能调度,因为没有一个现成的 DNS 服务能同时较好的满足国内外场景,或者用多个域名,不同的域名走不同的 DNS 。
    ##LB(负载均衡)
    LB(负载均衡)是一个通用服务,一般云厂商的 LB 服务基本都会如下功能:

    * 支持四层协议请求(包括 TCP、UDP 协议);
    * 支持七层协议请求(包括 HTTP、HTTPS 协议);
    * 集中化的证书管理系统支持 HTTPS 协议;
    * 健康检查;

    如果你线上的服务机器都是用的云服务,并且是在同一个云服务商的话,可以直接使用云服务商提供的 LB 服务,如阿里云的 SLB,腾讯云的 CLB,亚马逊的 ELB 等等。如果是自建机房基本都是 LVS + Nginx。
    ##CDN
    CDN 现在已经是一个很红很红的市场,基本上只能挣一些辛苦钱,都是贴着成本在卖。国内以网宿为龙头,他们家占据整个国内市场份额的 40% 以上,后面就是腾讯,阿里。网宿有很大一部分是因为直播的兴起而崛起。

    国外,Amazon 和 Akamai 合起来占比大概在 50%,曾经的国际市场老大 Akamai 拥有全球超一半的份额,在 Amazon CDN入局后,份额跌去了将近 20%,众多中小企业都转向后者,Akamai 也是无能为力。

    国内出海的 CDN 厂商,更多的是为国内的出海企业服务,三家大一点的 CDN 服务商里面也就网宿的节点多一些,但是也多不了多少。阿里和腾讯还处于前期阶段,仅少部分国家有节点。

    就创业公司来说,CDN 用腾讯云或阿里云即可,其相关系统较完善,能轻松接入,网宿在系统支持层面相对较弱一些,而且还贵一些。并且,当流量上来后,CDN 不能只用一家,需要用多家,不同的 CDN 在全国的节点覆盖不一样,而且针对不同的客户云厂商内部有些区分客户集群,并不是全节点覆盖(但有些云厂商说自己是全网节点),除了节点覆盖的问题,多 CDN 也在一定程度上起到容灾的作用。
    ##RPC 框架
    维基百科对 RPC 的定义是:远程过程调用(Remote Procedure Call,RPC)是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。

    通俗来讲,一个完整的 RPC 调用过程,就是 Server 端实现了一个函数,客户端使用 RPC 框架提供的接口,调用这个函数的实现,并获取返回值的过程。

    业界 RPC 框架大致分为两大流派,一种侧重跨语言调用,另一种是偏重服务治理。

    跨语言调用型的 RPC 框架有 Thrift、gRPC、Hessian、Hprose 等。这类 RPC 框架侧重于服务的跨语言调用,能够支持大部分的语言进行语言无关的调用,非常适合多语言调用场景。但这类框架没有服务发现相关机制,实际使用时需要代理层进行请求转发和负载均衡策略控制。

    其中,gRPC 是 Google 开发的高性能、通用的开源 RPC 框架,其由 Google 主要面向移动应用开发并基于 HTTP/2 协议标准而设计,基于 ProtoBuf(Protocol Buffers)序列化协议开发,且支持众多开发语言。本身它不是分布式的,所以要实现框架的功能需要进一步的开发。

    Hprose(High Performance Remote Object Service Engine)是一个 MIT 开源许可的新型轻量级跨语言跨平台的面向对象的高性能远程动态通讯中间件。

    服务治理型的 RPC 框架的特点是功能丰富,提供高性能的远程调用、服务发现及服务治理能力,适用于大型服务的服务解耦及服务治理,对于特定语言(Java)的项目可以实现透明化接入。缺点是语言耦合度较高,跨语言支持难度较大。国内常见的冶理型 RPC 框架如下:

    * Dubbo:Dubbo 是阿里巴巴公司开源的一个 Java 高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring 框架无缝集成。当年在淘宝内部,Dubbo 由于跟淘宝另一个类似的框架 HSF 有竞争关系,导致 Dubbo 团队解散,最近又活过来了,有专职同学投入。
    * DubboX:DubboX 是由当当在基于 Dubbo 框架扩展的一个 RPC 框架,支持 REST 风格的远程调用、Kryo/FST 序列化,增加了一些新的feature。
    Motan:Motan 是新浪微博开源的一个 Java 框架。它诞生的比较晚,起于 2013 年,2016 年 5 月开源。Motan 在微博平台中已经广泛应用,每天为数百个服务完成近千亿次的调用。
    * rpcx:rpcx 是一个类似阿里巴巴 Dubbo 和微博 Motan 的分布式的 RPC 服务框架,基于 Golang net/rpc 实现。但是 rpcx 基本只有一个人在维护,没有完善的社区,使用前要慎重,之前做 Golang 的 RPC 选型时也有考虑这个,最终还是放弃了,选择了 gRPC,如果想自己自研一个 RPC 框架,可以参考学习一下。

    ##名字发现/服务发现
    名字发现和服务发现分为两种模式,一个是客户端发现模式,一种是服务端发现模式。

    框架中常用的服务发现是客户端发现模式。

    所谓服务端发现模式是指客户端通过一个负载均衡器向服务发送请求,负载均衡器查询服务注册表并把请求路由到一台可用的服务实例上。现在常用的负载均衡器都是此类模式,常用于微服务中。

    所有的名字发现和服务发现都要依赖于一个可用性非常高的服务注册表,业界常用的服务注册表有如下三个:

    * etcd,一个高可用、分布式、一致性、key-value 方式的存储,被用在分享配置和服务发现中。两个著名的项目使用了它:Kubernetes 和 Cloud Foundry。
    * Consul,一个发现和配置服务的工具,为客户端注册和发现服务提供了API,Consul还可以通过执行健康检查决定服务的可用性。
    * Apache ZooKeeper,是一个广泛使用、高性能的针对分布式应用的协调服务。Apache ZooKeeper 本来是 Hadoop 的子工程,现在已经是顶级工程了。

    除此之外也可以自己实现服务实现,或者用 Redis 也行,只是需要自己实现高可用性。
    ##关系数据库
    关系数据库分为两种,一种是传统关系数据,如 Oracle,MySQL,Maria,DB2,PostgreSQL 等等,另一种是 NewSQL,即至少要满足以下五点的新型关系数据库:

    1. 完整地支持 SQL,支持 JOIN / GROUP BY /子查询等复杂 SQL 查询。
    2. 支持传统数据标配的 ACID 事务,支持强隔离级别。
    3. 具有弹性伸缩的能力,扩容缩容对于业务层完全透明。
    4. 真正的高可用,异地多活、故障恢复的过程不需要人为的接入,系统能够自动地容灾和进行强一致的数据恢复。
    5. 具备一定的大数据分析能力。

    传统关系数据库用得最多的是 MySQL,成熟,稳定,一些基本的需求都能满足,在一定数据量级之前基本单机传统数据库都可以搞定,而且现在较多的开源系统都是基于 MySQL,开箱即用,再加上主从同步和前端缓存,百万 pv 的应用都可以搞定了。不过 CentOS 7 已经放弃了 MySQL,而改使用 MariaDB。MariaDB 数据库管理系统是 MySQ L的一个分支,主要由开源社区在维护,采用 GPL 授权许可。开发这个分支的原因之一是:甲骨文公司收购了 MySQL 后,有将 MySQL 闭源的潜在风险,因此社区采用分支的方式来避开这个风险。

    在 Google 发布了 F1: A Distributed SQL Database That Scales 和 Spanner: Google’s Globally-Distributed Databasa 之后,业界开始流行起 NewSQL。于是有了 CockroachDB,于是有了奇叔公司的 TiDB。国内已经有比较多的公司使用 TiDB,之前在创业公司时在大数据分析时已经开始应用 TiDB,当时应用的主要原因是 MySQL 要使用分库分表,逻辑开发比较复杂,扩展性不够。
    ##NoSQL
    NoSQL 顾名思义就是 Not-Only SQL,也有人说是 No – SQL,个人偏向于 Not-Only SQL,它并不是用来替代关系库,而是作为关系型数据库的补充而存在。

    常见 NoSQL 有4个类型:

    * 键值,适用于内容缓存,适合混合工作负载并发高扩展要求大的数据集,其优点是简单,查询速度快,缺点是缺少结构化数据,常见的有 Redis,Memcache,BerkeleyDB 和 Voldemort 等等;
    * 列式,以列簇式存储,将同一列数据存在一起,常见于分布式的文件系统,其中以 Hbase,Cassandra 为代表。Cassandra 多用于写多读少的场景,国内用得比较多的有 360,大概 1500 台机器的集群,国外大规模使用的公司比较多,如 eBay,Instagram,Apple 和沃尔玛等等;
    * 文档,数据存储方案非常适用承载大量不相关且结构差别很大的复杂信息。性能介于 kv 和关系数据库之间,它的灵感来于 lotus notes,常见的有 MongoDB,CouchDB 等等;
    * 图形,图形数据库擅长处理任何涉及关系的状况。社交网络,推荐系统等。专注于构建关系图谱,需要对整个图做计算才能得出结果,不容易做分布式的集群方案,常见的有 Neo4J,InfoGrid 等。

    除了以上4种类型,还有一些特种的数据库,如对象数据库,XML 数据库,这些都有针对性对某些存储类型做了优化的数据库。

    在实际应用场景中,何时使用关系数据库,何时使用 NoSQL,使用哪种类型的数据库,这是我们在做架构选型时一个非常重要的考量,甚至会影响整个架构的方案。
    ##消息中间件
    消息中间件在后台系统中是必不可少的一个组件,一般我们会在以下场景中使用消息中间件:

    * 异步处理:异步处理是使用消息中间件的一个主要原因,在工作中最常见的异步场景有用户注册成功后需要发送注册成功邮件、缓存过期时先返回老的数据,然后异步更新缓存、异步写日志等等;通过异步处理,可以减少主流程的等待响应时间,让非主流程或者非重要业务通过消息中间件做集中的异步处理。
    * 系统解耦:比如在电商系统中,当用户成功支付完成订单后,需要将支付结果给通知ERP系统、发票系统、WMS、推荐系统、搜索系统、风控系统等进行业务处理;这些业务处理不需要实时处理、不需要强一致,只需要最终一致性即可,因此可以通过消息中间件进行系统解耦。通过这种系统解耦还可以应对未来不明确的系统需求。
    * 削峰填谷:当系统遇到大流量时,监控图上会看到一个一个的山峰样的流量图,通过使用消息中间件将大流量的请求放入队列,通过消费者程序将队列中的处理请求慢慢消化,达到消峰填谷的效果。最典型的场景是秒杀系统,在电商的秒杀系统中下单服务往往会是系统的瓶颈,因为下单需要对库存等做数据库操作,需要保证强一致性,此时使用消息中间件进行下单排队和流控,让下单服务慢慢把队列中的单处理完,保护下单服务,以达到削峰填谷的作用。

    业界消息中间件是一个非常通用的东西,大家在做选型时有使用开源的,也有自己造轮子的,甚至有直接用 MySQL 或 Redis 做队列的,关键看是否满足你的需求,如果是使用开源的项目,以下的表格在选型时可以参考:
    svr_stack3.png

    图 3

    以上图的纬度为:名字、成熟度、所属社区/公司、文档、授权方式、开发语言、支持的协议、客户端支持的语言、性能、持久化、事务、集群、负载均衡、管理界面、部署方式、评价。
    ##代码管理
    代码是互联网创业公司的命脉之一,代码管理很重要,常见的考量点包括两块:

    * 安全和权限管理,将代码放到内网并且对于关系公司命脉的核心代码做严格的代码控制和机器的物理隔离;
    * 代码管理工具,Git 作为代码管理的不二之选,你值得拥有。GitLab 是当今最火的开源 Git 托管服务端,没有之一,虽然有企业版,但是其社区版基本能满足我们大部分需求,结合 Gerrit 做 Code review,基本就完美了。当然 GitLab 也有代码对比,但没 Gerrit 直观。Gerrit 比 GitLab 提供了更好的代码检查界面与主线管理体验,更适合在对代码质量有高要求的文化下使用。

    ##持续集成
    持续集成简,称 CI(continuous integration),是一种软件开发实践,即团队开发成员经常集成他们的工作,每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。持续集成为研发流程提供了代码分支管理/比对、编译、检查、发布物输出等基础工作,为测试的覆盖率版本编译、生成等提供统一支持。

    业界免费的持续集成工具中系统我们有如下一些选择:

    * Jenkins:Java 写的有强大的插件机制,MIT 协议开源 (免费,定制化程度高,它可以在多台机器上进行分布式地构建和负载测试)。Jenkins 可以算是无所不能,基本没有 Jenkins 做不了的,无论从小型团队到大型团队 Jenkins 都可以搞定。不过如果要大规模使用,还是需要有人力来学习和维护。
    * TeamCity:TeamCity 与 Jenkins 相比使用更加友好,也是一个高度可定制化的平台。但是用的人多了,TeamCity就要收费了。
    * Strider:Strider 是一个开源的持续集成和部署平台,使用 Node.js 实现,存储使用的是 MongoDB,BSD 许可证,概念上类似 Travis 和Jenkins。
    * GitLab CI:从GitLab 8.0开始,GitLab CI 就已经集成在 GitLab,我们只要在项目中添加一个 .gitlab-ci.yml 文件,然后添加一个 Runner,即可进行持续集成。并且 GitLab 与 Docker 有着非常好的相互协作的能力。免费版与付费版本不同可以参见这里:https://about.gitlab.com/products/feature-comparison/。
    * Travis:Travis 和 GitHub 强关联;闭源代码使用 SaaS 还需考虑安全问题;不可定制;开源项目免费,其它收费。
    * Go:Go 是 ThoughtWorks 公司最新的 Cruise Control 的化身。除了 ThoughtWorks 提供的商业支持,Go 是免费的。它适用于 Windows,Mac 和各种 Linux 发行版。

    ##日志系统
    日志系统一般包括打日志,采集,中转,收集,存储,分析,呈现,搜索还有分发等。一些特殊的如染色,全链条跟踪或者监控都可能需要依赖于日志系统实现。日志系统的建设不仅仅是工具的建设,还有规范和组件的建设,最好一些基本的日志在框架和组件层面加就行了,比如全链接跟踪之类的。

    对于常规日志系统ELK能满足大部分的需求,ELK 包括如下组件:

    * ElasticSearch 是个开源分布式搜索引擎,它的特点有:分布式,零配置,自动发现,索引自动分片,索引副本机制,RESTful 风格接口,多数据源,自动搜索负载等。
    * Logstash 是一个完全开源的工具,它可以对你的日志进行收集、分析,并将其存储供以后使用。
    * Kibana 是一个开源和免费的工具,它可以为 Logstash 和 ElasticSearch 提供的日志分析友好的 Web 界面,可以帮助汇总、分析和搜索重要数据日志。
    * Filebeat 已经完全替代了 Logstash-Forwarder 成为新一代的日志采集器,同时鉴于它轻量、安全等特点,越来越多人开始使用它。

    因为免费的 ELK 没有任何安全机制,所以这里使用了 Nginx 作反向代理,避免用户直接访问 Kibana 服务器。加上配置 Nginx 实现简单的用户认证,一定程度上提高安全性。另外,Nginx 本身具有负载均衡的作用,能够提高系统访问性能。ELK 架构如图4所示:
    svr_stack4_elk.png

    图 4,ELK 流程图

    对于有实时计算的需求,可以使用 Flume + Kafka + Storm + MySQL 方案,一 般架构如图 5 所示:
    svr_stack5.png

    图 5,实时分析系统架构图

    其中:

    * Flume 是一个分布式、可靠、和高可用的海量日志采集、聚合和传输的日志收集系统,支持在日志系统中定制各类数据发送方,用于收集数据;同时,Flume 提供对数据进行简单处理,并写到各种数据接受方(可定制)的能力。
    * Kafka 是由 Apache 软件基金会开发的一个开源流处理平台,由 Scala 和 Java 编写。其本质上是一个“按照分布式事务日志架构的大规模发布/订阅消息队列”,它以可水平扩展和高吞吐率而被广泛使用。

    Kafka 追求的是高吞吐量、高负载,Flume 追求的是数据的多样性,二者结合起来简直完美。
    ##监控系统
    监控系统只包含与后台相关的,这里主要是两块,一个是操作系统层的监控,比如机器负载,IO,网络流量,CPU,内存等操作系统指标的监控。另一个是服务质量和业务质量的监控,比如服务的可用性,成功率,失败率,容量,QPS 等等。常见业务的监控系统先有操作系统层面的监控(这部分较成熟),然后扩展出其它监控,如 Zabbix,小米的 Open-Falcon,也有一出来就是两者都支持的,如 Prometheus。如果对业务监控要求比较高一些,在创业选型中建议可以优先考虑 Prometheus。这里有一个有趣的分布,如图6所示。
    svr_stack6.png

    图 6,监控系统分布

    亚洲区域使用 Zabbix 较多,而美洲和欧洲,以及澳大利亚使用 Prometheus 居多,换句话说,英文国家地区(发达国家?)使用 Prometheus 较多。

    Prometheus 是由 SoundCloud 开发的开源监控报警系统和时序列数据库(TSDB)。Prometheus 使用 Go 语言开发,是 Google BorgMon 监控系统的开源版本。相对于其它监控系统使用的 push 数据的方式,Prometheus 使用的是 pull 的方式,其架构如图 7 所示:
    svr_stack7.png

    图 7,Prometheus 架构图

    如上图所示,Prometheus 包含的主要组件如下:

    * Prometheus Server 主要负责数据采集和存储,提供 PromQL 查询语言的支持。Server 通过配置文件、文本文件、ZooKeeper、Consul、DNS SRV Lookup 等方式指定抓取目标。根据这些目标会,Server 定时去抓取 metrics 数据,每个抓取目标需要暴露一个 http 服务的接口给它定时抓取。
    * 客户端 SDK:官方提供的客户端类库有 Go、Java、Scala、Python、Ruby,其他还有很多第三方开发的类库,支持 Nodejs、PHP、Erlang 等。
    * Push Gateway 支持临时性 Job 主动推送指标的中间网关。
    * Exporter Exporter 是 Prometheus 的一类数据采集组件的总称。它负责从目标处搜集数据,并将其转化为 Prometheus 支持的格式。与传统的数据采集组件不同的是,它并不向中央服务器发送数据,而是等待中央服务器主动前来抓取。Prometheus 提供多种类型的 Exporter 用于采集各种不同服务的运行状态。目前支持的有数据库、硬件、消息中间件、存储系统、HTTP 服务器、JMX 等。
    * Alertmanager:是一个单独的服务,可以支持 Prometheus 的查询语句,提供十分灵活的报警方式。
    * Prometheus HTTP API 的查询方式,自定义所需要的输出。
    * Grafana 是一套开源的分析监视平台,支持 Graphite,InfluxDB,OpenTSDB,Prometheus,Elasticsearch,CloudWatch 等数据源,其 UI 非常漂亮且高度定制化。

    创业公司选择 Prometheus + Grafana 的方案,再加上统一的服务框架(如 gRPC),可以满足大部分中小团队的监控需求。
    ##配置系统
    随着程序功能的日益复杂,程序的配置日益增多:各种功能的开关、降级开关,灰度开关,参数的配置、服务器的地址、数据库配置等等,除此之外,对后台程序配置的要求也越来越高:配置修改后实时生效,灰度发布,分环境、分用户,分集群管理配置,完善的权限、审核机制等等,在这样的大环境下,传统的通过配置文件、数据库等方式已经越来越无法满足开发人员对配置管理的需求,业界有如下两种方案:

    * 基于 zk 和 etcd,支持界面和 api ,用数据库来保存版本历史,预案,走审核流程,最后下发到 zk 或 etcd 这种有推送能力的存储里(服务注册本身也是用 zk 或 etcd,选型就一块了)。客户端都直接和 zk 或 etcd 打交道。至于灰度发布,各家不同,有一种实现是同时发布一个需要灰度的 IP 列表,客户端监听到配置节点变化时,对比一下自己是否属于该列表。PHP 这种无状态的语言和其他 zk/etcd 不支持的语言,只好自己在客户端的机器上起一个 Agent 来监听变化,再写到配置文件或共享内存,如 360 的 Qconf。
    * 基于运维自动化的配置文件的推送,审核流程,配置数据管理和方案一类似,下发时生成配置文件,基于运维自动化工具如 Puppet,Ansible 推送到每个客户端,而应用则定时重新读取这个外部的配置文件,灰度发布在下发配置时指定IP列表。

    创业公司前期不需要这种复杂,直接上 zk,弄一个界面管理 zk 的内容,记录一下所有人的操作日志,程序直连 zk,或者或者用 Qconf 等基于 zk 优化后的方案。
    ##发布系统/部署系统
    从软件生产的层面看,代码到最终服务的典型流程如图 8 所示:
    svr_stack8.png

    图 8,流程图

    从上图中可以看出,从开发人员写下代码到服务最终用户是一个漫长过程,整体可以分成三个阶段:

    * 从代码(Code)到成品库(Artifact)这个阶段主要对开发人员的代码做持续构建并把构建产生的制品集中管理,是为部署系统准备输入内容的阶段。
    * 从制品到可运行服务 这个阶段主要完成制品部署到指定环境,是部署系统的最基本工作内容。
    * 从开发环境到最终生产环境 这个阶段主要完成一次变更在不同环境的迁移,是部署系统上线最终服务的核心能力。

    发布系统集成了制品管理,发布流程,权限控制,线上环境版本变更,灰度发布,线上服务回滚等几方面的内容,是开发人员工作结晶最终呈现的重要通道。开源的项目中没有完全满足的项目,如果只是 Web 类项目,Walle、Piplin 都是可用的,但是功能不太满足,创业初期可以集成 Jenkins + Gitlab + Walle(可以考虑两天时间完善一下),以上方案基本包括制品管理,发布流程,权限控制,线上环境版本变更,灰度发布(需要自己实现),线上服务回滚等功能。
    ##跳板机
    跳板机面对的是需求是要有一种能满足角色管理与授权审批、信息资源访问控制、操作记录和审计、系统变更和维护控制要求,并生成一些统计报表配合管理规范来不断提升IT内控的合规性,能对运维人员操作行为的进行控制和审计,对误操作、违规操作导致的操作事故,快速定位原因和责任人。其功能模块一般包括:帐户管理、认证管理、授权管理、审计管理等等。

    开源项目中,Jumpserver 能够实现跳板机常见需求,如授权、用户管理、服务器基本信息记录等,同时又可批量执行脚本等功能;其中录像回放、命令搜索、实时监控等特点,又能帮助运维人员回溯操作历史,方便查找操作痕迹,便于管理其他人员对服务器的操作控制。
    ##机器管理
    机器管理的工具选择的考量可以包含以下三个方面:

    1. 是否简单,是否需要每台机器部署 Agent(客户端)
    2. 语言的选择(Puppet/Chef vs Ansible/SaltStack )开源技术,不看官网不足以熟练,不懂源码不足以精通;Puppet、Chef 基于 Ruby 开发,Ansible、SaltStack 基于 Python 开发的
    3. 速度的选择(Ansible vs SaltStack)Ansible 基于 SSH 协议传输数据,SaltStack 使用消息队列 zeroMQ 传输数据;大规模并发的能力对于几十台-200 台规模的兄弟来讲,Ansible的性能也可接受,如果一次操作上千台,用 salt 好一些。

    如图9所示:
    svr_statck9.png

    图 9,机器管理软件对比

    一般创业公司选择 Ansible 能解决大部问题,其简单,不需要安装额外的客户端,可以从命令行来运行,不需要使用配置文件。至于比较复杂的任务,Ansible 配置通过名为 Playbook 的配置文件中的 YAML 语法来加以处理。Playbook 还可以使用模板来扩展其功能。
    #创业公司的选择
    ##选择合适的语言

    * 选择团队熟悉的/能掌控的,创业公司人少事多,无太多冗余让研发团队熟悉新的语言,能快速上手,能快速出活,出了问题能快速解决的问题的语言才是好的选择。
    * 选择更现代一些的,这里的现代是指语言本身已经完成一些之前需要特殊处理的特性,比如内存管理,线程等等。
    * 选择开源轮子多的或者社区活跃度高的,这个原则是为了保证在开发过程中减少投入,有稳定可靠的轮子可以使用,遇到问题可以在网上快速搜索到答案。
    * 选择好招人的 一门合适的语言会让创业团队减少招聘的成本,快速招到合适的人。
    * 选择能让人有兴趣的 与上面一点相关,让人感兴趣,在后面留人时有用。

    ##选择合适的组件和云服务商

    * 选择靠谱的云服务商;
    * 选择云服务商的组件;
    * 选择成熟的开源组件,而不是最新出的组件;
    * 选择采用在一线互联网公司落地并且开源的,且在社区内形成良好口碑的产品;
    * 开源社区活跃度;

    选择靠谱的云服务商,其实这是一个伪命题,因为哪个服务商都不靠谱,他们所承诺的那些可用性问题基本上都会在你的身上发生,这里我们还是需要自己做一些工作,比如多服务商备份,如用 CDN,你一定不要只选一家,至少选两家,一个是灾备,保持后台切换的能力,另一个是多点覆盖,不同的服务商在 CDN 节点上的资源是不一样的。

    选择了云服务商以后,就会有很多的产品你可以选择了,比较存储,队列这些都会有现成的产品,这个时候就纠结了,是用呢?还是自己在云主机上搭呢?在这里我的建议是前期先用云服务商的,大了后再自己搞,这样会少掉很多运维的事情,但是这里要多了解一下云服务商的组件特性以及一些坑,比如他们内网会经常断开,他们升级也会闪断,所以在业务侧要做好容错和规避。

    关于开源组件,尽可能选择成熟的,成熟的组件经历了时间的考验,基本不会出大的问题,并且有成套的配套工具,出了问题在网上也可以很快的找到答案,你所遇到的坑基本上都有人踩过了。
    ##制定流程和规范

    * 制定开发的规范,代码及代码分支管理规范,关键性代码仅少数人有权限;
    * 制定发布流程规范,从发布系统落地;
    * 制定运维规范;
    * 制定数据库操作规范,收拢数据库操作权限;
    * 制定告警处理流程,做到告警有人看有人处理;
    * 制定汇报机制,晨会/周报;

    ##自研和选型合适的辅助系统
    所有的流程和规范都需要用系统来固化,否则就是空中楼阁,如何选择这些系统呢?参照上个章节咱们那些开源的,对比一下选择的语言,组件之类的,选择一个最合适的即可。

    比如项目管理的,看下自己是什么类型的公司,开发的节奏是怎样的,瀑布,敏捷的 按项目划分,还是按客户划分等等,平时是按项目组织还是按任务组织等等

    比如日志系统,之前是打的文本,那么上一个 ELK,规范化一些日志组件,基本上很长一段时间内不用考虑日志系统的问题,最多拆分一下或者扩容一下。等到组织大了,自己搞一个日志系统。

    比如代码管理,项目管理系统这些都放内网,安全,在互联网公司来说,属于命脉了,命脉的东西还是放在别人拿不到或很难拿到的地方会比较靠谱一些。
    ##选择过程中需要思考的问题
    技术栈的选择有点像做出了某种承诺,在一定的时间内这种承诺没法改变,于是我们需要在选择的时候有一些思考。

    看前面内容,有一个词出现了三次,合适,选择是合适的,不是最好,也不是最新,是最合适,适合是针对当下,这种选择是最合适的吗?比如用 Go 这条线的东西,技术比较新,业界组件储备够吗?组织内的人员储备够吗?学习成本多少?写出来的东西能满足业务性能要求吗?能满足时间要求吗?

    向未来看一眼,在一年到三年内,我们需要做出改变吗?技术栈要做根本性的改变吗?如果组织发展很快,在 200 人,500 人时,现有的技术栈是否需要大动?

    创业过程中需要考虑成本,这里的成本不仅仅是花费多少钱,付出多少工资,有时更重要的是时间成本,很多业务在创业时大家拼的就是时间,就是一个时间窗,过了就没你什么事儿了。
    #基于云的创业公司后台技术架构
    结合上面内容的考量,在对一个个系统和组件的做选型之后,以云服务为基础,一个创业公司的后台技术架构如图10所示:
    svr_stack10.png

    图 10,后台技术架构

    参考资料:

    * http://database.51cto.com/art/201109/291781.htm
    * https://zh.wikipedia.org/wiki/Kafka
    * https://prometheus.io/docs/introduction/overview/
    * http://deadline.top/2016/11/23/配置中心那点事/
    * http://blog.fit2cloud.com/2016/01/26/deployment-system.html

    原文链接:从零开始搭建创业公司后台技术栈(作者:潘锦)

    分布式协调神器 ZooKeeper 之整体概述

    大卫 发表了文章 • 0 个评论 • 696 次浏览 • 2019-03-04 20:50 • 来自相关话题

    ZooKeeper 最早起源于雅虎研究院的一个研究小组。当时,雅虎内部很多大型系统基本都需要依赖一个类似的系统来进行分布式协调,但是这些系统往往都存在分布式单点问题。所以,雅虎的开发人员就试图开发一个通用的无单点问题的分布式协调框架,以便让开发人员将精力集中在 ...查看全部
    ZooKeeper 最早起源于雅虎研究院的一个研究小组。当时,雅虎内部很多大型系统基本都需要依赖一个类似的系统来进行分布式协调,但是这些系统往往都存在分布式单点问题。所以,雅虎的开发人员就试图开发一个通用的无单点问题的分布式协调框架,以便让开发人员将精力集中在处理业务逻辑上。

    立项初期,考虑到之前内部很多项目都是使用动物的名字来命名的(例如著名的 Pig 项目),雅虎的工程师希望给这个项目也取一个动物的名字。当时研究院的首席科学家 RaghuRamakrishnan 开玩笑说:“再这样下去,我们这儿就变成动物园了!”是不是很有趣,顺势大家就表示既然已经是动物园了,它就叫动物园管理员吧!各个以动物命名的分布式组件放在一起,雅虎的整个分布式系统看上去就像一个大型的动物园了,而 ZooKeeper 正好要用来进行分布式环境的协调一一于是,ZooKeeper 的名字也就由此诞生了!
    #ZooKeeper概述

    ZooKeeper 是一种用于分布式应用程序的分布式开源协调服务。它公开了一组简单的原语,分布式应用程序可以构建这些原语,以实现更高级别的服务,以实现同步,配置维护以及组和命名。它被设计为易于编程,并使用在熟悉的文件系统目录树结构之后设计的数据模型。它在Java中运行,并且具有Java和C的绑定。

    众所周知,协调服务很难做到。他们特别容易出现诸如竞争条件和死锁等错误。ZooKeeper 背后的动机是减轻分布式应用程序从头开始实施协调服务的责任。
    #集群模型

    1.png

    Leader 服务器是整个 ZooKeeper 集群工作机制中的核心,其主要工作有以下两个:

    1. 事务请求的唯一调度和处理者,保证集群事务处理的顺序性。
    2. 集群内部各服务器的调度者。

    从角色名字上可以看出,Follewer 服务器是 ZooKeeper 集群状态的跟随者,其主要工作有以下三个:

    1. 处理客户端非事务请求,转发事务请求给 Leader 服务器。
    2. 参与事务请求 Proposal 的投票。
    3. 参与 Leader 选举投票。

    Observer 充当了一个观察者的角色,在工作原理上基本和 Follower 一致,唯一的区别在于,它不参与任何形式的投票。
    #数据结构

    2.png

    #树形结构

    首先我们来看上述数据节点示意图,从而对 ZooKeeper 上的数据节点有一个大体上的认识,在 ZooKeeper 中,每一个节点都被称为一个 ZNode,所有 ZNode 按层次化机构进行组织,形成一棵树。ZNode 节点路径标识方式和 Unix 文件系统路径非常相似,都是由一系列使用斜杠(/)进行分割的路径表示,开发人员可以向这个节点中写入数据,也可以在节点下面创建子节点。
    #节点操作流程

    3.png


    1. 在 Client 向 Follower 发出一个写请求。
    2. Follower 把请求转发给 Leader。
    3. Leader 接收到以后开始发起投票并通知 Follower 进行投票。
    4. Follower 把投票结果发送给 Leader。
    5. Leader 将结果汇总后,如果需要写入,则开始写入,同时把写入操作通知给 Follower,然后 commit。
    6. Follower 把请求结果返回给 Client。

    #设计目标


    1. 顺序一致性,来自任意特定客户端的更新都会按其发送顺序被提交。也就是说,如果一个客户端将 Znode z 的值更新为 a,在之后的操作中,它又将 z 的值更新为 b,则没有客户端能够在看到z的值是b之后再看到值 a(如果没有其他对z的更新)。
    2. 原子性,每个更新要么成功,要么失败。这意味着如果一个更新失败,则不会有客户端会看到这个更新的结果。
    3. 单一系统映像,一个客户端无论连接到哪一台服务器,它看到的都是同样的系统视图。这意味着,如果一个客户端在同一个会话中连接到一台新的服务器,它所看到的系统状态不会比 在之前服务器上所看到的更老。当一台服务器出现故障,导致它的一个客户端需要尝试连接集合体中其他的服务器时,所有滞后于故障服务器的服务器都不会接受该 连接请求,除非这些服务器赶上故障服务器。
    4. 持久性,一个更新一旦成功,其结果就会持久存在并且不会被撤销。这表明更新不会受到服务器故障的影响。

    #整体架构

    4.png


    * ServerCnxnFactory,ZooKeeper服务端网络连接工厂。在早期版本中,ZooKeeper 都是自己实现 NIO 框架,从 3.4.0 版本开始,引入了 Netty。可以通过 zookeeper.serverCnxnFactory 来指定使用具体的实现。
    * SessionTracker,ZooKeeper 服务端会话管理器。创建时,会初始化 expirationInterval、nextExpirationTime、sessionsWithTimeout(用于保存每个会话的超时时间),同时还会计算出一个初始化的sessionID。
    * RequestProcessor,ZooKeeper的请求处理方式是典型的责任链模式,在服务端,会有多个请求处理器依次来处理一个客户的请求。在服务器启动的时候,会将这些请求处理器串联起来形成一个请求处理链。基本的请求处理链如下:
    5.png

    * LearnerCnxAcceptor,Learner服务器(等于 Follower 服务器)连接请求接收器。负责 Leader 服务器和 Follower 服务器保持连接,以确定集群机器存活情况,并处理连接请求。
    * LearnerHandler,Leader 接收来自其他机器的连接创建请求后,会创建一个 LearnerHandler 实例。每个 LearnerHandler 实例都对应了一个 Leader 和 Learner 服务器之间的连接,其负责 Leader 和 Learner 服务器之间几乎所有的消息通信和数据同步。
    * ZKDatabase,ZooKeeper 内存数据库,负责管理 ZooKeeper 的所有会话记录以及 DataTree 和事务日志的存储。
    * FileTxnSnapLog,ZooKeeper 上层服务和底层数据存储之间的对接层,提供了一系列的操作数据文件的接口,包括事务文件和快照数据文件。ZooKeeper 根据 zoo.cfg 文件中解析出的快照数据目录 dataDir 和事务日志目录 dataLogDir 来创建 FileTxnSnapLog。
    * LeaderElection,ZooKeeper 会根据 zoo.cfg 中的配置,创建相应的 Leader 选举算法实现。在 ZooKeeper 中,默认提供了三种 Leader 选举算法的实现,分别是 LeaderElection、AuthFastLeaderElection、FastLeaderElection,可以通过配置文件中 electionAlg 属性来指定,分别用 0 ~ 3 来表示。从 3.4.0 版本开始,ZooKeeper 废弃了前两种算法,只支持 FastLeaderEletion 选举算法。

    原文链接:http://rdc.hundsun.com/portal/article/952.html

    详解分布式协调服务 ZooKeeper

    JetLee 发表了文章 • 0 个评论 • 1424 次浏览 • 2018-11-24 17:30 • 来自相关话题

    zetcd:让应用解除对ZooKeeper的依赖

    hokingyang 发表了文章 • 0 个评论 • 5477 次浏览 • 2017-05-22 17:50 • 来自相关话题

    【编者的话】etcd团队最近发布了zetcd的beta版本,一款能够让etcd兼容Zookeeper协议的代理工具,用户可以通过它解除现有应用对Zookeeper服务的依赖。 分布式系统通常都依赖一个仲裁系统协同工作,一般这样的系统通 ...查看全部
    【编者的话】etcd团队最近发布了zetcd的beta版本,一款能够让etcd兼容Zookeeper协议的代理工具,用户可以通过它解除现有应用对Zookeeper服务的依赖。

    分布式系统通常都依赖一个仲裁系统协同工作,一般这样的系统通过仲裁来保证信息的准确传达,以避免出现脑裂。这类系统通过牺牲通用性换来了充分的设计余地,这种做法显然已经被不断扩散的各种具体实现所例证。这样的系统有很多,例如:chubbyZooKeeperetcdconsul等。尽管这些系统的理念和协议不同,但是提供的都是类似的基于key-value的分布式仲裁。作为将etcd打造成分布式系统最受瞩目的基础组件计划的一部分,etcd团队开发了一款全新的代理,zetcd,无需变动就可以让etcd集群消费ZooKeeper的服务请求。

    ZooKeeper是第一个开源实现的仲裁软件,这促使它成为众多分布式系统偏好的后端。理论上来说这些系统应该可以跟etcd兼容,但由于历史原因,事实并非如此;etcd集群无法替代ZooKeeper,其数据模型和客户端协议跟ZooKeeper应用不兼容;反之亦然。倘若该系统已经投产,那么几乎没什么动力可以推动它接入新的后端,引入新的复杂度。幸运的是,etcd v3 API正准备通过一个标准代理zetcd实现对ZooKeeper数据模型的模拟支持,zetcd是一个由etcd团队开发的全新开源项目,今天发布了zetcd第一个beta版本,v0.0.1,目标是在生产系统中管理和部署zetcd系统。

    zetcd 代理部署在etcd集群前面,服务于一个模拟的ZooKeeper客户端端口,使得ZooKeeper应用可以在上层原生调用etcd。总体上来说,`zetcd`接受ZooKeeper的客户请求,转化成etcd的数据模型和API,将请求转发到etcd,然后将返回信息以客户端可以理解的方式转发回去。zetcd的性能跟ZooKeeper不相上下,并且简化了ZooKeeper集群与etcd之间的管理和操作复杂性。本文将揭示如何使用zetcd,zetcd工作原理以及性能基准。
    #zetcd入门
    zetcd运行所需的是一个go编译器,从互联网上获取的源代码,以及一个可以运行etcd的系统。以下例子展示了从获取zetcd源码,一直到如何在zetcd上运行ZooKeeper命令。由于均是基于开发分支构建的etcd和zetcd,所以并不建议在生产环境这样做,这只是一个讲解如何使用的简单示例。

    首先,获得etcd和zetcd源码,并编译成二进制代码:​
    go get github.com/coreos/etcd/cmd/etcd 
    go get github.com/coreos/zetcd/cmd/zetcd

    其次,运行etcd,将zetcd连接到etcd客户服务端:
    #etcd uses localhost:2379 by default 
    etcd &
    zetcd -zkaddr localhost:2181 -endpoints localhost:2379 &​

    通过增加订阅和创建一个key来试用zetd:
    go install github.com/coreos/zetcd/cmd/zkctl 
    zkctl watch / &
    zkctl create /abc "foo"

    从概念上讲,上述例子即完成在一个单个的etcd实例上增加一层zetcd。
    01.png

    zetcd这层到底是做什么的呢?​
    #etcd3中对ZooKeeper的支持
    深入来讲,zetcd会将ZooKeeper的数据模型翻译成适配etcd API。对于key查找,zetcd将ZooKeeper层级式目录转换成etcd扁平的二分键值空间(flat binary keyspace)。为了管理元数据,当写入etcd后端时,zetcd利用内存级别的事务将信息安全、原子地更新为ZooKeeper znode信息。

    ZooKeeper以目录方式列出key(getChildren),而etcd则是通过间隔(Range)方式。下图讲解了zetcd如何对etcd下的key进行编码从而有效地支持以目录形式列出。所有在etcd中的zetcd key都有一个包括全目录名的前缀(例如:"/"和“/abc”分别代表深度为0 和1)。要列出一个目录时,zetcd发出一个带前缀的range请求(例如[“/zk/key/002/abc/”, “/zk/key/002/abc0”)来列出满足目录深度和路径的所有/abc/下的key。深度限制只针对目录本身;如果zetcd只使用路径而不使用深度,那么etcd将返回这个目录下的所有key,zetcd则会丢弃该结果,反之则只返回本目录下的key。
    02.png

    每个ZooKeeper key在ZNode里都带有一些元数据,即key的调整,版本和权限等。尽管etcd也有每个key的元数据,却比ZNode要简单很多,例如因为没有目录也就没有子版本,因为etcd使用的是基于角色认证的机制因此也就没有ACL,因为实际的时钟超出范畴所以没有时间戳。这些额外的元数据会被映射为对应的key,用来描述一个完整的ZNode。修改元数据时,zetcd利用内存级别的软事务来自动更新key的子集,确保ZNodes不需要昂贵的加锁机制就可以保持一致。

    此外,zetcd可以和一台授权的ZooKeeper服务器做动态校验。为了做比较,zetcd可以同时连到etcd和外部ZooKeeper服务器。当客户端发起请求给该模式下的zetcd时,请求会被同时转发到zetcd和ZooKeeper服务端。如果两个服务器响应的数据不一致,zetcd会给此响应标识一个警告。
    #性能基准
    由于数据的转换以及额外的网络开销,也许很容易觉得这样的模拟不切实际。尽管对于ZooKeeper或者etcd集群来说,zetcd有额外的花销,但是它仍然有一个优势,即某些etcd应用安装完毕后仍然需要ZooKeeper来协调的场景。例如,早期用户报告称在zetcd里通过etcd的TLS加密流量比一个类似的经典ZooKeeper配置更简单。在这些场景中,支持同一种ZooKeeper协议所带来的简单可靠性比性能更重要一些。

    跟etcd性能工具的接口及报告形式类似,zetcd命令行工具zkboom可以用来判断一个zetcd的性能基准是否满足要求。其它ZooKeeper性能工具应该也可以用在zetcd;zkboom为用户提供了便利,我们不妨试试用它来做下创建key的测试:
    go get github.com/coreos/zetcd/cmd/zkboom 
    zkboom --conns=50 --total=10000 --endpoints=localhost:2181 create​

    zetcd应当可以为小负载提供充分的性能保障。一个简单两节点配置的延迟基准表明zetcd是可以承受大量请求的。具体配置为两台Linux服务器通过一个千兆交换机互联,其中一台设备在RAID磁盘配置上运行代理和和服务端,另外一台设备用于产生客户请求。zkbook通过创建一个空的key存储,然后从中读取128Kbytes的键值对来进行测量。用户请求被限制在每秒2500个请求,然后逐渐增加并发客户端数量。ZooKeeper 3.4.10和etcd结果对比见下图。

    下图揭示了zetcd客户端并发数与创建key的平均延迟之间的关系。由于etcd在延迟上比zookeeper要有5-35ms的优势,zetcd有足够余地处理由于额外负载和网络跳转造成的延迟。比起ZooKeeper,zetcd代理始终还是存在20ms左右的差距,但是从处理2500请求的吞吐量数据来看,并没有出现队列堵塞。一种对zetcd写比较慢的解释是,与读不一样,由于数据模型上存在差异,所以在每个ZooKeeper key写时需要写多个key。
    03.png

    下图揭示了zetcd客户端并发数与key取值的平均延迟之间的关系。由于Zookeeper的取值延迟比etcd要快那么2ms左右,想要zetcd提供数据的速度快过Zookeeper的话恐怕还得依赖于etcd本身性能的进一步提升。然而,尽管zetcd需要从etcd请求额外的key来模拟Zookeeper znode的元数据,zetcd的命中延迟在等待etcd key提取数据方面只增加了大概1.5ms。zetcd在key的数据提取操作方面仅需一次往返,因为读请求会被打包到一个etcd事务中。
    04.png

    #未来
    zetcd承诺上述性能基准测试的结果是合理的,在可接受的延迟情况下,可以轻松支撑每秒上千个操作。以上模拟对于Mesos,Kafka和Drill替代ZooKeeper提供了一个替代选择。但是对于zetcd本身来说性能方面仍有不少的提升空间。与此同时测试更多的Zookeeper应用也会进一步推动zetcd成为Zookeeper服务器的替代品。

    zetcd从去年十月开始在开源社区发行,最近刚发布第一个版本:zetcd v0.0.1。尽管是第一个beta发行版,但是已经为未来生产系统提供稳定管理和部署。如果跟etcd配合起来使用,运行zetcd的系统将会一套自驱动的“ZooKeeper”集群,它可以自动后台升级,备份和TLS管理。如想了解更多,请参阅 https://github.com/coreos/zetcd/。

    原文链接:zetcd: running ZooKeeper apps without ZooKeeper(翻译:杨峰 审校:吴佳兴)

    服务发现:Zookeeper vs etcd vs Consul

    国会山上的猫TuxHu 发表了文章 • 6 个评论 • 99714 次浏览 • 2015-09-13 18:48 • 来自相关话题

    【编者的话】本文对比了Zookeeper、etcd和Consul三种服务发现工具,探讨了最佳的服务发现解决方案,仅供参考。 如果使用预定义的端口,服务越多,发生冲突的可能性越大,毕竟,不可能有两个服务监听同一个端口。管理一个拥挤的比方 ...查看全部
    【编者的话】本文对比了Zookeeper、etcd和Consul三种服务发现工具,探讨了最佳的服务发现解决方案,仅供参考。

    如果使用预定义的端口,服务越多,发生冲突的可能性越大,毕竟,不可能有两个服务监听同一个端口。管理一个拥挤的比方说被几百个服务所使用的所有端口的列表,本身就是一个挑战,添加到该列表后,这些服务需要的数据库和数量会日益增多。因此我们应该部署无需指定端口的服务,并且让Docker为我们分配一个随机的端口。唯一的问题是我们需要发现端口号,并且让别人知道。
    single-node-docker.png

    当我们开始在一个分布式系统上部署服务到其中一台服务器上时,事情会变得更加复杂,我们可以选择预先定义哪台服务器运行哪个服务的方式,但这会导致很多问题。我们应该尽我们所能尽量利用服务器资源,但是如果预先定义每个服务的部署位置,那么要实现尽量利用服务器资源是几乎不可能的。另一个问题是服务的自动伸缩将会非常困难,更不用说自动恢复了,比方说服务器故障。另一方面,如果我们将服务部署到某台只有最少数量的容器在运行的服务器上,我们需要添加IP地址到数据列表中,这些数据需要可以被发现并存储在某处。
    multi-node-docker.png

    当我们需要存储和发现一些与正在工作的服务相关的信息时,还有很多其他的例子。

    为了能够定位服务,我们需要至少接下来的两个有用的步骤。

    • 服务注册——该步骤存储的信息至少包括正在运行的服务的主机和端口信息
    • 服务发现——该步骤允许其他用户可以发现在服务注册阶段存储的信息。

    除了上述的步骤,我们还需要考虑其他方面。如果一个服务停止工作并部署/注册了一个新的服务实例,那么该服务是否应该注销呢?当有相同服务的多个副本时咋办?我们该如何做负载均衡呢?如果一个服务器宕机了咋办?所有这些问题都与注册和发现阶段紧密关联。现在,我们限定只在服务发现的范围里(常见的名字,围绕上述步骤)以及用于服务发现任务的工具,它们中的大多数采用了高可用的分布式键/值存储。
    #服务发现工具
    服务发现工具的主要目标是用来服务查找和相互对话,为此该工具需要知道每个服务,这不是一个新概念,在Docker之前就已经存在很多类似的工具了,然而,容器带给了这些工具一个全新水平的需求。

    服务发现背后的基本思想是对于服务的每一个新实例(或应用程序),能够识别当前环境和存储相关信息。存储的注册表信息本身通常采用键/值对的格式,由于服务发现经常用于分布式系统,所以要求这些信息可伸缩、支持容错和分布式集群中的所有节点。这种存储的主要用途是给所有感兴趣的各方提供最起码诸如服务IP地址和端口这样的信息,用于它们之间的相互通讯,这些数据还经常扩展到其它类型的信息服务发现工具倾向于提供某种形式的API,用于服务自身的注册以及服务信息的查找。

    比方说我们有两个服务,一个是提供方,另一个是第一个服务的消费者,一旦部署了服务提供方,就需要在服务发现注册表中存储其信息。接着,当消费者试图访问服务提供者时,它首先查询服务注册表,使用获取到的IP地址和端口来调用服务提供者。为了与注册表中的服务提供方的具体实现解耦,我们常常采用某种代理服务。这样消费者总是向固定IP地址的代理请求信息,代理再依次使用服务发现来查找服务提供方信息并重定向请求,在本文中我们稍后通过反向代理来实现。现在重要的是要理解基于三种角色(服务消费者、提供者和代理)的服务发现流程。

    服务发现工具要查找的是数据,至少我们应该能够找出服务在哪里?服务是否健康和可用?配置是什么样的?既然我们正在多台服务器上构建一个分布式系统,那么该工具需要足够健壮,保证其中一个节点的宕机不会危及数据,同时,每个节点应该有完全相同的数据副本,进一步地,我们希望能够以任何顺序启动服务、杀死服务或者替换服务的新版本,我们还应该能够重新配置服务并且查看到数据相应的变化。

    让我们看一下一些常用的选项来完成我们上面设定的目标。
    #手动配置
    大多数服务仍然是需要手动管理的,我们预先决定在何处部署服务、如何配置和希望不管什么原因,服务都将继续正常工作,直到天荒地老。这样的目标不是可以轻易达到的。部署第二个服务实例意味着我们需要启动全程的手动处理,我们需要引入一台新的服务器,或者找出哪一台服务器资源利用率较低,然后创建一个新的配置集并启动服务。情况或许会变得越来越复杂,比方说,硬件故障导致的手动管理下的反应时间变得很慢。可见性是另外一个痛点,我们知道什么是静态配置,毕竟是我们预先准备好的,然而,大多数的服务有很多动态生成的信息,这些信息不是轻易可见的,也没有一个单独的地方供我们在需要时参考这些数据。

    反应时间会不可避免的变慢,鉴于存在许多需要手动处理的移动组件,故障恢复和监控也会变得非常难以管理。

    尽管在过去或者当服务/服务器数量很少的时候有借口不做这项工作,随着服务发现工具的出现,这个借口已经不存在了。
    #Zookeeper
    Zookeeper是这种类型的项目中历史最悠久的之一,它起源于Hadoop,帮助在Hadoop集群中维护各种组件。它非常成熟、可靠,被许多大公司(YouTube、eBay、雅虎等)使用。其数据存储的格式类似于文件系统,如果运行在一个服务器集群中,Zookeper将跨所有节点共享配置状态,每个集群选举一个领袖,客户端可以连接到任何一台服务器获取数据。

    Zookeeper的主要优势是其成熟、健壮以及丰富的特性,然而,它也有自己的缺点,其中采用Java开发以及复杂性是罪魁祸首。尽管Java在许多方面非常伟大,然后对于这种类型的工作还是太沉重了,Zookeeper使用Java以及相当数量的依赖使其对于资源竞争非常饥渴。因为上述的这些问题,Zookeeper变得非常复杂,维护它需要比我们期望从这种类型的应用程序中获得的收益更多的知识。这部分地是由于丰富的特性反而将其从优势转变为累赘。应用程序的特性功能越多,就会有越大的可能性不需要这些特性,因此,我们最终将会为这些不需要的特性付出复杂度方面的代价。

    Zookeeper为其他项目相当大的改进铺平了道路,“大数据玩家“在使用它,因为没有更好的选择。今天,Zookeeper已经老态龙钟了,我们有了更好的选择。
    #etcd
    etcd是一个采用HTTP协议的健/值对存储系统,它是一个分布式和功能层次配置系统,可用于构建服务发现系统。其很容易部署、安装和使用,提供了可靠的数据持久化特性。它是安全的并且文档也十分齐全。

    etcd比Zookeeper是比更好的选择,因为它很简单,然而,它需要搭配一些第三方工具才可以提供服务发现功能。
    etcd.png

    现在,我们有一个地方来存储服务相关信息,我们还需要一个工具可以自动发送信息给etcd。但在这之后,为什么我们还需要手动把数据发送给etcd呢?即使我们希望手动将信息发送给etcd,我们通常情况下也不会知道是什么信息。记住这一点,服务可能会被部署到一台运行最少数量容器的服务器上,并且随机分配一个端口。理想情况下,这个工具应该监视所有节点上的Docker容器,并且每当有新容器运行或者现有的一个容器停止的时候更新etcd,其中的一个可以帮助我们达成目标的工具就是Registrator。
    #Registrator
    Registrator通过检查容器在线或者停止运行状态自动注册和去注册服务,它目前支持etcd、Consul和SkyDNS 2。

    Registrator与etcd是一个简单但是功能强大的组合,可以运行很多先进的技术。每当我们打开一个容器,所有数据将被存储在etcd并传播到集群中的所有节点。我们将决定什么信息是我们的。
    etcd-registrator.png

    上述的拼图游戏还缺少一块,我们需要一种方法来创建配置文件,与数据都存储在etcd,通过运行一些命令来创建这些配置文件。
    #Confd
    Confd是一个轻量级的配置管理工具,常见的用法是通过使用存储在etcd、consul和其他一些数据登记处的数据保持配置文件的最新状态,它也可以用来在配置文件改变时重新加载应用程序。换句话说,我们可以用存储在etcd(或者其他注册中心)的信息来重新配置所有服务。
    etcd-registrator-confd.png

    #对于etcd、Registrator和Confd组合的最后的思考
    当etcd、Registrator和Confd结合时,可以获得一个简单而强大的方法来自动化操作我们所有的服务发现和需要的配置。这个组合还展示了“小”工具正确组合的有效性,这三个小东西可以如我们所愿正好完成我们需要达到的目标,若范围稍微小一些,我们将无法完成我们面前的目标,而另一方面如果他们设计时考虑到更大的范围,我们将引入不必要的复杂性和服务器资源开销。

    在我们做出最后的判决之前,让我们看看另一个有相同目标的工具组合,毕竟,我们不应该满足于一些没有可替代方案的选择。
    #Consul
    Consul是强一致性的数据存储,使用gossip形成动态集群。它提供分级键/值存储方式,不仅可以存储数据,而且可以用于注册器件事各种任务,从发送数据改变通知到运行健康检查和自定义命令,具体如何取决于它们的输出。

    与Zookeeper和etcd不一样,Consul内嵌实现了服务发现系统,所以这样就不需要构建自己的系统或使用第三方系统。这一发现系统除了上述提到的特性之外,还包括节点健康检查和运行在其上的服务。

    Zookeeper和etcd只提供原始的键/值队存储,要求应用程序开发人员构建他们自己的系统提供服务发现功能。而Consul提供了一个内置的服务发现的框架。客户只需要注册服务并通过DNS或HTTP接口执行服务发现。其他两个工具需要一个亲手制作的解决方案或借助于第三方工具。

    Consul为多种数据中心提供了开箱即用的原生支持,其中的gossip系统不仅可以工作在同一集群内部的各个节点,而且还可以跨数据中心工作。
    consul1.png

    Consul还有另一个不错的区别于其他工具的功能,它不仅可以用来发现已部署的服务以及其驻留的节点信息,还通过HTTP请求、TTLs(time-to-live)和自定义命令提供了易于扩展的健康检查特性。
    #Registrator
    Registrator有两个Consul协议,其中consulkv协议产生类似于etcd协议的结果。

    除了通常的IP和端口存储在etcd或consulkv协议中之外,Registrator consul协议存储了更多的信息,我们可以得到服务运行节点的信息,以及服务ID和名称。我们也可以借助于一些额外的环境变量按照一定的标记存储额外的信息。
    consul-registrator1.png

    #Consul-template
    confd可以像和etce搭配一样用于Consul,不过Consul有自己的模板服务,其更适配Consul。

    通过从Consul获得的信息,Consul-template是一个非常方便的创建文件的途径,还有一个额外的好处就是在文件更新后可以运行任意命令,正如confd,Consul-template也可以使用Go模板格式。
    consul-registrator-consul-template1.png

    #Consul健康检查、Web界面和数据中心
    监控集群节点和服务的健康状态与测试和部署它们一样的重要。虽然我们应该向着拥有从来没有故障的稳定的环境努力,但我们也应该承认,随时会有意想不到的故障发生,时刻准备着采取相应的措施。例如我们可以监控内存使用情况,如果达到一定的阈值,那么迁移一些服务到集群中的另外一个节点,这将是在发生“灾难”前执行的一个预防措施。另一方面,并不是所有潜在的故障都可以被及时检测到并采取措施。单个服务可能会齿白,一个完整的节点也可能由于硬件故障而停止工作。在这种情况下我们应该准备尽快行动,例如一个节点替换为一个新的并迁移失败的服务。Consul有一个简单的、优雅的但功能强大的方式进行健康检查,当健康阀值达到一定数目时,帮助用户定义应该执行的操作。

    如果用户Google搜索“etcd ui”或者“etec dashboard”时,用户可能看到只有几个可用的解决方案,可能会问为什么我们还没有介绍给用户,这个原因很简单,etcd只是键/值对存储,仅此而已。通过一个UI呈现数据没有太多的用处,因为我们可以很容易地通过etcdctl获得这些数据。这并不意味着etcd UI是无用的,但鉴于其有限的使用范围,它不会产生多大影响。

    Consu不仅仅是一个简单的键/值对存储,正如我们已经看到的,除了存储简单的键/值对,它还有一个服务的概念以及所属的数据。它还可以执行健康检查,因此成为一个好的候选dashboard,在上面可以看到我们的节点的状态和运行的服务。最后,它支持了多数据中心的概念。所有这些特性的结合让我们从不同的角度看到引入dashboard的必要性。

    通过Consul Web界面,用户可以查看所有的服务和节点、监控健康检查状态以及通过切换数据中心读取设置键/值对数据。
    consul-nodes.png

    #对于Consul、Registrator、Template、健康检查和Web UI的最终思考
    Consul以及上述我们一起探讨的工具在很多情况下提供了比etcd更好的解决方案。这是从内心深处为了服务架构和发现而设计的方案,简单而强大。它提供了一个完整的同时不失简洁的解决方案,在许多情况下,这是最佳的服务发现以及满足健康检查需求的工具。
    #结论
    所有这些工具都是基于相似的原则和架构,它们在节点上运行,需要仲裁来运行,并且都是强一致性的,都提供某种形式的键/值对存储。

    Zookeeper是其中最老态龙钟的一个,使用年限显示出了其复杂性、资源利用和尽力达成的目标,它是为了与我们评估的其他工具所处的不同时代而设计的(即使它不是老得太多)。

    etcdRegistratorConfd是一个非常简单但非常强大的组合,可以解决大部分问题,如果不是全部满足服务发现需要的话。它还展示了我们可以通过组合非常简单和特定的工具来获得强大的服务发现能力,它们中的每一个都执行一个非常具体的任务,通过精心设计的API进行通讯,具备相对自治工作的能力,从架构和功能途径方面都是微服务方式。

    Consul的不同之处在于无需第三方工具就可以原生支持多数据中心和健康检查,这并不意味着使用第三方工具不好。实际上,在这篇博客里我们通过选择那些表现更佳同时不会引入不必要的功能的的工具,尽力组合不同的工具。使用正确的工具可以获得最好的结果。如果工具引入了工作不需要的特性,那么工作效率反而会下降,另一方面,如果工具没有提供工作所需要的特性也是没有用的。Consul很好地权衡了权重,用尽量少的东西很好的达成了目标。

    Consul使用gossip来传播集群信息的方式,使其比etcd更易于搭建,特别是对于大的数据中心。将存储数据作为服务的能力使其比etcd仅仅只有健/值对存储的特性更加完整、更有用(即使Consul也有该选项)。虽然我们可以在etcd中通过插入多个键来达成相同的目标,Consul的服务实现了一个更紧凑的结果,通常只需要一次查询就可以获得与服务相关的所有数据。除此之外,Registrator很好地实现了Consul的两个协议,使其合二为一,特别是添加Consul-template到了拼图中。Consul的Web UI更是锦上添花般地提供了服务和健康检查的可视化途径。

    我不能说Consul是一个明确的赢家,而是与etcd相比其有一个轻微的优势。服务发现作为一个概念,以及作为工具都很新,我们可以期待在这一领域会有许多的变化。秉承开放的心态,大家可以对本文的建议持保留态度,尝试不同的工具然后做出自己的结论。

    原文链接:Service Discovery: Zookeeper vs etcd vs Consul(翻译:胡震)

    =================================================================

    译者介绍: 胡震, 曾任互联网金融创业公司首席架构师&CTO,现在平安金融科技中心架构组负责技术管理和架构设计工作。

    如何构建基于容器的本机监控系统

    hokingyang 发表了文章 • 2 个评论 • 6180 次浏览 • 2015-09-08 09:44 • 来自相关话题

    【作者的话】Docker 目前非常火爆,如何把Docker使用起来,并且和日常工作结合起来,是需要考虑的一个问题。 本文意图将一个测试的具体步骤展示给大家,可以在一台内存较大的台式机上进行(建议16GB内存),另外本文的参考意义更 ...查看全部
    【作者的话】Docker 目前非常火爆,如何把Docker使用起来,并且和日常工作结合起来,是需要考虑的一个问题。

    本文意图将一个测试的具体步骤展示给大家,可以在一台内存较大的台式机上进行(建议16GB内存),另外本文的参考意义更加在于尝试和使用,对于实际场景的意义则需要方家讨论。

    测试拓扑图如下所示:

    topo.png


    各个功能Docker模块功能如下:

    • Flume:负责搜集日志信息(本文中启动了三个flume容器)
    - 第一个负责从本机搜集/var/log/messages日志,直接发送到elasticsearch中
    - 第二个负责从本机搜集/var/log/messages日志,发送到kafka中间件,读取日志序列,发送到elasticsearch
    - 第三个负责从kafka读取日志序列,发送到elasticsearch。
    - 还有一个没有实现的可能性,从kafka读取日志序列,写入HDFS,以便后续进行hadoop分析
    • docker ps的输出则是真正运行在CentOS7中的容器集合,共同完成以上任务。

    #Config docker hub repository accelerator to Daocloud
    http://dashboard.daocloud.io/mirror
    For CentOS:

    - sudo sed -i 's|other_args=|other_args=--registry-mirror=http://4c5cf935.m.daocloud.io |g' /etc/sysconfig/docker
    - sudo sed -i "s|OPTIONS='|OPTIONS='--registry-mirror=http://4c5cf935.m.daocloud.io |g" /etc/sysconfig/docker
    - sudo service docker restart

    #Install docker


    - https://docs.docker.com/installation/centos
    - yum –y update = 3.10.0-229.el7.x86_64>
    - crul –sSL https://get.docker/com/ | sh
    - this script adds the ‘docker.repo’ repository and installs Docker
    - yum –y install docker-selinux
    - systemctl start docker.service

    #Install docker-compose


    - https://docs.docker.com/compose/install
    - curl -L https://github.com/docker/compose/releases/download/1.3.3/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
    - chmod +x /usr/local/bin/docker-compose
    docker-compose kafka
    - https://github.com/wurstmeister/kafka-docker
    - under the kafka-docker-master directory:
    - modify the KAFKA_ADVERTISED_HOST_NAME in docker-compose.yml to match your docker host IP (Note: Do not use localhost or 127.0.0.1 as the host ip if you want to run multiple brokers.)
    - start a cluster : #docker-compute up –d
    - Add user broker: docker-compose scale kafka=2<-(no less than replication factor below)
    - Destroy a cluster: docker-compose stop
    - Monitor the logs: docker-compose logs
    - To see the containers IP and ports:
    - Systemctl status docker.service
    - ./start-kafka-shell
    - # $KAFKA_HOME/bin/kafka-topics.sh –create –topic topic –partitions 4 –zookeeper $ZK –replication-factor 2←(must equal to kafka broker’s #)
    - # $KAFKA_HOME/bin/kafka-topics.sh –list –zookeeper $ZK
    - # $KAFKA_HOME/bin/kafka-topics.sh –describe –topic topic –zookeeper $ZK
    - # $KAFKA_HOME/bin/kafka-console-producer.sh –topic=topic –broker-list=`broker-list.sh`
    - ./start-kafka-shell
    - # $KAFKA_HOME/bin/kafka-console-consumer.sh –topic=topic –zookeeper=$ZK –from-beginning
    - troubleshooting: http://wurstmeister.github.io/kafka-docker/

    #配置elasticsearch


    - docker pull elasticsearch:latest
    - mkdir /mnt/isilon
    - mount isilon.mini:/ifs/hdfs /mnt/isilon
    - docker run –d –p 9200:9200 –p 9300:9300 –v /mnt/isilon/elasticsearch:/data –v /mnt/isilon/elasticsearch/conf/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml elasticsearch
    - <会将以上目录和文件挂载到container内部;>
    - 起作用的配置文件在:/usr/share/elasticsearch/config目录下
    - 一台物理机,只能启动一个elasticsearch容器
    - systemctl status docker.service

    - http://:9200 or http://:9200
    - docker exec –it /bin/bash
    - cd /usr/share/elasticsearch/plugins
    - /usr/share/elasticsearch/bin/plugin –install mobz/elasticsearch-head
    - /usr/share/elasticsearch/bin/plugin –install lukas-vlcek/bigdesk
    - http://:9200/_plugin/bigdesk
    - http://:9200/_plugin/head
    - docker exec –it /bin/bash
    - cp –r /usr/share/elasticsearch/lib/* /data/lib
    - used for later flume library to access elasticsearch

    #配置kibana


    - docker pull kibana
    - docker run - -link -d kibana
    - 默认方式,5601端口只在container内部可用
    - docker run - -link -d kibana - -plugins /somewhere/else
    - 可以传递某些参数
    - docker run - -name kibana - -link -p 5601:5601 –d kibana
    - 对外输出5601端口,可以通过主机IP访问,但是有可能对elasticsearch提供服务的主机名localhost解析不了,造成问题。建议用下一种方式
    - docker run - -name kibana –e http://:9200 -p 5601:5601 –d kibana
    - netstat –tupln | grep 5601
    - docker logs
    - http://:5601

    #配置flume-----监控文件日志输出到elasticsearch


    - docker pull probablyfine/flume <最新flume-ng为1.6.0版本>
    - cat /mnt/isilon/config/flume_log2es.conf
    - refer to 《配置ELK》文章中,配置flume-ng一节,第12A步配置
    - docker run -e FLUME_AGENT_NAME=log2es -v /mnt/isilon:/data –v /var/log/messages:/var/log/messages -e FLUME_CONF_FILE=/data/config/flume_log2es.conf -d probablyfine/flume
    - 为调试起见将host机器/var/log/messages挂入flume容器中
    - flume_log2es.conf中监控/var/log/messages的变化,可以修改为容器内任何感兴趣的日志
    - docker exec –it /bin/bash
    - cp –r /data/elasticsearch/lib/* /opt/flume/lib
    - copy previous elasticsearch lib files to flume lib
    - docker logs
    - docker stop
    - docker start 即可,再用logs命令看状态

    #配置flume-----监控日志文件输出到kafka


    - docker pull probablyfine/flume
    - yum –y install maven
    - to create flume-ng-kafka library
    - https://github.com/jinoos/flume-ng-extends, download the zip file
    - unzip flume-ng-extends-source-master.zip
    - cd flume-ng-extends-source-master
    - mvn clean packages
    - mkdir –p /mnt/isilon/kafka/lib
    - cp target/flume-ng-extends-source-0.8.0.jar /mnt/isilon/kafka/lib
    - cat /mnt/isilon/config/flume_kafka_producer.conf
    - refer to 《配置ELK》文章中,配置flume-ng一节,第12B步配置
    - docker run -e FLUME_AGENT_NAME=kfk_pro -v /mnt/isilon:/data -v /var/log/messages:/var/log/messages -e FLUME_CONF_FILE=/data/config/flume_kafka_producer.conf -d probablyfine/flume
    - docker exec –it /bin/bash
    - cp –r /data/elasticsearch/lib/* /opt/flume/lib
    - cp –r /data/kafka/lib/* /opt/flume/lib
    - docker stop
    - docker start
    - docker logs
    - refer to “docker-compose kafka” about topic list and consumer command
    - cat /mnt/isilon/config/flume_kafka_consumer.conf
    - refer to 《配置ELK》文章中,配置flume-ng一节,第12C步配置
    - docker run -e FLUME_AGENT_NAME=kfk_con -v /mnt/isilon:/data -e FLUME_CONF_FILE=/data/config/flume_kafka_consumer.conf -d probablyfine/flume
    - docker exec –it /bin/bash
    - cp –r /data/elasticsearch/lib/* /opt/flume/lib
    - cp –r /data/kafka/lib/* /opt/flume/lib
    - docker stop
    - docker start
    - docker logs
    - refer to “docker-compose kafka” about topic list and consumer command

    #配置kibana

    kibana.png

    #Bigdesk插件输出情况

    bigdesk.png

    logstash日志为12A配置的flume生成。
    es-index日志为12B/C配置的flume生成(via kafka)
    es-index.png

    通过kibana接收到的基于时序的日志信息。
    #整个系统状况

    dockerps.png

    停止所有docker:

    - docker ps | grep ^[0-9] | awk ‘{print $1}’ | xargs –t –I docker stop {}
    启动所有docker:
    - docker ps -a| grep ^[0-9] | awk ‘{print $1}’ | xargs –t –I docker stop {}

    尝鲜 | 在Docker中运行你的Mesos集群

    unodba 发表了文章 • 2 个评论 • 5173 次浏览 • 2015-09-04 12:57 • 来自相关话题

    【编者的话】本文介绍了在Docker中运行Apache Mesos,Mesos官方网站提供了安装包来直接运行它,但文中尝试使用Docker来运行Mesos,Mesosphere也提供了相应的镜像。在Docker中运行Mesos也有它的好处,读者可以尝试下。 ...查看全部
    【编者的话】本文介绍了在Docker中运行Apache Mesos,Mesos官方网站提供了安装包来直接运行它,但文中尝试使用Docker来运行Mesos,Mesosphere也提供了相应的镜像。在Docker中运行Mesos也有它的好处,读者可以尝试下。

    很多人都已经开始在Apache Mesos中运行容器化的应用,我们也是这样。尽管我们尝试在容器中运行应用,但我们还是通过传统的安装包来在自己的主机上安装Mesos。尽管这是最简单和直接的方式来安装Mesos,大部分也都这样做。但不知道你是否想过,在容器中安装和运行Mesos?

    在容器中安装Mesos可以帮助我们解决一个非常实际的问题,我们可以在任意版本的主机系统上运行Mesos和它的框架,包括各种测试版本。因为基于容器,问题就转化为只需要在主机上运行不同的Docker镜像。
    #Mesos、 Marathon和Zookeeper的Docker镜像
    目前在Docker Hub上有几个不同的Apache Mesos Docker镜像,(注意Mesos master 和 slave是两个镜像)。我们选择Mesosphere的镜像。Redjack的仓库同样提供高质量、文档丰富的Mesos镜像。使用哪个取决于你,哪个都可以正常运行,不过需要注意配置可能有些不同。注意这篇文章中使用的是Mesosphere提供的镜像。

    上周Thijs Schnitger 创建了一个ZooKeeper 3.5的镜像。Apache ZooKeeper在这个版本引入了动态主机重配置,一个非常酷的特性,特别是在一个经常变化的环境中特别有用,集群也是。所以我们也使用ZooKeeper容器化的解决方案。

    我们还会用到Mesosphere的Marathon Docker 镜像
    #容器化集群配置
    我们使用Terraform启动集群。参考terraform-mesos的GitHub仓库并下载它的容器分支,你会找到解决方案(至少在写这篇文章时)。不久,它会被合并到master分支。

    让我们来看一下配置过程的有趣部分,通过Docker来运行Mesos、Marathon和Zookeeper。
    #Mesos 主节点
    QUORUM=2 # number of masters divided by 2 plus 1
    CLUSTERNAME="cluster7"
    ZK="zk:///mesos"
    MESOS_VERSION="0.22.1-1.0.ubuntu1404"

    docker run -d \
    -e MESOS_QUORUM=${QUORUM} \
    -e MESOS_WORK_DIR=/var/lib/mesos \
    -e MESOS_LOG_DIR=/var/log \
    -e MESOS_CLUSTER=${CLUSTERNAME} \
    -e MESOS_ZK=${ZK}/mesos \
    --net="host" \
    redjack/mesos-master:${MESOSVERSION}
    --mesosphere/mesos-master:${MESOSVERSION}

    如你所见,我们仅仅传递给镜像几个相关的版本tag就能运行指定版本的Mesos。除此之外,为了Mesos正常工作还需要传递几个环境变量给容器并启用host networking

    简而言之,带`MESOS_`前缀的变量保存配置的值,我们一般会写到主机`/etc/mesos*`目录下的配置文件。

    在集群中的每台主机上运行主节点容器。
    #Mesos从节点
    ZK="zk:///mesos"
    HOSTNAME="host1" # use a hostname of the host
    IP="10.20.30.2" # use an IP address of the host
    MESOSVERSION="0.22.1-1.0.ubuntu1404"

    docker run -d \
    -e MESOS_LOG_DIR=/var/log/mesos \
    -e MESOS_MASTER=${ZK} \
    -e MESOS_EXECUTOR_REGISTRATION_TIMEOUT=5mins \
    -e MESOS_HOSTNAME=${HOSTNAME} \
    -e MESOS_ISOLATOR=cgroups/cpu,cgroups/mem \
    -e MESOS_CONTAINERIZERS=docker,mesos \
    -e MESOS_PORT=5051 \
    -e MESOS_IP=${IP} \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -v /usr/bin/docker:/usr/bin/docker \
    -v /sys:/sys:ro \
    --net="host" \
    redjack/mesos-slave
    --mesosphere/mesos-slave:${MESOSVERSION}

    对于Mesos从节点,配置有点复杂,主要是需要确保从节点可以访问主机上的Docker后台进程(同一个后台进程用来运行这个Mesos从节点实例)。通过挂载`/var/run/docker.sock`文件,`/usr/bin/docker`执行文件和`/sys` 目录(只读)到Mesos从节点容器实现。请注意,这还不是一个完美的解决方案,例如当从Marathon运行另一个Mesos框架,需要再进行细微调整。
    #Zookeeper
     # 第一个节点
    docker run -d -p 2181:2181 containersol/zookeeper 1

    # 其它节点
    MYID="2" # 3,4,5,...
    FIRST_NODE="cluster7-mesos-master-0" # hostname of the first node

    docker run -d -p 2181:2181 containersol/zookeeper ${MYID} ${FIRST_NODE}
    --需要更改端口2888:2888,3888:3888

    在这个配置中,运行容器化的ZooKeeper需要一点小技巧。在第一个节点以“standalone” 模式运行,等其它所有节点ZooKeeper实例能连接到它并运行正常,ZooKeeper接着会自动重新配置它自己向所有节点同步。
    #Marathon
    因为我们想运行应用容器和其它框架,我们在Mesos之上安装“datacenter init system” Marathon框架。
    MARATHONVERSION="v0.8.2"
    ZK="zk://" # zookeeper quorum string

    docker run -d \
    -p 8080:8080 \
    -p 5051:5051 \
    mesosphere/marathon:${MARATHONVERSION} \
    --master ${ZK}/mesos \
    --zk ${ZK}/marathon

    注意,我们指定Marathon UI 运行在8080端口,监听5051端口。我们运行任意版本镜像并传递两个强制参数:

    `master` – quorum hostname/ip and Mesos registration path
    `zk` – quorum hostname/ip and Marathon registration path

    这样就可以在Docker容器中运行Mesos集群的关键组件了。除了运行不同版本的组件,这个我们最大的动机,这种方式自然而然的带来在集群管理层次容器化的所有好处:快速部署,维护简单和设备可移植性。

    在这一过程,我们还需要解决haproxy-marathon bridge和Mesos DNS配置的问题。

    如果你有任何问题或者要进行评论,请直接在文章下面留言或者在 GitHub上创建issues

    原文链接:Containerized Mesos Cluster (翻译:朱高校)

    Mesos+ZooKeeper+Marathon+Docker分布式部署打造PaaS云平台实践(一)

    赵英俊 发表了文章 • 5 个评论 • 20386 次浏览 • 2015-07-21 16:39 • 来自相关话题

    【编者的话】本文先给出一个分布式部署的过程,在完成这种分布式部署的过程花费了我一个周末的时间,因为国内几乎没有找到分布式部署的实践过程记录,希望我的实践过程能够给有兴趣的小伙伴在进行分布式部署中提供一定的帮助。 最近开始对Mesos非 ...查看全部
    【编者的话】本文先给出一个分布式部署的过程,在完成这种分布式部署的过程花费了我一个周末的时间,因为国内几乎没有找到分布式部署的实践过程记录,希望我的实践过程能够给有兴趣的小伙伴在进行分布式部署中提供一定的帮助。

    最近开始对Mesos非常的感兴趣,Mesos和Docker一样是一个使用Go语言编写的新兴且具有活力的项目,因为最近痴迷学习Go语言,所以对使用Go语言编写的项目都比较看好。本文先给出一个分布式部署的过程,在完成这种分布式部署的过程花费了我一个周末的时间,因为国内几乎没有找到分布式部署的实践过程记录,希望我的实践过程能够给有兴趣的小伙伴在进行分布式部署中提供一定的帮助。之后我会在本系列博文的后半部分综合阐述一下当前Docker的一些应用场景,以及未来基于Mesos+ZooKeeper+Marathon+Docker分布式部署打造PaaS云平台的可行性。
    #本文中用到的项目组件介绍(全部摘自网络)
    Mesos:Mesos采用与Linux Kernel相同的机制,只是运行在不同的抽象层次上。Mesos Kernel利用资源管理和调度的API在整个数据中心或云环境中运行和提供引用(例如,Hadoop、Spark、Kafaka、Elastic Search)。

    ZooKeeper:ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和HBase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、名字服务、分布式同步、组服务等。

    Marathon:Marathon是一个Mesos框架,能够支持运行长服务,比如Web应用等。它是集群的分布式Init.d,能够原样运行任何Linux二进制发布版本,如Tomcat、Play等等。它也是一种私有的PaSS,实现服务的发现,为部署提供提供REST API服务,有授权和SSL、配置约束,通过HAProxy实现服务发现和负载平衡。

    Docker:Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。
    #Mesos+ZooKeeper+Marathon+Docker分布式部署过程记录
    Mesos为了管理的简单也采用了master-slave的架构,因此我本次使用了6个Ubuntu 14.04的虚拟机作为部署节点。其中3个mastser节点,3个slave节点。为直观的理解,我简单的画了一张架构图:
    3AD0EBE4-D449-4E1F-B5FA-AB8CDC8F265B.png

    如图所示其中master节点都需要运行ZooKeeper、Mesos-master、Marathon,在slave节点上只需要运行master-slave就可以了,但是需要修改ZooKeeper的内容来保证slave能够被master发现和管理。为了节约时间和搞错掉,我在公司内部云平台上开一个虚拟机把所有的软件都安装上去,做成快照进行批量的创建,这样只需要在slave节点上关闭ZooKeeper、Mesos-master服务器就可以了,在文中我是通过制定系统启动规则来实现的。希望我交代清楚了,现在开始部署。
    ##一、准备部署环境

    * 在Ubuntu 14.04的虚拟机上安装所有用到软件,虚拟机可以上互联网。

    * 安装Python依赖
    apt-get install curl python-setuptools python-pip python-dev python-protobuf


    * 安装配置ZooKeeper
    apt-get install ZooKeeperd
    echo 1 | sudo dd of=/var/lib/ZooKeeper/myid


    * 安装Docker
    echo deb http://get.Docker.io/ubuntu Docker main | sudo tee /etc/apt/sources.list.d/Docker.list
    sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9
    apt-get update && apt-get install lxc-Docker


    * 安装配置Mesos-master和Mesos-slave
     curl -fL http://downloads.Mesosphere.io/master/ubuntu/14.04/Mesos_0.19.0~ubuntu14.04%2B1_amd64.deb -o /tmp/Mesos.deb 
    dpkg -i /tmp/Mesos.deb
    mkdir -p /etc/Mesos-master
    echo in_memory | sudo dd of=/etc/Mesos-master/registry


    * 安装配置Mesos的Python框架
     curl -fL http://downloads.Mesosphere.io/master/ubuntu/14.04/Mesos-0.19.0_rc2-py2.7-linux-x86_64.egg -o /tmp/Mesos.egg
    easy_install /tmp/Mesos.egg


    * 下载Marathon
    curl -O http://downloads.Mesosphere.io/marathon/marathon-0.6.0/marathon-0.6.0.tgz  
    tar xvzf marathon-0.6.0.tgz


    * 下载安装Mesos管理Docker的代理组件Deimos
     pip install deimos


    * 配置Mesos使用Deimos
    mkdir -p /etc/mesos-slave
    echo /usr/local/bin/deimos | sudo dd of=/etc/Mesos-slave/containerizer_path
    echo external | sudo dd of=/etc/Mesos-slave/isolation


    好,至此在一个虚拟机上就完成了所有组件的安装部署,下面就是对虚拟机打快照,然后快速的复制出6个一样的虚拟机,按照上图的ip配置进行配置之后就可以进入下个阶段,当然为了保险你可以测试一下上处组件是否安装成功和配置正确。如果你没有使用云平台,或者不具备快照功能,那就只能在6个虚拟机上重复6遍上处过程了。
    ##二、在所有的节点上配置ZooKeeper
    在配置maser节点和slave节点之前,需要先在所有的6个节点上配置一下ZooKeeper,配置步骤如下:

    • 修改zk的内容
    sudo vi /etc/Mesos/zk} 
    • 将zk的内容修改为如下:
    zk://10.162..2.91:2181,10.162.2.92:2181,10.162.2.93:2181/Mesos
    ##三、 配置所有的master节点在所有的master节点上都要进行如下操作:
    • 修改ZooKeeper的myid的内容
    sudo vi /etc/ZooKeeper/conf/myid
    将三个master节点的myid按照顺序修改为1,2,3。
    • 修改ZooKeeper的zoo.cfg
    sudo vi/etc/ZooKeeper/conf/zoo.cfg
    server.1=10.162.2.91:2888:3888server.2=10.162.2.92:2888:3888server.3=10.162.2.93:2888:3888
    • 修改Mesos的quorum
    sudo vi /etc/Mesos-master/quorum
    将值修改为2。
    • 配置master节点的Mesos 识别ip和和hostname(以在master1上的配置为例)
    echo 10.162.2.91 | sudo tee /etc/Mesos-master/ip 
    sudo cp /etc/Mesos-master/ip /etc/Mesos-master/hostname
    • 配置master节点服务启动规则
    sudo stop Mesos-slaveecho manual | sudo tee /etc/init/Mesos-slave.override
    四、配置所有的slave节点
    • 配置slave节点的服务启动规则
    sudo stop ZooKeeperecho manual | sudo tee /etc/init/ZooKeeper.overrideecho manual | sudo tee /etc/init/Mesos-master.overridesudo stop Mesos-master
    • 配置slave节点的识别ip和hostname(以slave1节点为例)
    echo 192.168.2.94 | sudo tee /etc/Mesos-slave/ipsudo cp /etc/Mesos-slave/ip /etc/Mesos-slave/hostname
    五、在所有节点上启动服务
    • 在master节点上启动服务(以在master1节点上为例)
    initctl reload-configuration service Docker start service Zookeeper start service Mesos-master start service Mesos-slave start cd marathon-0.6.0 ./bin/start --master zk://10.162.2.91:2181,10.162.2.92:2181,10.162.2.93:2181/Mesos --zk_hosts 10.162.2.91:2181
    • 在所有slave节点上启动服务
    sudo start mesos-slave

    好,至此,如果配置没有出现错误的话就会成功了,由于有的网络情况和设备情况不一样,所以选举的过程有的快有的慢,当发现slave节点有些正常有些不正常时,可以通过reboot来促使自己被master发现。

    六、Marathon简单的使用
    要使用Marathon进行应用的部署,需要先在slave节点上有Docker镜像,这个可以自己写Dockerfile来做,也可以直接从公共仓库里pull下来一个。Marathon的应用部署和k8s一样,使用通过JSON文件来进行的,以下就是一个jason文件的范例:
    curl -X POST -H "Accept: application/json" -H "Content-Type: application/json" \
    10.162.2.91:8080/v2/apps -d '{
    "container": {"image": "docker:///ubuntu", "options": ["--privileged"]},
    "cpus": 0.5,
    "cmd": "ok",
    "id": "cci-docker",
    "instances": 2,
    "mem": 30
    }'


    七、成功部署的结果是这样的
    1393540D-028A-49CA-B4BE-D8C515C88A10.png

    3677CB31-F1DF-430D-BC6A-82A576C0DFE8.png

    176B3875-B5F6-42BC-9EF4-A49EBE835184.png

    07CDB99D-51D6-4D5C-8B15-8151EE32F155.png

    4D7C8BAF-FC99-4BCD-88E5-6CD5EEFEF85E.png

    05BD7E57-90A1-4891-8AC3-4D179B6DF248.png

    B8434C3A-DDCE-4562-90A2-31BF8D31CB99.png

    关于对Docker应用场景的总结以及这种基于Docker的PaaS平台未来的发展前景,我会在后面的博文中进行讨论分析。

    赵英俊 2015年7月21日 于 杭州 城云科技

    k8s搭建zookeeper 失败,

    回复

    lincoln_alex 回复了问题 • 1 人关注 • 1 个回复 • 643 次浏览 • 2019-04-22 11:02 • 来自相关话题

    宿主机无法访问容器内的zookeeper服务

    回复

    halbart 回复了问题 • 2 人关注 • 1 个回复 • 3939 次浏览 • 2017-06-01 09:44 • 来自相关话题

    marathon不能启动docker容器

    回复

    ppt0501 回复了问题 • 1 人关注 • 2 个回复 • 4206 次浏览 • 2016-05-12 11:17 • 来自相关话题

    etcd是什么东西?它和ZooKeeper有什么区别?

    回复

    云上独思考 回复了问题 • 12 人关注 • 5 个回复 • 51128 次浏览 • 2015-04-13 21:16 • 来自相关话题

    服务端高并发分布式架构演进之路

    翔宇 发表了文章 • 0 个评论 • 215 次浏览 • 2019-06-04 11:16 • 来自相关话题

    【编者的话】本文以淘宝作为例子,介绍从一百个并发到千万级并发情况下服务端的架构的演进过程,同时列举出每个演进阶段会遇到的相关技术,让大家对架构的演进有一个整体的认知,文章最后汇总了一些架构设计的原则。 #基本概念 在介绍架构之前,为 ...查看全部
    【编者的话】本文以淘宝作为例子,介绍从一百个并发到千万级并发情况下服务端的架构的演进过程,同时列举出每个演进阶段会遇到的相关技术,让大家对架构的演进有一个整体的认知,文章最后汇总了一些架构设计的原则。
    #基本概念

    在介绍架构之前,为了避免部分读者对架构设计中的一些概念不了解,下面对几个最基础的概念进行介绍:
    ##分布式

    系统中的多个模块在不同服务器上部署,即可称为分布式系统,如Tomcat和数据库分别部署在不同的服务器上,或两个相同功能的Tomcat分别部署在不同服务器上。
    ##高可用

    系统中部分节点失效时,其他节点能够接替它继续提供服务,则可认为系统具有高可用性。
    ##集群

    一个特定领域的软件部署在多台服务器上并作为一个整体提供一类服务,这个整体称为集群。如Zookeeper中的Master和Slave分别部署在多台服务器上,共同组成一个整体提供集中配置服务。在常见的集群中,客户端往往能够连接任意一个节点获得服务,并且当集群中一个节点掉线时,其他节点往往能够自动的接替它继续提供服务,这时候说明集群具有高可用性。
    ##负载均衡

    请求发送到系统时,通过某些方式把请求均匀分发到多个节点上,使系统中每个节点能够均匀的处理请求负载,则可认为系统是负载均衡的。
    ##正向代理和反向代理

    系统内部要访问外部网络时,统一通过一个代理服务器把请求转发出去,在外部网络看来就是代理服务器发起的访问,此时代理服务器实现的是正向代理;当外部请求进入系统时,代理服务器把该请求转发到系统中的某台服务器上,对外部请求来说,与之交互的只有代理服务器,此时代理服务器实现的是反向代理。简单来说,正向代理是代理服务器代替系统内部来访问外部网络的过程,反向代理是外部请求访问系统时通过代理服务器转发到内部服务器的过程。
    #架构演进

    ##单机架构

    1.png

    以淘宝作为例子。在网站最初时,应用数量与用户数都较少,可以把Tomcat和数据库部署在同一台服务器上。浏览器往www.taobao.com发起请求时,首先经过DNS服务器(域名系统)把域名转换为实际IP地址10.102.4.1,浏览器转而访问该IP对应的Tomcat。如果你想和更多Tomcat技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

    随着用户数的增长,Tomcat和数据库之间竞争资源,单机性能不足以支撑业务
    ##第一次演进:Tomcat与数据库分开部署

    2.png

    Tomcat和数据库分别独占服务器资源,显著提高两者各自性能。

    随着用户数的增长,并发读写数据库成为瓶颈
    ##第二次演进:引入本地缓存和分布式缓存

    3.png

    在Tomcat同服务器上或同JVM中增加本地缓存,并在外部增加分布式缓存,缓存热门商品信息或热门商品的html页面等。通过缓存能把绝大多数请求在读写数据库前拦截掉,大大降低数据库压力。其中涉及的技术包括:使用memcached作为本地缓存,使用Redis作为分布式缓存,还会涉及缓存一致性、缓存穿透/击穿、缓存雪崩、热点数据集中失效等问题。

    缓存抗住了大部分的访问请求,随着用户数的增长,并发压力主要落在单机的Tomcat上,响应逐渐变慢
    ##第三次演进:引入反向代理实现负载均衡

    4.png

    在多台服务器上分别部署Tomcat,使用反向代理软件(Nginx)把请求均匀分发到每个Tomcat中。此处假设Tomcat最多支持100个并发,Nginx最多支持50000个并发,那么理论上Nginx把请求分发到500个Tomcat上,就能抗住50000个并发。其中涉及的技术包括:Nginx、HAProxy,两者都是工作在网络第七层的反向代理软件,主要支持http协议,还会涉及session共享、文件上传下载的问题。

    反向代理使应用服务器可支持的并发量大大增加,但并发量的增长也意味着更多请求穿透到数据库,单机的数据库最终成为瓶颈
    ##第四次演进:数据库读写分离

    5.png

    把数据库划分为读库和写库,读库可以有多个,通过同步机制把写库的数据同步到读库,对于需要查询最新写入数据场景,可通过在缓存中多写一份,通过缓存获得最新数据。其中涉及的技术包括:Mycat,它是数据库中间件,可通过它来组织数据库的分离读写和分库分表,客户端通过它来访问下层数据库,还会涉及数据同步,数据一致性的问题。

    业务逐渐变多,不同业务之间的访问量差距较大,不同业务直接竞争数据库,相互影响性能
    ##第五次演进:数据库按业务分库

    6.png

    把不同业务的数据保存到不同的数据库中,使业务之间的资源竞争降低,对于访问量大的业务,可以部署更多的服务器来支撑。这样同时导致跨业务的表无法直接做关联分析,需要通过其他途径来解决,但这不是本文讨论的重点,有兴趣的可以自行搜索解决方案。

    随着用户数的增长,单机的写库会逐渐会达到性能瓶颈
    ##第六次演进:把大表拆分为小表

    7.png

    比如针对评论数据,可按照商品ID进行hash,路由到对应的表中存储;针对支付记录,可按照小时创建表,每个小时表继续拆分为小表,使用用户ID或记录编号来路由数据。只要实时操作的表数据量足够小,请求能够足够均匀的分发到多台服务器上的小表,那数据库就能通过水平扩展的方式来提高性能。其中前面提到的Mycat也支持在大表拆分为小表情况下的访问控制。

    这种做法显著的增加了数据库运维的难度,对DBA的要求较高。数据库设计到这种结构时,已经可以称为分布式数据库,但是这只是一个逻辑的数据库整体,数据库里不同的组成部分是由不同的组件单独来实现的,如分库分表的管理和请求分发,由Mycat实现,SQL的解析由单机的数据库实现,读写分离可能由网关和消息队列来实现,查询结果的汇总可能由数据库接口层来实现等等,这种架构其实是MPP(大规模并行处理)架构的一类实现。

    目前开源和商用都已经有不少MPP数据库,开源中比较流行的有Greenplum、TiDB、Postgresql XC、HAWQ等,商用的如南大通用的GBase、睿帆科技的雪球DB、华为的LibrA等等,不同的MPP数据库的侧重点也不一样,如TiDB更侧重于分布式OLTP场景,Greenplum更侧重于分布式OLAP场景,这些MPP数据库基本都提供了类似Postgresql、Oracle、MySQL那样的SQL标准支持能力,能把一个查询解析为分布式的执行计划分发到每台机器上并行执行,最终由数据库本身汇总数据进行返回,也提供了诸如权限管理、分库分表、事务、数据副本等能力,并且大多能够支持100个节点以上的集群,大大降低了数据库运维的成本,并且使数据库也能够实现水平扩展。

    数据库和Tomcat都能够水平扩展,可支撑的并发大幅提高,随着用户数的增长,最终单机的Nginx会成为瓶颈
    ##第七次演进:使用LVS或F5来使多个Nginx负载均衡

    8.png

    由于瓶颈在Nginx,因此无法通过两层的Nginx来实现多个Nginx的负载均衡。图中的LVS和F5是工作在网络第四层的负载均衡解决方案,其中LVS是软件,运行在操作系统内核态,可对TCP请求或更高层级的网络协议进行转发,因此支持的协议更丰富,并且性能也远高于Nginx,可假设单机的LVS可支持几十万个并发的请求转发;F5是一种负载均衡硬件,与LVS提供的能力类似,性能比LVS更高,但价格昂贵。由于LVS是单机版的软件,若LVS所在服务器宕机则会导致整个后端系统都无法访问,因此需要有备用节点。可使用keepalived软件模拟出虚拟IP,然后把虚拟IP绑定到多台LVS服务器上,浏览器访问虚拟IP时,会被路由器重定向到真实的LVS服务器,当主LVS服务器宕机时,keepalived软件会自动更新路由器中的路由表,把虚拟IP重定向到另外一台正常的LVS服务器,从而达到LVS服务器高可用的效果。

    此处需要注意的是,上图中从Nginx层到Tomcat层这样画并不代表全部Nginx都转发请求到全部的Tomcat,在实际使用时,可能会是几个Nginx下面接一部分的Tomcat,这些Nginx之间通过keepalived实现高可用,其他的Nginx接另外的Tomcat,这样可接入的Tomcat数量就能成倍的增加。

    由于LVS也是单机的,随着并发数增长到几十万时,LVS服务器最终会达到瓶颈,此时用户数达到千万甚至上亿级别,用户分布在不同的地区,与服务器机房距离不同,导致了访问的延迟会明显不同
    ##第八次演进:通过DNS轮询实现机房间的负载均衡

    9.png

    在DNS服务器中可配置一个域名对应多个IP地址,每个IP地址对应到不同的机房里的虚拟IP。当用户访问www.taobao.com时,DNS服务器会使用轮询策略或其他策略,来选择某个IP供用户访问。此方式能实现机房间的负载均衡,至此,系统可做到机房级别的水平扩展,千万级到亿级的并发量都可通过增加机房来解决,系统入口处的请求并发量不再是问题。

    随着数据的丰富程度和业务的发展,检索、分析等需求越来越丰富,单单依靠数据库无法解决如此丰富的需求
    ##第九次演进:引入NoSQL数据库和搜索引擎等技术

    10.png

    当数据库中的数据多到一定规模时,数据库就不适用于复杂的查询了,往往只能满足普通查询的场景。对于统计报表场景,在数据量大时不一定能跑出结果,而且在跑复杂查询时会导致其他查询变慢,对于全文检索、可变数据结构等场景,数据库天生不适用。因此需要针对特定的场景,引入合适的解决方案。如对于海量文件存储,可通过分布式文件系统HDFS解决,对于key value类型的数据,可通过HBase和Redis等方案解决,对于全文检索场景,可通过搜索引擎如ElasticSearch解决,对于多维分析场景,可通过Kylin或Druid等方案解决。

    当然,引入更多组件同时会提高系统的复杂度,不同的组件保存的数据需要同步,需要考虑一致性的问题,需要有更多的运维手段来管理这些组件等。

    引入更多组件解决了丰富的需求,业务维度能够极大扩充,随之而来的是一个应用中包含了太多的业务代码,业务的升级迭代变得困难
    ##第十次演进:大应用拆分为小应用

    11.png

    按照业务板块来划分应用代码,使单个应用的职责更清晰,相互之间可以做到独立升级迭代。这时候应用之间可能会涉及到一些公共配置,可以通过分布式配置中心Zookeeper来解决。

    不同应用之间存在共用的模块,由应用单独管理会导致相同代码存在多份,导致公共功能升级时全部应用代码都要跟着升级
    ##第十一次演进:复用的功能抽离成微服务

    12.png

    如用户管理、订单、支付、鉴权等功能在多个应用中都存在,那么可以把这些功能的代码单独抽取出来形成一个单独的服务来管理,这样的服务就是所谓的微服务,应用和服务之间通过HTTP、TCP或RPC请求等多种方式来访问公共服务,每个单独的服务都可以由单独的团队来管理。此外,可以通过Dubbo、SpringCloud等框架实现服务治理、限流、熔断、降级等功能,提高服务的稳定性和可用性。

    不同服务的接口访问方式不同,应用代码需要适配多种访问方式才能使用服务,此外,应用访问服务,服务之间也可能相互访问,调用链将会变得非常复杂,逻辑变得混乱
    ##第十二次演进:引入企业服务总线ESB屏蔽服务接口的访问差异

    13.png

    通过ESB统一进行访问协议转换,应用统一通过ESB来访问后端服务,服务与服务之间也通过ESB来相互调用,以此降低系统的耦合程度。这种单个应用拆分为多个应用,公共服务单独抽取出来来管理,并使用企业消息总线来解除服务之间耦合问题的架构,就是所谓的SOA(面向服务)架构,这种架构与微服务架构容易混淆,因为表现形式十分相似。个人理解,微服务架构更多是指把系统里的公共服务抽取出来单独运维管理的思想,而SOA架构则是指一种拆分服务并使服务接口访问变得统一的架构思想,SOA架构中包含了微服务的思想。

    业务不断发展,应用和服务都会不断变多,应用和服务的部署变得复杂,同一台服务器上部署多个服务还要解决运行环境冲突的问题,此外,对于如大促这类需要动态扩缩容的场景,需要水平扩展服务的性能,就需要在新增的服务上准备运行环境,部署服务等,运维将变得十分困难。
    ##第十三次演进:引入容器化技术实现运行环境隔离与动态服务管理

    14.png

    目前最流行的容器化技术是Docker,最流行的容器管理服务是Kubernetes(K8S),应用/服务可以打包为Docker镜像,通过K8S来动态分发和部署镜像。Docker镜像可理解为一个能运行你的应用/服务的最小的操作系统,里面放着应用/服务的运行代码,运行环境根据实际的需要设置好。把整个“操作系统”打包为一个镜像后,就可以分发到需要部署相关服务的机器上,直接启动Docker镜像就可以把服务起起来,使服务的部署和运维变得简单。

    在大促的之前,可以在现有的机器集群上划分出服务器来启动Docker镜像,增强服务的性能,大促过后就可以关闭镜像,对机器上的其他服务不造成影响(在3.14节之前,服务运行在新增机器上需要修改系统配置来适配服务,这会导致机器上其他服务需要的运行环境被破坏)。

    使用容器化技术后服务动态扩缩容问题得以解决,但是机器还是需要公司自身来管理,在非大促的时候,还是需要闲置着大量的机器资源来应对大促,机器自身成本和运维成本都极高,资源利用率低
    ##第十四次演进:以云平台承载系统

    15.png

    系统可部署到公有云上,利用公有云的海量机器资源,解决动态硬件资源的问题,在大促的时间段里,在云平台中临时申请更多的资源,结合Docker和K8S来快速部署服务,在大促结束后释放资源,真正做到按需付费,资源利用率大大提高,同时大大降低了运维成本。

    所谓的云平台,就是把海量机器资源,通过统一的资源管理,抽象为一个资源整体,在之上可按需动态申请硬件资源(如CPU、内存、网络等),并且之上提供通用的操作系统,提供常用的技术组件(如Hadoop技术栈,MPP数据库等)供用户使用,甚至提供开发好的应用,用户不需要关系应用内部使用了什么技术,就能够解决需求(如音视频转码服务、邮件服务、个人博客等)。在云平台中会涉及如下几个概念:

    * IaaS:基础设施即服务。对应于上面所说的机器资源统一为资源整体,可动态申请硬件资源的层面;
    * PaaS:平台即服务。对应于上面所说的提供常用的技术组件方便系统的开发和维护;
    * SaaS:软件即服务。对应于上面所说的提供开发好的应用或服务,按功能或性能要求付费。

    至此,以上所提到的从高并发访问问题,到服务的架构和系统实施的层面都有了各自的解决方案,但同时也应该意识到,在上面的介绍中,其实是有意忽略了诸如跨机房数据同步、分布式事务实现等等的实际问题,这些问题以后有机会再拿出来单独讨论。
    #架构设计总结

    ##架构的调整是否必须按照上述演变路径进行?

    不是的,以上所说的架构演变顺序只是针对某个侧面进行单独的改进,在实际场景中,可能同一时间会有几个问题需要解决,或者可能先达到瓶颈的是另外的方面,这时候就应该按照实际问题实际解决。如在政府类的并发量可能不大,但业务可能很丰富的场景,高并发就不是重点解决的问题,此时优先需要的可能会是丰富需求的解决方案。
    ##对于将要实施的系统,架构应该设计到什么程度?

    对于单次实施并且性能指标明确的系统,架构设计到能够支持系统的性能指标要求就足够了,但要留有扩展架构的接口以便不备之需。对于不断发展的系统,如电商平台,应设计到能满足下一阶段用户量和性能指标要求的程度,并根据业务的增长不断的迭代升级架构,以支持更高的并发和更丰富的业务。
    ##服务端架构和大数据架构有什么区别?

    所谓的“大数据”其实是海量数据采集清洗转换、数据存储、数据分析、数据服务等场景解决方案的一个统称,在每一个场景都包含了多种可选的技术,如数据采集有Flume、Sqoop、Kettle等,数据存储有分布式文件系统HDFS、FastDFS,NoSQL数据库HBase、MongoDB等,数据分析有Spark技术栈、机器学习算法等。总的来说大数据架构就是根据业务的需求,整合各种大数据组件组合而成的架构,一般会提供分布式存储、分布式计算、多维分析、数据仓库、机器学习算法等能力。而服务端架构更多指的是应用组织层面的架构,底层能力往往是由大数据架构来提供。
    #有没有一些架构设计的原则?


    * N+1设计。系统中的每个组件都应做到没有单点故障;
    * 回滚设计。确保系统可以向前兼容,在系统升级时应能有办法回滚版本;
    * 禁用设计。应该提供控制具体功能是否可用的配置,在系统出现故障时能够快速下线功能;
    * 监控设计。在设计阶段就要考虑监控的手段;
    * 多活数据中心设计。若系统需要极高的高可用,应考虑在多地实施数据中心进行多活,至少在一个机房断电的情况下系统依然可用;
    * 采用成熟的技术。刚开发的或开源的技术往往存在很多隐藏的bug,出了问题没有商业支持可能会是一个灾难;
    * 资源隔离设计。应避免单一业务占用全部资源;
    * 架构应能水平扩展。系统只有做到能水平扩展,才能有效避免瓶颈问题;
    * 非核心则购买。非核心功能若需要占用大量的研发资源才能解决,则考虑购买成熟的产品;
    * 使用商用硬件。商用硬件能有效降低硬件故障的机率;
    * 快速迭代。系统应该快速开发小功能模块,尽快上线进行验证,早日发现问题大大降低系统交付的风险;
    * 无状态设计。服务接口应该做成无状态的,当前接口的访问不依赖于接口上次访问的状态。

    原文链接:https://segmentfault.com/a/1190000018626163

    ZooKeeper开发分布式系统,动态服务上下线感知

    JetLee 发表了文章 • 0 个评论 • 151 次浏览 • 2019-06-04 10:31 • 来自相关话题

    #什么是ZooKeeper ZooKeeper是一个分布式开源框架,提供了协调分布式应用的基本服务,它向外部应用暴露一组通用服务——分布式同步(Distributed Synchronization)、命名服务(Naming Service)、集 ...查看全部
    #什么是ZooKeeper

    ZooKeeper是一个分布式开源框架,提供了协调分布式应用的基本服务,它向外部应用暴露一组通用服务——分布式同步(Distributed Synchronization)、命名服务(Naming Service)、集群维护(Group Maintenance)等,简化分布式应用协调及其管理的难度,提供高性能的分布式服务。ZooKeeper本身可以以单机模式安装运行,不过它的长处在于通过分布式ZooKeeper集群(一个Leader,多个Follower),基于一定的策略来保证ZooKeeper集群的稳定性和可用性,从而实现分布式应用的可靠性。
    #Zookeeper简介


    1. ZooKeeper是为别的分布式程序服务的
    2. ZooKeeper本身就是一个分布式程序(只要有半数以上节点存活,ZooKeeper就能正常服务)
    3. ZooKeeper所提供的服务涵盖:主从协调、服务器节点动态上下线、统一配置管理、分布式共享锁、统> 一名称服务等
    4. 虽然说可以提供各种服务,但是ZooKeeper在底层其实只提供了两个功能:

    1. 管理(存储,读取)用户程序提交的数据(类似namenode中存放的metadata)
    2. 为用户程序提供数据节点监听服务

    #ZooKeeper应用场景图

    1.jpg

    2.jpg

    #ZooKeeper集群机制

    ZooKeeper集群的角色: Leader 和 follower

    只要集群中有半数以上节点存活,集群就能提供服务。
    #ZooKeeper特性


    1. ZooKeeper:一个leader,多个follower组成的集群
    2. 全局数据一致:每个server保存一份相同的数据副本,client无论连接到哪个server,数据都是一致的
    3. 分布式读写,更新请求转发,由leader实施
    4. 更新请求顺序进行,来自同一个client的更新请求按其发送顺序依次执行
    5. 数据更新原子性,一次数据更新要么成功,要么失败
    6. 实时性,在一定时间范围内,client能读到最新数据

    #ZooKeeper的数据存储机制

    ##数据存储形式

    ZooKeeper中对用户的数据采用kv形式存储。如果你想和更多ZooKeeper技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

    只是ZooKeeper有点特别:

    key:是以路径的形式表示的,那就以为着,各key之间有父子关系,比如:

    * / 是顶层key
    * 用户建的key只能在/ 下作为子节点,比如建一个key: /aa 这个key可以带value数据
    * 也可以建一个key: /bb
    * 也可以建key: /aa/xx

    ZooKeeper中,对每一个数据key,称作一个znode

    综上所述,ZooKeeper中的数据存储形式如下:
    3.jpg

    ##znode类型

    ZooKeeper中的znode有多种类型:

    1. PERSISTENT 持久的:创建者就算跟集群断开联系,该类节点也会持久存在与zk集群中
    2. EPHEMERAL 短暂的:创建者一旦跟集群断开联系,zk就会将这个节点删除
    3. SEQUENTIAL 带序号的:这类节点,zk会自动拼接上一个序号,而且序号是递增的

    组合类型:

    * PERSISTENT :持久不带序号
    * EPHEMERAL :短暂不带序号
    * PERSISTENT 且 SEQUENTIAL :持久且带序号
    * EPHEMERAL 且 SEQUENTIAL :短暂且带序号

    #ZooKeeper的集群部署

    集群选举示意图:
    4.jpg

    解压ZooKeeper安装包到apps目录下:
    tar -zxvf zookeeper-3.4.6.tar.gz -C apps

    cd /root/apps/zookeeper-3.4.6/conf
    cp zoo_sample.cfg zoo.cfg
    vi zoo.cfg

    修改dataDir=/root/zkdata

    在后面加上集群的机器:2888是leader和follower通讯端口,3888是投票的
    server.1=hdp-01:2888:3888
    server.2=hdp-02:2888:3888
    server.3=hdp-03:2888:3888

    对3台节点,都创建目录 mkdir /root/zkdata

    对3台节点,在工作目录中生成myid文件,但内容要分别为各自的id: 1,2,3
    echo 1 > /root/zkdata/myid
    echo 2 > /root/zkdata/myid
    echo 3 > /root/zkdata/myid

    从hdp20-01上scp安装目录到其他两个节点
    cd apps
    scp -r zookeeper-3.4.6/ hdp-02:$PWD
    scp -r zookeeper-3.4.6/ hdp-03:$PWD

    启动ZooKeeper集群

    ZooKeeper没有提供自动批量启动脚本,需要手动一台一台地起ZooKeeper进程

    在每一台节点上,运行命令:
    cd /root/apps/zookeeper-3.4.6
    bin/zkServer.sh start

    启动后,用jps应该能看到一个进程:QuorumPeerMain

    但是,光有进程不代表zk已经正常服务,需要用命令检查状态:
    bin/zkServer.sh status

    能看到角色模式:为leader或follower,即正常了。

    自己写个脚本,一键启动
    vi zkmanage.sh

    #!/bin/bash
    for host in hdp-01 hdp-02 hdp-03
    do
    echo "${host}:$1ing....."
    ssh $host "/root/apps/zookeeper-3.4.6/bin/zkServer.sh $1"
    done

    停止命令:sh zjmanage.sh stop

    加个可执行权限:chmod +zkmanage.sh

    启动命令:./zkmanage.sh start

    但是出现没有Java环境变量问题,修改配置文件
    vi zkmanage.sh

    修改配置如下:
    #!/bin/bash
    for host in hdp-01 hdp-02 hdp-03
    do
    echo "${host}:$1ing....."
    ssh $host "source /etc/profile;/root/apps/zookeeper-3.4.6/bin/zkServer.sh $1"
    done

    sleep 2
    for host in hdp-01 hdp-02 hdp-03
    do
    ssh $host "source /etc/profile;/root/apps/zookeeper-3.4.6/bin/zkServer.sh status"
    done

    启动集群结果:
    hdp-01:starting.....
    JMX enabled by default
    Using config: /root/apps/zookeeper-3.4.6/bin/../conf/zoo.cfg
    Starting zookeeper ... STARTED
    hdp-02:starting.....
    JMX enabled by default
    Using config: /root/apps/zookeeper-3.4.6/bin/../conf/zoo.cfg
    Starting zookeeper ... STARTED
    hdp-03:starting.....
    JMX enabled by default
    Using config: /root/apps/zookeeper-3.4.6/bin/../conf/zoo.cfg
    Starting zookeeper ... STARTED
    JMX enabled by default
    Using config: /root/apps/zookeeper-3.4.6/bin/../conf/zoo.cfg
    Mode: follower
    JMX enabled by default
    Using config: /root/apps/zookeeper-3.4.6/bin/../conf/zoo.cfg
    Mode: leader
    JMX enabled by default
    Using config: /root/apps/zookeeper-3.4.6/bin/../conf/zoo.cfg
    Mode: follower

    ZooKeeper的Java客户端操作代码:
    public class ZookeeperCliDemo {
    ZooKeeper zk =null;
    @Before
    public void init() throws Exception {
    zk=new ZooKeeper("hdp-01:2181,hdp-02:2181,hdp-03:2181", 2000, null);
    }
    @Test
    public void testCreate() throws Exception {
    //参数1:要创建的节点路径;参数2:数据;参数3:访问权限;参数4:节点类型
    String create = zk.create("/eclipse", "hello eclipse".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    System.out.println(create);
    zk.close();
    }

    @Test
    public void testUpdate() throws Exception {
    //参数1:节点路径;参数2:数据;参数3:所要修改的版本,-1表示任意版本
    zk.setData("/eclipse","我喜欢青青".getBytes(),-1);
    zk.close();
    }
    @Test
    public void testGet() throws Exception {
    //参数1:节点路径;参数2:事件监听;参数3:所要修改的版本,null表示最新版本
    byte[] data = zk.getData("/eclipse", false, null);
    System.out.println(new String(data,"UTF-8"));
    zk.close();
    }

    @Test
    public void testListChildren() throws KeeperException, InterruptedException {
    //参数1:节点路径;参数2:是否要监听
    //注意:返回的结果只有子节点的名字,不带全路径
    List children = zk.getChildren("/cc", false);
    for(String child:children){
    System.out.println(child);
    }
    zk.close();
    }

    @Test
    public void testRm() throws KeeperException, InterruptedException {
    zk.delete("/eclipse",-1);
    zk.close();
    }
    }

    ZooKeeper监听功能代码:
    public class ZookeeperWatchDemo {
    ZooKeeper zk =null;
    @Before
    public void init() throws Exception {
    zk=new ZooKeeper("hdp-01:2181,hdp-02:2181,hdp-03:2181", 2000, new Watcher() {
    public void process(WatchedEvent watchedEvent) {
    if (watchedEvent.getState() == Event.KeeperState.SyncConnected
    && watchedEvent.getType() == Event.EventType.NodeDataChanged) {
    System.out.println("收到事件所发生节点的路径" + watchedEvent.getPath());
    System.out.println("收到事件所发生节点的状态" + watchedEvent.getState());
    System.out.println("收到事件所发生节点的类型" + watchedEvent.getType());
    System.out.println("watch事件通知。。换照片");
    try {
    zk.getData("/mygirls", true, null);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }else if(watchedEvent.getState()==Event.KeeperState.SyncConnected &&
    watchedEvent.getType()==Event.EventType.NodeChildrenChanged){
    System.out.println("收到事件所发生节点的路径" + watchedEvent.getPath());
    System.out.println("收到事件所发生节点的状态" + watchedEvent.getState());
    System.out.println("收到事件所发生节点的类型" + watchedEvent.getType());

    }
    }
    });
    }

    @Test
    public void testGetWatch() throws Exception {
    byte[] data = zk.getData("/mygirls",true, null);
    List children = zk.getChildren("/mygirls", true);
    System.out.println(new String(data,"UTF-8"));
    Thread.sleep(Long.MAX_VALUE);
    }
    }

    ZooKeeper开发分布式系统案例代码,动态上下线感知。

    服务代码:
    public class TimeQueryServer {
    ZooKeeper zk=null;
    public void connectZk()throws Exception{
    zk=new ZooKeeper("hdp-01:2181,hdp-02:2181,hdp-03:2181", 2000, null);
    }

    public void registerServerInfo(String hostname,String port)throws Exception{
    /**
    * 先判断注册节点的父节点是否存在,如果不存在,则创建持久节点
    */
    Stat exists = zk.exists("/servers", false);
    if(exists==null){
    zk.create("/servers",null,ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
    }
    /**
    * 注册服务器数据到zk的约定注册节点下
    */
    String create = zk.create("/servers/server", (hostname + ":" + port).getBytes(),
    ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
    System.out.println(hostname+" 服务器向zk 注册成功,注册节点为:/servers"+create);
    }
    public static void main(String[] args) throws Exception {
    //1.构造zk连接
    TimeQueryServer timeQueryServer = new TimeQueryServer();
    timeQueryServer.connectZk();
    //2.注册服务器信息
    timeQueryServer.registerServerInfo("192.168.150.3","44772");
    //3.启动业务线程开始处理业务
    new TimeQueryService(44772).start();
    }
    }

    public class TimeQueryService extends Thread {
    int port=0;
    public TimeQueryService(int port){
    this.port=port;
    }
    @Override
    public void run() {
    try {
    ServerSocket ss = new ServerSocket(port);
    System.out.println("业务线程已经绑定端口"+port+"开始接受客户端请求..");
    while (true){
    Socket sc = ss.accept();
    InputStream inputStream = sc.getInputStream();
    OutputStream outputStream = sc.getOutputStream();
    outputStream.write(new Date().toString().getBytes());
    }
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }

    消费者代码:
    public class Consumer {

    //定义一个list用于存放在线的服务器列表
    private volatile ArrayListonlineServers=new ArrayList();
    ZooKeeper zk=null;
    public void connectZk()throws Exception{
    zk=new ZooKeeper("hdp-01:2181,hdp-02:2181,hdp-03:2181", 2000, new Watcher() {
    public void process(WatchedEvent watchedEvent) {
    if (watchedEvent.getState()==Event.KeeperState.SyncConnected && watchedEvent.getType()==Event.EventType.NodeChildrenChanged){
    try{
    //事件回调逻辑中,再次查询zk上在线服务器节点即可,查询逻辑中又再次注册子节点变化事件监听
    getOnlineServers();
    }catch (Exception e){
    e.printStackTrace();
    }
    }
    }
    });
    }
    //查询在线服务器列表
    public void getOnlineServers()throws Exception{
    List children = zk.getChildren("/servers", true);
    ArrayList servers = new ArrayList();
    for (String child:children){
    byte[] data = zk.getData("/servers/" + child, false, null);
    String serverInfo=new String(data);
    servers.add(serverInfo);
    }
    onlineServers=servers;
    System.out.println("查询了一次zk,当前在线的服务器有:"+servers);

    }

    public void setRequest() throws Exception {
    Random random = new Random();
    while (true){
    try {
    int nextInt=random.nextInt(onlineServers.size());
    String server=onlineServers.get(nextInt);
    String hostname=server.split(":")[0];
    int port=Integer.parseInt(server.split(":")[1]);
    System.out.println("本次请求挑选的服务器为:"+server);

    Socket socket = new Socket(hostname, port);
    OutputStream out = socket.getOutputStream();
    InputStream in = socket.getInputStream();
    out.write("hahaha".getBytes());
    out.flush();

    byte[] buf = new byte[256];
    int read=in.read(buf);
    String s = new String(buf, 0, read);
    System.out.println("服务器响应时间为:"+s);
    out.close();
    in.close();
    socket.close();
    Thread.sleep(2000);
    }catch (Exception e){

    }

    }
    }
    public static void main(String[] args) throws Exception {
    //构造zk连接对象
    Consumer consumer = new Consumer();
    consumer.connectZk();
    //查询在线服务器列表
    consumer.getOnlineServers();
    //处理业务
    consumer.setRequest();
    }
    }

    pom


    junit
    junit
    RELEASE


    org.apache.logging.log4j
    log4j-core
    2.8.2



    org.apache.zookeeper
    zookeeper
    3.4.10


    启动多个服务。

    控制台输出:

    192.168.150.3 服务器向zk 注册成功,注册节点为:/servers/servers/server0000000018
    业务线程已经绑定端口44772开始接受客户端请求..

    192.168.150.3 服务器向zk 注册成功,注册节点为:/servers/servers/server0000000019
    业务线程已经绑定端口44773开始接受客户端请求..

    192.168.150.3 服务器向zk 注册成功,注册节点为:/servers/servers/server0000000020
    业务线程已经绑定端口44774开始接受客户端请求..

    消费者启动

    控制台输出:

    查询了一次zk,当前在线的服务器有:[192.168.150.3:44773, 192.168.150.3:44772, 192.168.150.3:44774]
    本次请求挑选的服务器为:192.168.150.3:44772
    服务器响应时间为:Mon Jun 03 20:03:21 CST 2019
    本次请求挑选的服务器为:192.168.150.3:44773
    服务器响应时间为:Mon Jun 03 20:03:23 CST 2019
    本次请求挑选的服务器为:192.168.150.3:44773
    服务器响应时间为:Mon Jun 03 20:03:25 CST 2019
    本次请求挑选的服务器为:192.168.150.3:44772
    服务器响应时间为:Mon Jun 03 20:03:27 CST 2019

    下线一个服务后,控制台输出:

    查询了一次zk,当前在线的服务器有:[192.168.150.3:44773, 192.168.150.3:44772]
    本次请求挑选的服务器为:192.168.150.3:44773
    服务器响应时间为:Mon Jun 03 20:04:19 CST 2019
    本次请求挑选的服务器为:192.168.150.3:44773
    服务器响应时间为:Mon Jun 03 20:04:21 CST 2019
    本次请求挑选的服务器为:192.168.150.3:44773
    服务器响应时间为:Mon Jun 03 20:04:23 CST 2019
    本次请求挑选的服务器为:192.168.150.3:44773

    原文链接:https://my.oschina.net/u/3995125/blog/3057475

    基于 ZooKeeper 实现爬虫集群的监控

    阿娇 发表了文章 • 0 个评论 • 154 次浏览 • 2019-05-29 13:03 • 来自相关话题

    Zookeeper 扩展之殇

    齐达内 发表了文章 • 0 个评论 • 156 次浏览 • 2019-05-28 12:27 • 来自相关话题

    #一、背景 基于公司发展硬性需求,生产VM服务器要统一迁移到ZStack虚拟化服务器。检查自己项目使用的服务器,其中ZooKeeper集群中招,所以需要进行迁移。如果你想和更多ZooKeeper技术专家交流,可以加我微信liyingjiese,备 ...查看全部
    #一、背景

    基于公司发展硬性需求,生产VM服务器要统一迁移到ZStack虚拟化服务器。检查自己项目使用的服务器,其中ZooKeeper集群中招,所以需要进行迁移。如果你想和更多ZooKeeper技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态
    #二、迁移计划

    为了使迁移不对业务产生影响,所以最好是采用扩容 -> 缩容 的方式进行。
    1.png

    说明:

    1. 原生产集群为VM-1,VM-2,VM-3组成一个3节点的ZooKeeper集群;
    2. 对该集群扩容,增加至6节点(新增ZS-1,ZS-2,ZS-3),进行数据同步完成;
    3. 进行缩容,下掉原先来的三个节点(VM-1,VM-2,VM-3);
    4. 替换Nginx解析地址。

    OK! 目标很明确,过程也很清晰,然后开干。
    #三、步骤(过程已在测试环境验证无问题)

    对新增的三台服务器进行zk环境配置,和老集群配置一样即可,最好使用同一版本(版主使用的是3.4.6);

    对老节点的zoo.cfg增加新集群的地址(逐一增加),然后对新增加节点逐一重启。
    2.png

    #四、问题

    ZS-1 启动成功,zkServer.sh status报错,用zkServer.sh status查看,反馈如下异常:
    [root@localhost bin]# ./zkServer.sh  status
    ZooKeeper JMX enabled by default
    Using config: /usr/zookeeper/zookeeper-3.4.6/bin/../conf/zoo.cfg
    Error contacting service. It is probably not running.

    此时查看数据,数据同步正常。

    ZS-1数据同步正常,但是无法查看节点的状态信息;

    怀疑是因为老节点没有重启的原因;此时去查看原集群节点信息,发现原集群节点状态异常。经排查定位,原集群的状态一直处于异常状态。

    初步定位原因可能是原集群的选举存在异常,导致新节点无法正常纳入,继续排查。

    恢复集群初始状态,如果集群节点的状态一直没法正常查看。OK 继续定位……
    #五、排查过程

    以下方法来自于网络:

    可能有以下几个原因:

    第一、zoo.cfg文件配置:dataLogDir指定的目录未被创建。
    1.zoo.cfg
    [root@SIA-215 conf]# cat zoo.cfg
    ...
    dataDir=/app/zookeeperdata/data
    dataLogDir=/app/zookeeperdata/log
    ...

    2.路径
    [root@SIA-215 conf]# cd /app/zookeeperdata/
    [root@SIA-215 zookeeperdata]# ll
    total 8
    drwxr-xr-x 3 root root 4096 Apr 23 19:59 data
    drwxr-xr-x 3 root root 4096 Aug 29 2015 log

    经排查 排除该因素。

    第二、myid文件中的整数格式不对,或者与zoo.cfg中的server整数不对应。
    [root@SIA-215 data]# cd /app/zookeeperdata/data
    [root@SIA-215 data]# cat myid
    2[root@SIA-215 data]#

    定位排查后排除不是该原因。

    第三、防火墙未关闭。

    使用service iptables stop关闭防火墙;使用service iptables status确认;使用chkconfig iptables off禁用防火墙。

    确认防火墙是关闭的。
    [root@localhost ~]# service iptables status
    iptables: Firewall is not running.
    确认防火墙是关闭的

    第四、端口被占用。
    [root@localhost bin]# netstat -tunlp | grep 2181
    tcp 0 0 :::12181 :::* LISTEN 30035/java
    tcp 0 0 :::22181 :::* LISTEN 30307/java

    确认端口没有被占用

    第五、zoo.cfg文件中主机名出错。

    经测试环境测试,主机名正确,多域名解析也正常,不存在此问题。

    第六、hosts文件中,本机的主机名有两个对应,只需保留主机名和IP地址的映射。

    经测试环境测试,主机名正确,多域名解析也正常,不存在此问题 排除。

    第七、zkServer.sh里的nc命令有问题。

    可能是机器上没有安装nc命令,还有种说法是在zkServer.sh里找到这句:

    STAT=`echo stat | nc localhost $(grep clientPort “$ZOOCFG” | sed -e ‘s/.*=//’) 2> /dev/null| grep Mode`
    在nc与localhost之间加上 -q 1 (是数字1而不是字母l)。

    ZooKeeper版本是3.4.6,zkServer.sh里根本没有这一句(获取状态的语句没有用nc命令)
     # -q is necessary on some versions of linux where nc returns too quickly, and no stat result is output
    clientPortAddress=`grep "^[[:space:]][i]clientPortAddress[^[:alpha:]]" "$ZOOCFG" | sed -e 's/.[/i]=//'`
    if ! [ $clientPortAddress ]
    then
    clientPortAddress="localhost"
    fi
    clientPort=`grep "^[[:space:]][i]clientPort[^[:alpha:]]" "$ZOOCFG" | sed -e 's/.[/i]=//'`
    STAT=`"$JAVA" "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \
    -cp "$CLASSPATH" $JVMFLAGS org.apache.zookeeper.client.FourLetterWordMain \
    $clientPortAddress $clientPort srvr 2> /dev/null \
    | grep Mode`
    if [ "x$STAT" = "x" ]
    then
    echo "Error contacting service. It is probably not running."
    exit 1
    else
    echo $STAT
    exit 0
    fi
    ;;

    #六、以下是自己排查的方式

    目前现象老集群数据同步正常,也能进行leader选举(从日志获取),但是无法查看节点状态,同异常信息;进行集群扩容,数据不能同步。

    解决方法:

    1、尝试进行foreground 模式启动,选择一台非主节点进行重启,可以前台查看启动日志。
    zkserver.sh start-foreground

    节点启动正常,无异常输出。
    2、查看shell脚本:分析zkServer.sh。

    "Error contacting service. It is probably not running." 这块日志出现以下脚本中。
    STAT=`"$JAVA" "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \
    -cp "$CLASSPATH" $JVMFLAGS org.apache.zookeeper.client.FourLetterWordMain \
    $clientPortAddress $clientPort srvr 2> /dev/null \
    | grep Mode`
    if [ "x$STAT" = "x" ]
    then
    echo "Error contacting service. It is probably not running."
    exit 1
    else
    echo $STAT
    exit 0
    fi
    ;;

    截取其中一部分脚本内容:我们可以初步定为应该是 $STAT 获取存在异常 如果STAT变量为空,则会显示Error contacting service. It is probably not running.: OK,那就分析下这个$STAT到底是什么鬼?
     if [ “x$STAT” = “x” ]
    then
    echo “Error contacting service. It is probably not running.”
    exit 1
    else
    echo $STAT
    exit 0
    fi

    3、尝试用shell的debug模式 看下执行过程:

    截取片段执行日志如下 :果然STAT变量确实为空,导致输出Error contacting service. It is probably not running.并且退出。
    ++ grep '^[[:space:]]*clientPort[^[:alpha:]]' /app/zookeeper-3.4.6/bin/../conf/zoo.cfg
    + clientPort=5181
    ++ grep Mode
    ++ /opt/jdk1.8.0_131/bin/java -Dzookeeper.log.dir=. -Dzookeeper.root.logger=INFO,CONSOLE -cp '/app/zookeeper-3.4.6/bin/../build/classes:/app/zookeeper-3.4.6/bin/../build/lib/[i].jar:/app/zookeeper-3.4.6/bin/../lib/slf4j-log4j12-1.6.1.jar:/app/zookeeper-3.4.6/bin/../lib/slf4j-api-1.6.1.jar:/app/zookeeper-3.4.6/bin/../lib/netty-3.7.0.Final.jar:/app/zookeeper-3.4.6/bin/../lib/log4j-1.2.16.jar:/app/zookeeper-3.4.6/bin/../lib/jline-0.9.94.jar:/app/zookeeper-3.4.6/bin/../zookeeper-3.4.6.jar:/app/zookeeper-3.4.6/bin/../src/java/lib/[/i].jar:/app/zookeeper-3.4.6/bin/../conf:.:/opt/jdk1.8.0_131/lib/dt.jar:/opt/jdk1.8.0_131/lib/tools.jar' org.apache.zookeeper.client.FourLetterWordMain localhost 5181 srvr
    + STAT=
    + ‘[‘ x = x ‘]’
    + echo ‘Error contacting service. It is probably not running.’
    Error contacting service. It is probably not running.
    + exit 1

    4、修改shell脚本:分析zkServer.sh 在脚本总增加输出STAT 内容,这次我们不进行过滤。
    STAT1=`"$JAVA" "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \
    -cp "$CLASSPATH" $JVMFLAGS org.apache.zookeeper.client.FourLetterWordMain \
    $clientPortAddress $clientPort srvr 2> test.log \ `

    echo "$STAT1"

    最好的方式是copy一个新脚本,以免污染原本的脚本。我是这么做的;然后运行该脚本。
    [root@localhost bin]# ./zkServer.sh  status
    ZooKeeper JMX enabled by default
    Using config: /usr/zookeeper/zookeeper-3.4.10/bin/../conf/zoo.cfg
    Error contacting service. It is probably not running.

    然后查看生成的test.log 文件:果然存在异常内容。
    in thread “main” java.lang.NumberFormatException: For input string: “2181
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Integer.parseInt(Integer.java:492)
    at java.lang.Integer.parseInt(Integer.java:527)
    at org.apache.zookeeper.client.FourLetterWordMain.main(FourLetterWordMain.java:76)

    从日志信息来看,提示说2181这个端口号造成的。 不是一个合法的数字。

    zkServer.sh里有这么一句:
    clientPort=`grep “^[[:space:]][i]clientPort[^[:alpha:]]” “$ZOOCFG” | sed -e ‘s/.[/i]=//’`
    grep “^[[:space:]][i]clientPort[^[:alpha:]]” “$ZOOCFG” | sed -e ‘s/.[/i]=//’在执行过程中,实际命令如下:
    grep ‘^[[:space:]][i]clientPort[^[:alpha:]]’ /app/zookeeper-3.4.6/bin/../conf/zoo.cfg | sed -e ‘s/.[/i]=//’

    最终可以基本确认配置文件存在问题。

    替换配置文件:重启,问题解决。

    存在原因可能是编辑zoo.cfg编码格式等等引起文件内容解析异常。

    原文链接:http://college.creditease.cn/detail/252

    从零开始搭建创业公司后台技术栈

    Andy_Lee 发表了文章 • 0 个评论 • 988 次浏览 • 2019-04-24 22:09 • 来自相关话题

    说到后台技术栈,脑海中是不是浮现的是这样一幅图?有点眼晕,以下只是我们会用到的一些语言的合集,而且只是语言层面的一部分,就整个后台技术栈来说,这只是一个开始,从语言开始,还有很多很多的内容。今天要说的后台是大后台的概念,放在服务器上的东西都属于后台的东西,比如 ...查看全部
    说到后台技术栈,脑海中是不是浮现的是这样一幅图?有点眼晕,以下只是我们会用到的一些语言的合集,而且只是语言层面的一部分,就整个后台技术栈来说,这只是一个开始,从语言开始,还有很多很多的内容。今天要说的后台是大后台的概念,放在服务器上的东西都属于后台的东西,比如使用的框架,语言,数据库,服务,操作系统等等。
    svr_stack1.png

    整个后台技术栈我的理解包括 4 个层面的内容:

    * 语言: 用了哪些开发语言,如:C++/Java/Go/PHP/Python/Ruby 等等;
    * 组件:用了哪些组件,如:MQ 组件,数据库组件等等;
    * 流程:怎样的流程和规范,如:开发流程,项目流程,发布流程,监控告警流程,代码规范等等;
    * 系统:系统化建设,上面的流程需要有系统来保证,如:规范发布流程的发布系统,代码管理系统等等;

    结合以上的的 4 个层面的内容,整个后台技术栈的结构如图 2 所示:
    svr_stack2.png

    图2 后台技术栈结构

    以上的这些内容都需要我们从零开始搭建,在创业公司,没有大公司那些完善的基础设施,需要我们从开源界,从云服务商甚至有些需要自己去组合,去拼装,去开发一个适合自己的组件或系统以达成我们的目标。咱们一个个系统和组件的做选型,最终形成我们的后台技术栈。
    #各系统组件选型
    ##项目管理/Bug管理/问题管理
    项目管理软件是整个业务的需求,问题,流程等等的集中地,大家的跨部门沟通协同大多依赖于项目管理工具。有一些 SaaS 的项目管理服务可以使用,但是很多时间不满足需求,此时我们可以选择一些开源的项目,这些项目本身有一定的定制能力,有丰富的插件可以使用,一般的创业公司需求基本上都能得到满足,常用的项目如下:

    * Redmine: 用 Ruby 开发的,有较多的插件可以使用,能自定义字段,集成了项目管理,Bug 问题跟踪,WIKI 等功能,不过好多插件 N 年没有更新了;
    * Phabricator:用 PHP 开发的,Facebook 之前的内部工具,开发这工具的哥们离职后自己搞了一个公司专门做这个软件,集成了代码托管, Code Review,任务管理,文档管理,问题跟踪等功能,强烈推荐较敏捷的团队使用;
    * Jira:用 Java 开发的,有用户故事,task 拆分,燃尽图等等,可以做项目管理,也可以应用于跨部门沟通场景,较强大;
    * 悟空 CRM :这个不是项目管理,这个是客户管理,之所以在这里提出来,是因为在 To B 的创业公司里面,往往是以客户为核心来做事情的,可以将项目管理和问题跟进的在悟空 CRM 上面来做,他的开源版本已经基本实现了 CR< 的核心 功能,还带有一个任务管理功能,用于问题跟进,不过用这个的话,还是需要另一个项目管理的软件协助,顺便说一嘴,这个系统的代码写得很难维护,只能适用于客户规模小(1万以内)时。

    ##DNS
    DNS 是一个很通用的服务,创业公司基本上选择一个合适的云厂商就行了,国内主要是两家:

    * 阿里万网:阿里 2014 年收购了万网,整合了其域名服务,最终形成了现在的阿里万网,其中就包含 DNS 这块的服务;
    * 腾讯 DNSPod:腾讯 2012 年以 4000 万收购 DNSPod 100% 股份,主要提供域名解析和一些防护功能;

    如果你的业务是在国内,主要就是这两家,选 一个就好,像今日头条这样的企业用的也是 DNSPod 的服务,除非一些特殊的原因才需要自建,比如一些 CDN 厂商,或者对区域有特殊限制的。要实惠一点用阿里最便宜的基础版就好了,要成功率高一些,还是用 DNSPod 的贵的那种。

    在国外还是选择亚马逊吧,阿里的 DNS 服务只有在日本和美国有节点,东南亚最近才开始部点, DNSPod 也只有美国和日本,像一些出海的企业,其选择的云服务基本都是亚马逊。

    如果是线上产品,DNS 强烈建议用付费版,阿里的那几十块钱的付费版基本可以满足需求。如果还需要一些按省份或按区域调试的逻辑,则需要加钱,一年也就几百块,省钱省力。

    如果是国外,优先选择亚马逊,如果需要国内外互通并且有自己的 APP 的话,建议还是自己实现一些容灾逻辑或者智能调度,因为没有一个现成的 DNS 服务能同时较好的满足国内外场景,或者用多个域名,不同的域名走不同的 DNS 。
    ##LB(负载均衡)
    LB(负载均衡)是一个通用服务,一般云厂商的 LB 服务基本都会如下功能:

    * 支持四层协议请求(包括 TCP、UDP 协议);
    * 支持七层协议请求(包括 HTTP、HTTPS 协议);
    * 集中化的证书管理系统支持 HTTPS 协议;
    * 健康检查;

    如果你线上的服务机器都是用的云服务,并且是在同一个云服务商的话,可以直接使用云服务商提供的 LB 服务,如阿里云的 SLB,腾讯云的 CLB,亚马逊的 ELB 等等。如果是自建机房基本都是 LVS + Nginx。
    ##CDN
    CDN 现在已经是一个很红很红的市场,基本上只能挣一些辛苦钱,都是贴着成本在卖。国内以网宿为龙头,他们家占据整个国内市场份额的 40% 以上,后面就是腾讯,阿里。网宿有很大一部分是因为直播的兴起而崛起。

    国外,Amazon 和 Akamai 合起来占比大概在 50%,曾经的国际市场老大 Akamai 拥有全球超一半的份额,在 Amazon CDN入局后,份额跌去了将近 20%,众多中小企业都转向后者,Akamai 也是无能为力。

    国内出海的 CDN 厂商,更多的是为国内的出海企业服务,三家大一点的 CDN 服务商里面也就网宿的节点多一些,但是也多不了多少。阿里和腾讯还处于前期阶段,仅少部分国家有节点。

    就创业公司来说,CDN 用腾讯云或阿里云即可,其相关系统较完善,能轻松接入,网宿在系统支持层面相对较弱一些,而且还贵一些。并且,当流量上来后,CDN 不能只用一家,需要用多家,不同的 CDN 在全国的节点覆盖不一样,而且针对不同的客户云厂商内部有些区分客户集群,并不是全节点覆盖(但有些云厂商说自己是全网节点),除了节点覆盖的问题,多 CDN 也在一定程度上起到容灾的作用。
    ##RPC 框架
    维基百科对 RPC 的定义是:远程过程调用(Remote Procedure Call,RPC)是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。

    通俗来讲,一个完整的 RPC 调用过程,就是 Server 端实现了一个函数,客户端使用 RPC 框架提供的接口,调用这个函数的实现,并获取返回值的过程。

    业界 RPC 框架大致分为两大流派,一种侧重跨语言调用,另一种是偏重服务治理。

    跨语言调用型的 RPC 框架有 Thrift、gRPC、Hessian、Hprose 等。这类 RPC 框架侧重于服务的跨语言调用,能够支持大部分的语言进行语言无关的调用,非常适合多语言调用场景。但这类框架没有服务发现相关机制,实际使用时需要代理层进行请求转发和负载均衡策略控制。

    其中,gRPC 是 Google 开发的高性能、通用的开源 RPC 框架,其由 Google 主要面向移动应用开发并基于 HTTP/2 协议标准而设计,基于 ProtoBuf(Protocol Buffers)序列化协议开发,且支持众多开发语言。本身它不是分布式的,所以要实现框架的功能需要进一步的开发。

    Hprose(High Performance Remote Object Service Engine)是一个 MIT 开源许可的新型轻量级跨语言跨平台的面向对象的高性能远程动态通讯中间件。

    服务治理型的 RPC 框架的特点是功能丰富,提供高性能的远程调用、服务发现及服务治理能力,适用于大型服务的服务解耦及服务治理,对于特定语言(Java)的项目可以实现透明化接入。缺点是语言耦合度较高,跨语言支持难度较大。国内常见的冶理型 RPC 框架如下:

    * Dubbo:Dubbo 是阿里巴巴公司开源的一个 Java 高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring 框架无缝集成。当年在淘宝内部,Dubbo 由于跟淘宝另一个类似的框架 HSF 有竞争关系,导致 Dubbo 团队解散,最近又活过来了,有专职同学投入。
    * DubboX:DubboX 是由当当在基于 Dubbo 框架扩展的一个 RPC 框架,支持 REST 风格的远程调用、Kryo/FST 序列化,增加了一些新的feature。
    Motan:Motan 是新浪微博开源的一个 Java 框架。它诞生的比较晚,起于 2013 年,2016 年 5 月开源。Motan 在微博平台中已经广泛应用,每天为数百个服务完成近千亿次的调用。
    * rpcx:rpcx 是一个类似阿里巴巴 Dubbo 和微博 Motan 的分布式的 RPC 服务框架,基于 Golang net/rpc 实现。但是 rpcx 基本只有一个人在维护,没有完善的社区,使用前要慎重,之前做 Golang 的 RPC 选型时也有考虑这个,最终还是放弃了,选择了 gRPC,如果想自己自研一个 RPC 框架,可以参考学习一下。

    ##名字发现/服务发现
    名字发现和服务发现分为两种模式,一个是客户端发现模式,一种是服务端发现模式。

    框架中常用的服务发现是客户端发现模式。

    所谓服务端发现模式是指客户端通过一个负载均衡器向服务发送请求,负载均衡器查询服务注册表并把请求路由到一台可用的服务实例上。现在常用的负载均衡器都是此类模式,常用于微服务中。

    所有的名字发现和服务发现都要依赖于一个可用性非常高的服务注册表,业界常用的服务注册表有如下三个:

    * etcd,一个高可用、分布式、一致性、key-value 方式的存储,被用在分享配置和服务发现中。两个著名的项目使用了它:Kubernetes 和 Cloud Foundry。
    * Consul,一个发现和配置服务的工具,为客户端注册和发现服务提供了API,Consul还可以通过执行健康检查决定服务的可用性。
    * Apache ZooKeeper,是一个广泛使用、高性能的针对分布式应用的协调服务。Apache ZooKeeper 本来是 Hadoop 的子工程,现在已经是顶级工程了。

    除此之外也可以自己实现服务实现,或者用 Redis 也行,只是需要自己实现高可用性。
    ##关系数据库
    关系数据库分为两种,一种是传统关系数据,如 Oracle,MySQL,Maria,DB2,PostgreSQL 等等,另一种是 NewSQL,即至少要满足以下五点的新型关系数据库:

    1. 完整地支持 SQL,支持 JOIN / GROUP BY /子查询等复杂 SQL 查询。
    2. 支持传统数据标配的 ACID 事务,支持强隔离级别。
    3. 具有弹性伸缩的能力,扩容缩容对于业务层完全透明。
    4. 真正的高可用,异地多活、故障恢复的过程不需要人为的接入,系统能够自动地容灾和进行强一致的数据恢复。
    5. 具备一定的大数据分析能力。

    传统关系数据库用得最多的是 MySQL,成熟,稳定,一些基本的需求都能满足,在一定数据量级之前基本单机传统数据库都可以搞定,而且现在较多的开源系统都是基于 MySQL,开箱即用,再加上主从同步和前端缓存,百万 pv 的应用都可以搞定了。不过 CentOS 7 已经放弃了 MySQL,而改使用 MariaDB。MariaDB 数据库管理系统是 MySQ L的一个分支,主要由开源社区在维护,采用 GPL 授权许可。开发这个分支的原因之一是:甲骨文公司收购了 MySQL 后,有将 MySQL 闭源的潜在风险,因此社区采用分支的方式来避开这个风险。

    在 Google 发布了 F1: A Distributed SQL Database That Scales 和 Spanner: Google’s Globally-Distributed Databasa 之后,业界开始流行起 NewSQL。于是有了 CockroachDB,于是有了奇叔公司的 TiDB。国内已经有比较多的公司使用 TiDB,之前在创业公司时在大数据分析时已经开始应用 TiDB,当时应用的主要原因是 MySQL 要使用分库分表,逻辑开发比较复杂,扩展性不够。
    ##NoSQL
    NoSQL 顾名思义就是 Not-Only SQL,也有人说是 No – SQL,个人偏向于 Not-Only SQL,它并不是用来替代关系库,而是作为关系型数据库的补充而存在。

    常见 NoSQL 有4个类型:

    * 键值,适用于内容缓存,适合混合工作负载并发高扩展要求大的数据集,其优点是简单,查询速度快,缺点是缺少结构化数据,常见的有 Redis,Memcache,BerkeleyDB 和 Voldemort 等等;
    * 列式,以列簇式存储,将同一列数据存在一起,常见于分布式的文件系统,其中以 Hbase,Cassandra 为代表。Cassandra 多用于写多读少的场景,国内用得比较多的有 360,大概 1500 台机器的集群,国外大规模使用的公司比较多,如 eBay,Instagram,Apple 和沃尔玛等等;
    * 文档,数据存储方案非常适用承载大量不相关且结构差别很大的复杂信息。性能介于 kv 和关系数据库之间,它的灵感来于 lotus notes,常见的有 MongoDB,CouchDB 等等;
    * 图形,图形数据库擅长处理任何涉及关系的状况。社交网络,推荐系统等。专注于构建关系图谱,需要对整个图做计算才能得出结果,不容易做分布式的集群方案,常见的有 Neo4J,InfoGrid 等。

    除了以上4种类型,还有一些特种的数据库,如对象数据库,XML 数据库,这些都有针对性对某些存储类型做了优化的数据库。

    在实际应用场景中,何时使用关系数据库,何时使用 NoSQL,使用哪种类型的数据库,这是我们在做架构选型时一个非常重要的考量,甚至会影响整个架构的方案。
    ##消息中间件
    消息中间件在后台系统中是必不可少的一个组件,一般我们会在以下场景中使用消息中间件:

    * 异步处理:异步处理是使用消息中间件的一个主要原因,在工作中最常见的异步场景有用户注册成功后需要发送注册成功邮件、缓存过期时先返回老的数据,然后异步更新缓存、异步写日志等等;通过异步处理,可以减少主流程的等待响应时间,让非主流程或者非重要业务通过消息中间件做集中的异步处理。
    * 系统解耦:比如在电商系统中,当用户成功支付完成订单后,需要将支付结果给通知ERP系统、发票系统、WMS、推荐系统、搜索系统、风控系统等进行业务处理;这些业务处理不需要实时处理、不需要强一致,只需要最终一致性即可,因此可以通过消息中间件进行系统解耦。通过这种系统解耦还可以应对未来不明确的系统需求。
    * 削峰填谷:当系统遇到大流量时,监控图上会看到一个一个的山峰样的流量图,通过使用消息中间件将大流量的请求放入队列,通过消费者程序将队列中的处理请求慢慢消化,达到消峰填谷的效果。最典型的场景是秒杀系统,在电商的秒杀系统中下单服务往往会是系统的瓶颈,因为下单需要对库存等做数据库操作,需要保证强一致性,此时使用消息中间件进行下单排队和流控,让下单服务慢慢把队列中的单处理完,保护下单服务,以达到削峰填谷的作用。

    业界消息中间件是一个非常通用的东西,大家在做选型时有使用开源的,也有自己造轮子的,甚至有直接用 MySQL 或 Redis 做队列的,关键看是否满足你的需求,如果是使用开源的项目,以下的表格在选型时可以参考:
    svr_stack3.png

    图 3

    以上图的纬度为:名字、成熟度、所属社区/公司、文档、授权方式、开发语言、支持的协议、客户端支持的语言、性能、持久化、事务、集群、负载均衡、管理界面、部署方式、评价。
    ##代码管理
    代码是互联网创业公司的命脉之一,代码管理很重要,常见的考量点包括两块:

    * 安全和权限管理,将代码放到内网并且对于关系公司命脉的核心代码做严格的代码控制和机器的物理隔离;
    * 代码管理工具,Git 作为代码管理的不二之选,你值得拥有。GitLab 是当今最火的开源 Git 托管服务端,没有之一,虽然有企业版,但是其社区版基本能满足我们大部分需求,结合 Gerrit 做 Code review,基本就完美了。当然 GitLab 也有代码对比,但没 Gerrit 直观。Gerrit 比 GitLab 提供了更好的代码检查界面与主线管理体验,更适合在对代码质量有高要求的文化下使用。

    ##持续集成
    持续集成简,称 CI(continuous integration),是一种软件开发实践,即团队开发成员经常集成他们的工作,每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。持续集成为研发流程提供了代码分支管理/比对、编译、检查、发布物输出等基础工作,为测试的覆盖率版本编译、生成等提供统一支持。

    业界免费的持续集成工具中系统我们有如下一些选择:

    * Jenkins:Java 写的有强大的插件机制,MIT 协议开源 (免费,定制化程度高,它可以在多台机器上进行分布式地构建和负载测试)。Jenkins 可以算是无所不能,基本没有 Jenkins 做不了的,无论从小型团队到大型团队 Jenkins 都可以搞定。不过如果要大规模使用,还是需要有人力来学习和维护。
    * TeamCity:TeamCity 与 Jenkins 相比使用更加友好,也是一个高度可定制化的平台。但是用的人多了,TeamCity就要收费了。
    * Strider:Strider 是一个开源的持续集成和部署平台,使用 Node.js 实现,存储使用的是 MongoDB,BSD 许可证,概念上类似 Travis 和Jenkins。
    * GitLab CI:从GitLab 8.0开始,GitLab CI 就已经集成在 GitLab,我们只要在项目中添加一个 .gitlab-ci.yml 文件,然后添加一个 Runner,即可进行持续集成。并且 GitLab 与 Docker 有着非常好的相互协作的能力。免费版与付费版本不同可以参见这里:https://about.gitlab.com/products/feature-comparison/。
    * Travis:Travis 和 GitHub 强关联;闭源代码使用 SaaS 还需考虑安全问题;不可定制;开源项目免费,其它收费。
    * Go:Go 是 ThoughtWorks 公司最新的 Cruise Control 的化身。除了 ThoughtWorks 提供的商业支持,Go 是免费的。它适用于 Windows,Mac 和各种 Linux 发行版。

    ##日志系统
    日志系统一般包括打日志,采集,中转,收集,存储,分析,呈现,搜索还有分发等。一些特殊的如染色,全链条跟踪或者监控都可能需要依赖于日志系统实现。日志系统的建设不仅仅是工具的建设,还有规范和组件的建设,最好一些基本的日志在框架和组件层面加就行了,比如全链接跟踪之类的。

    对于常规日志系统ELK能满足大部分的需求,ELK 包括如下组件:

    * ElasticSearch 是个开源分布式搜索引擎,它的特点有:分布式,零配置,自动发现,索引自动分片,索引副本机制,RESTful 风格接口,多数据源,自动搜索负载等。
    * Logstash 是一个完全开源的工具,它可以对你的日志进行收集、分析,并将其存储供以后使用。
    * Kibana 是一个开源和免费的工具,它可以为 Logstash 和 ElasticSearch 提供的日志分析友好的 Web 界面,可以帮助汇总、分析和搜索重要数据日志。
    * Filebeat 已经完全替代了 Logstash-Forwarder 成为新一代的日志采集器,同时鉴于它轻量、安全等特点,越来越多人开始使用它。

    因为免费的 ELK 没有任何安全机制,所以这里使用了 Nginx 作反向代理,避免用户直接访问 Kibana 服务器。加上配置 Nginx 实现简单的用户认证,一定程度上提高安全性。另外,Nginx 本身具有负载均衡的作用,能够提高系统访问性能。ELK 架构如图4所示:
    svr_stack4_elk.png

    图 4,ELK 流程图

    对于有实时计算的需求,可以使用 Flume + Kafka + Storm + MySQL 方案,一 般架构如图 5 所示:
    svr_stack5.png

    图 5,实时分析系统架构图

    其中:

    * Flume 是一个分布式、可靠、和高可用的海量日志采集、聚合和传输的日志收集系统,支持在日志系统中定制各类数据发送方,用于收集数据;同时,Flume 提供对数据进行简单处理,并写到各种数据接受方(可定制)的能力。
    * Kafka 是由 Apache 软件基金会开发的一个开源流处理平台,由 Scala 和 Java 编写。其本质上是一个“按照分布式事务日志架构的大规模发布/订阅消息队列”,它以可水平扩展和高吞吐率而被广泛使用。

    Kafka 追求的是高吞吐量、高负载,Flume 追求的是数据的多样性,二者结合起来简直完美。
    ##监控系统
    监控系统只包含与后台相关的,这里主要是两块,一个是操作系统层的监控,比如机器负载,IO,网络流量,CPU,内存等操作系统指标的监控。另一个是服务质量和业务质量的监控,比如服务的可用性,成功率,失败率,容量,QPS 等等。常见业务的监控系统先有操作系统层面的监控(这部分较成熟),然后扩展出其它监控,如 Zabbix,小米的 Open-Falcon,也有一出来就是两者都支持的,如 Prometheus。如果对业务监控要求比较高一些,在创业选型中建议可以优先考虑 Prometheus。这里有一个有趣的分布,如图6所示。
    svr_stack6.png

    图 6,监控系统分布

    亚洲区域使用 Zabbix 较多,而美洲和欧洲,以及澳大利亚使用 Prometheus 居多,换句话说,英文国家地区(发达国家?)使用 Prometheus 较多。

    Prometheus 是由 SoundCloud 开发的开源监控报警系统和时序列数据库(TSDB)。Prometheus 使用 Go 语言开发,是 Google BorgMon 监控系统的开源版本。相对于其它监控系统使用的 push 数据的方式,Prometheus 使用的是 pull 的方式,其架构如图 7 所示:
    svr_stack7.png

    图 7,Prometheus 架构图

    如上图所示,Prometheus 包含的主要组件如下:

    * Prometheus Server 主要负责数据采集和存储,提供 PromQL 查询语言的支持。Server 通过配置文件、文本文件、ZooKeeper、Consul、DNS SRV Lookup 等方式指定抓取目标。根据这些目标会,Server 定时去抓取 metrics 数据,每个抓取目标需要暴露一个 http 服务的接口给它定时抓取。
    * 客户端 SDK:官方提供的客户端类库有 Go、Java、Scala、Python、Ruby,其他还有很多第三方开发的类库,支持 Nodejs、PHP、Erlang 等。
    * Push Gateway 支持临时性 Job 主动推送指标的中间网关。
    * Exporter Exporter 是 Prometheus 的一类数据采集组件的总称。它负责从目标处搜集数据,并将其转化为 Prometheus 支持的格式。与传统的数据采集组件不同的是,它并不向中央服务器发送数据,而是等待中央服务器主动前来抓取。Prometheus 提供多种类型的 Exporter 用于采集各种不同服务的运行状态。目前支持的有数据库、硬件、消息中间件、存储系统、HTTP 服务器、JMX 等。
    * Alertmanager:是一个单独的服务,可以支持 Prometheus 的查询语句,提供十分灵活的报警方式。
    * Prometheus HTTP API 的查询方式,自定义所需要的输出。
    * Grafana 是一套开源的分析监视平台,支持 Graphite,InfluxDB,OpenTSDB,Prometheus,Elasticsearch,CloudWatch 等数据源,其 UI 非常漂亮且高度定制化。

    创业公司选择 Prometheus + Grafana 的方案,再加上统一的服务框架(如 gRPC),可以满足大部分中小团队的监控需求。
    ##配置系统
    随着程序功能的日益复杂,程序的配置日益增多:各种功能的开关、降级开关,灰度开关,参数的配置、服务器的地址、数据库配置等等,除此之外,对后台程序配置的要求也越来越高:配置修改后实时生效,灰度发布,分环境、分用户,分集群管理配置,完善的权限、审核机制等等,在这样的大环境下,传统的通过配置文件、数据库等方式已经越来越无法满足开发人员对配置管理的需求,业界有如下两种方案:

    * 基于 zk 和 etcd,支持界面和 api ,用数据库来保存版本历史,预案,走审核流程,最后下发到 zk 或 etcd 这种有推送能力的存储里(服务注册本身也是用 zk 或 etcd,选型就一块了)。客户端都直接和 zk 或 etcd 打交道。至于灰度发布,各家不同,有一种实现是同时发布一个需要灰度的 IP 列表,客户端监听到配置节点变化时,对比一下自己是否属于该列表。PHP 这种无状态的语言和其他 zk/etcd 不支持的语言,只好自己在客户端的机器上起一个 Agent 来监听变化,再写到配置文件或共享内存,如 360 的 Qconf。
    * 基于运维自动化的配置文件的推送,审核流程,配置数据管理和方案一类似,下发时生成配置文件,基于运维自动化工具如 Puppet,Ansible 推送到每个客户端,而应用则定时重新读取这个外部的配置文件,灰度发布在下发配置时指定IP列表。

    创业公司前期不需要这种复杂,直接上 zk,弄一个界面管理 zk 的内容,记录一下所有人的操作日志,程序直连 zk,或者或者用 Qconf 等基于 zk 优化后的方案。
    ##发布系统/部署系统
    从软件生产的层面看,代码到最终服务的典型流程如图 8 所示:
    svr_stack8.png

    图 8,流程图

    从上图中可以看出,从开发人员写下代码到服务最终用户是一个漫长过程,整体可以分成三个阶段:

    * 从代码(Code)到成品库(Artifact)这个阶段主要对开发人员的代码做持续构建并把构建产生的制品集中管理,是为部署系统准备输入内容的阶段。
    * 从制品到可运行服务 这个阶段主要完成制品部署到指定环境,是部署系统的最基本工作内容。
    * 从开发环境到最终生产环境 这个阶段主要完成一次变更在不同环境的迁移,是部署系统上线最终服务的核心能力。

    发布系统集成了制品管理,发布流程,权限控制,线上环境版本变更,灰度发布,线上服务回滚等几方面的内容,是开发人员工作结晶最终呈现的重要通道。开源的项目中没有完全满足的项目,如果只是 Web 类项目,Walle、Piplin 都是可用的,但是功能不太满足,创业初期可以集成 Jenkins + Gitlab + Walle(可以考虑两天时间完善一下),以上方案基本包括制品管理,发布流程,权限控制,线上环境版本变更,灰度发布(需要自己实现),线上服务回滚等功能。
    ##跳板机
    跳板机面对的是需求是要有一种能满足角色管理与授权审批、信息资源访问控制、操作记录和审计、系统变更和维护控制要求,并生成一些统计报表配合管理规范来不断提升IT内控的合规性,能对运维人员操作行为的进行控制和审计,对误操作、违规操作导致的操作事故,快速定位原因和责任人。其功能模块一般包括:帐户管理、认证管理、授权管理、审计管理等等。

    开源项目中,Jumpserver 能够实现跳板机常见需求,如授权、用户管理、服务器基本信息记录等,同时又可批量执行脚本等功能;其中录像回放、命令搜索、实时监控等特点,又能帮助运维人员回溯操作历史,方便查找操作痕迹,便于管理其他人员对服务器的操作控制。
    ##机器管理
    机器管理的工具选择的考量可以包含以下三个方面:

    1. 是否简单,是否需要每台机器部署 Agent(客户端)
    2. 语言的选择(Puppet/Chef vs Ansible/SaltStack )开源技术,不看官网不足以熟练,不懂源码不足以精通;Puppet、Chef 基于 Ruby 开发,Ansible、SaltStack 基于 Python 开发的
    3. 速度的选择(Ansible vs SaltStack)Ansible 基于 SSH 协议传输数据,SaltStack 使用消息队列 zeroMQ 传输数据;大规模并发的能力对于几十台-200 台规模的兄弟来讲,Ansible的性能也可接受,如果一次操作上千台,用 salt 好一些。

    如图9所示:
    svr_statck9.png

    图 9,机器管理软件对比

    一般创业公司选择 Ansible 能解决大部问题,其简单,不需要安装额外的客户端,可以从命令行来运行,不需要使用配置文件。至于比较复杂的任务,Ansible 配置通过名为 Playbook 的配置文件中的 YAML 语法来加以处理。Playbook 还可以使用模板来扩展其功能。
    #创业公司的选择
    ##选择合适的语言

    * 选择团队熟悉的/能掌控的,创业公司人少事多,无太多冗余让研发团队熟悉新的语言,能快速上手,能快速出活,出了问题能快速解决的问题的语言才是好的选择。
    * 选择更现代一些的,这里的现代是指语言本身已经完成一些之前需要特殊处理的特性,比如内存管理,线程等等。
    * 选择开源轮子多的或者社区活跃度高的,这个原则是为了保证在开发过程中减少投入,有稳定可靠的轮子可以使用,遇到问题可以在网上快速搜索到答案。
    * 选择好招人的 一门合适的语言会让创业团队减少招聘的成本,快速招到合适的人。
    * 选择能让人有兴趣的 与上面一点相关,让人感兴趣,在后面留人时有用。

    ##选择合适的组件和云服务商

    * 选择靠谱的云服务商;
    * 选择云服务商的组件;
    * 选择成熟的开源组件,而不是最新出的组件;
    * 选择采用在一线互联网公司落地并且开源的,且在社区内形成良好口碑的产品;
    * 开源社区活跃度;

    选择靠谱的云服务商,其实这是一个伪命题,因为哪个服务商都不靠谱,他们所承诺的那些可用性问题基本上都会在你的身上发生,这里我们还是需要自己做一些工作,比如多服务商备份,如用 CDN,你一定不要只选一家,至少选两家,一个是灾备,保持后台切换的能力,另一个是多点覆盖,不同的服务商在 CDN 节点上的资源是不一样的。

    选择了云服务商以后,就会有很多的产品你可以选择了,比较存储,队列这些都会有现成的产品,这个时候就纠结了,是用呢?还是自己在云主机上搭呢?在这里我的建议是前期先用云服务商的,大了后再自己搞,这样会少掉很多运维的事情,但是这里要多了解一下云服务商的组件特性以及一些坑,比如他们内网会经常断开,他们升级也会闪断,所以在业务侧要做好容错和规避。

    关于开源组件,尽可能选择成熟的,成熟的组件经历了时间的考验,基本不会出大的问题,并且有成套的配套工具,出了问题在网上也可以很快的找到答案,你所遇到的坑基本上都有人踩过了。
    ##制定流程和规范

    * 制定开发的规范,代码及代码分支管理规范,关键性代码仅少数人有权限;
    * 制定发布流程规范,从发布系统落地;
    * 制定运维规范;
    * 制定数据库操作规范,收拢数据库操作权限;
    * 制定告警处理流程,做到告警有人看有人处理;
    * 制定汇报机制,晨会/周报;

    ##自研和选型合适的辅助系统
    所有的流程和规范都需要用系统来固化,否则就是空中楼阁,如何选择这些系统呢?参照上个章节咱们那些开源的,对比一下选择的语言,组件之类的,选择一个最合适的即可。

    比如项目管理的,看下自己是什么类型的公司,开发的节奏是怎样的,瀑布,敏捷的 按项目划分,还是按客户划分等等,平时是按项目组织还是按任务组织等等

    比如日志系统,之前是打的文本,那么上一个 ELK,规范化一些日志组件,基本上很长一段时间内不用考虑日志系统的问题,最多拆分一下或者扩容一下。等到组织大了,自己搞一个日志系统。

    比如代码管理,项目管理系统这些都放内网,安全,在互联网公司来说,属于命脉了,命脉的东西还是放在别人拿不到或很难拿到的地方会比较靠谱一些。
    ##选择过程中需要思考的问题
    技术栈的选择有点像做出了某种承诺,在一定的时间内这种承诺没法改变,于是我们需要在选择的时候有一些思考。

    看前面内容,有一个词出现了三次,合适,选择是合适的,不是最好,也不是最新,是最合适,适合是针对当下,这种选择是最合适的吗?比如用 Go 这条线的东西,技术比较新,业界组件储备够吗?组织内的人员储备够吗?学习成本多少?写出来的东西能满足业务性能要求吗?能满足时间要求吗?

    向未来看一眼,在一年到三年内,我们需要做出改变吗?技术栈要做根本性的改变吗?如果组织发展很快,在 200 人,500 人时,现有的技术栈是否需要大动?

    创业过程中需要考虑成本,这里的成本不仅仅是花费多少钱,付出多少工资,有时更重要的是时间成本,很多业务在创业时大家拼的就是时间,就是一个时间窗,过了就没你什么事儿了。
    #基于云的创业公司后台技术架构
    结合上面内容的考量,在对一个个系统和组件的做选型之后,以云服务为基础,一个创业公司的后台技术架构如图10所示:
    svr_stack10.png

    图 10,后台技术架构

    参考资料:

    * http://database.51cto.com/art/201109/291781.htm
    * https://zh.wikipedia.org/wiki/Kafka
    * https://prometheus.io/docs/introduction/overview/
    * http://deadline.top/2016/11/23/配置中心那点事/
    * http://blog.fit2cloud.com/2016/01/26/deployment-system.html

    原文链接:从零开始搭建创业公司后台技术栈(作者:潘锦)

    分布式协调神器 ZooKeeper 之整体概述

    大卫 发表了文章 • 0 个评论 • 696 次浏览 • 2019-03-04 20:50 • 来自相关话题

    ZooKeeper 最早起源于雅虎研究院的一个研究小组。当时,雅虎内部很多大型系统基本都需要依赖一个类似的系统来进行分布式协调,但是这些系统往往都存在分布式单点问题。所以,雅虎的开发人员就试图开发一个通用的无单点问题的分布式协调框架,以便让开发人员将精力集中在 ...查看全部
    ZooKeeper 最早起源于雅虎研究院的一个研究小组。当时,雅虎内部很多大型系统基本都需要依赖一个类似的系统来进行分布式协调,但是这些系统往往都存在分布式单点问题。所以,雅虎的开发人员就试图开发一个通用的无单点问题的分布式协调框架,以便让开发人员将精力集中在处理业务逻辑上。

    立项初期,考虑到之前内部很多项目都是使用动物的名字来命名的(例如著名的 Pig 项目),雅虎的工程师希望给这个项目也取一个动物的名字。当时研究院的首席科学家 RaghuRamakrishnan 开玩笑说:“再这样下去,我们这儿就变成动物园了!”是不是很有趣,顺势大家就表示既然已经是动物园了,它就叫动物园管理员吧!各个以动物命名的分布式组件放在一起,雅虎的整个分布式系统看上去就像一个大型的动物园了,而 ZooKeeper 正好要用来进行分布式环境的协调一一于是,ZooKeeper 的名字也就由此诞生了!
    #ZooKeeper概述

    ZooKeeper 是一种用于分布式应用程序的分布式开源协调服务。它公开了一组简单的原语,分布式应用程序可以构建这些原语,以实现更高级别的服务,以实现同步,配置维护以及组和命名。它被设计为易于编程,并使用在熟悉的文件系统目录树结构之后设计的数据模型。它在Java中运行,并且具有Java和C的绑定。

    众所周知,协调服务很难做到。他们特别容易出现诸如竞争条件和死锁等错误。ZooKeeper 背后的动机是减轻分布式应用程序从头开始实施协调服务的责任。
    #集群模型

    1.png

    Leader 服务器是整个 ZooKeeper 集群工作机制中的核心,其主要工作有以下两个:

    1. 事务请求的唯一调度和处理者,保证集群事务处理的顺序性。
    2. 集群内部各服务器的调度者。

    从角色名字上可以看出,Follewer 服务器是 ZooKeeper 集群状态的跟随者,其主要工作有以下三个:

    1. 处理客户端非事务请求,转发事务请求给 Leader 服务器。
    2. 参与事务请求 Proposal 的投票。
    3. 参与 Leader 选举投票。

    Observer 充当了一个观察者的角色,在工作原理上基本和 Follower 一致,唯一的区别在于,它不参与任何形式的投票。
    #数据结构

    2.png

    #树形结构

    首先我们来看上述数据节点示意图,从而对 ZooKeeper 上的数据节点有一个大体上的认识,在 ZooKeeper 中,每一个节点都被称为一个 ZNode,所有 ZNode 按层次化机构进行组织,形成一棵树。ZNode 节点路径标识方式和 Unix 文件系统路径非常相似,都是由一系列使用斜杠(/)进行分割的路径表示,开发人员可以向这个节点中写入数据,也可以在节点下面创建子节点。
    #节点操作流程

    3.png


    1. 在 Client 向 Follower 发出一个写请求。
    2. Follower 把请求转发给 Leader。
    3. Leader 接收到以后开始发起投票并通知 Follower 进行投票。
    4. Follower 把投票结果发送给 Leader。
    5. Leader 将结果汇总后,如果需要写入,则开始写入,同时把写入操作通知给 Follower,然后 commit。
    6. Follower 把请求结果返回给 Client。

    #设计目标


    1. 顺序一致性,来自任意特定客户端的更新都会按其发送顺序被提交。也就是说,如果一个客户端将 Znode z 的值更新为 a,在之后的操作中,它又将 z 的值更新为 b,则没有客户端能够在看到z的值是b之后再看到值 a(如果没有其他对z的更新)。
    2. 原子性,每个更新要么成功,要么失败。这意味着如果一个更新失败,则不会有客户端会看到这个更新的结果。
    3. 单一系统映像,一个客户端无论连接到哪一台服务器,它看到的都是同样的系统视图。这意味着,如果一个客户端在同一个会话中连接到一台新的服务器,它所看到的系统状态不会比 在之前服务器上所看到的更老。当一台服务器出现故障,导致它的一个客户端需要尝试连接集合体中其他的服务器时,所有滞后于故障服务器的服务器都不会接受该 连接请求,除非这些服务器赶上故障服务器。
    4. 持久性,一个更新一旦成功,其结果就会持久存在并且不会被撤销。这表明更新不会受到服务器故障的影响。

    #整体架构

    4.png


    * ServerCnxnFactory,ZooKeeper服务端网络连接工厂。在早期版本中,ZooKeeper 都是自己实现 NIO 框架,从 3.4.0 版本开始,引入了 Netty。可以通过 zookeeper.serverCnxnFactory 来指定使用具体的实现。
    * SessionTracker,ZooKeeper 服务端会话管理器。创建时,会初始化 expirationInterval、nextExpirationTime、sessionsWithTimeout(用于保存每个会话的超时时间),同时还会计算出一个初始化的sessionID。
    * RequestProcessor,ZooKeeper的请求处理方式是典型的责任链模式,在服务端,会有多个请求处理器依次来处理一个客户的请求。在服务器启动的时候,会将这些请求处理器串联起来形成一个请求处理链。基本的请求处理链如下:
    5.png

    * LearnerCnxAcceptor,Learner服务器(等于 Follower 服务器)连接请求接收器。负责 Leader 服务器和 Follower 服务器保持连接,以确定集群机器存活情况,并处理连接请求。
    * LearnerHandler,Leader 接收来自其他机器的连接创建请求后,会创建一个 LearnerHandler 实例。每个 LearnerHandler 实例都对应了一个 Leader 和 Learner 服务器之间的连接,其负责 Leader 和 Learner 服务器之间几乎所有的消息通信和数据同步。
    * ZKDatabase,ZooKeeper 内存数据库,负责管理 ZooKeeper 的所有会话记录以及 DataTree 和事务日志的存储。
    * FileTxnSnapLog,ZooKeeper 上层服务和底层数据存储之间的对接层,提供了一系列的操作数据文件的接口,包括事务文件和快照数据文件。ZooKeeper 根据 zoo.cfg 文件中解析出的快照数据目录 dataDir 和事务日志目录 dataLogDir 来创建 FileTxnSnapLog。
    * LeaderElection,ZooKeeper 会根据 zoo.cfg 中的配置,创建相应的 Leader 选举算法实现。在 ZooKeeper 中,默认提供了三种 Leader 选举算法的实现,分别是 LeaderElection、AuthFastLeaderElection、FastLeaderElection,可以通过配置文件中 electionAlg 属性来指定,分别用 0 ~ 3 来表示。从 3.4.0 版本开始,ZooKeeper 废弃了前两种算法,只支持 FastLeaderEletion 选举算法。

    原文链接:http://rdc.hundsun.com/portal/article/952.html

    详解分布式协调服务 ZooKeeper

    JetLee 发表了文章 • 0 个评论 • 1424 次浏览 • 2018-11-24 17:30 • 来自相关话题

    解析分布式锁之Zookeeper实现(一)

    老李 发表了文章 • 0 个评论 • 1783 次浏览 • 2018-03-27 22:20 • 来自相关话题

    实现分布式锁目前有三种流行方案,分别为基于数据库、Redis、Zookeeper的方案,本文主要阐述基于Zookeeper的分布式锁,其他两种会在后文中一起探讨。现在我们来看下使用Zookeeper如何实现分布式锁。 什么是Zooke ...查看全部
    实现分布式锁目前有三种流行方案,分别为基于数据库、Redis、Zookeeper的方案,本文主要阐述基于Zookeeper的分布式锁,其他两种会在后文中一起探讨。现在我们来看下使用Zookeeper如何实现分布式锁。

    什么是Zookeeper?

    Zookeeper(业界简称zk)是一种提供配置管理、分布式协同以及命名的中心化服务,这些提供的功能都是分布式系统中非常底层且必不可少的基本功能,但是如果自己实现这些功能而且要达到高吞吐、低延迟同时还要保持一致性和可用性,实际上非常困难。因此zookeeper提供了这些功能,开发者在zookeeper之上构建自己的各种分布式系统。

    虽然zookeeper的实现比较复杂,但是它提供的模型抽象却是非常简单的。Zookeeper提供一个多层级的节点命名空间(节点称为znode),每个节点都用一个以斜杠(/)分隔的路径表示,而且每个节点都有父节点(根节点除外),非常类似于文件系统。例如,/foo/doo这个表示一个znode,它的父节点为/foo,父父节点为/,而/为根节点没有父节点。与文件系统不同的是,这些节点都可以设置关联的数据,而文件系统中只有文件节点可以存放数据而目录节点不行。Zookeeper为了保证高吞吐和低延迟,在内存中维护了这个树状的目录结构,这种特性使得Zookeeper不能用于存放大量的数据,每个节点的存放数据上限为1M。

    而为了保证高可用,zookeeper需要以集群形态来部署,这样只要集群中大部分机器是可用的(能够容忍一定的机器故障),那么zookeeper本身仍然是可用的。客户端在使用zookeeper时,需要知道集群机器列表,通过与集群中的某一台机器建立TCP连接来使用服务,客户端使用这个TCP链接来发送请求、获取结果、获取监听事件以及发送心跳包。如果这个连接异常断开了,客户端可以连接到另外的机器上。

    架构简图如下所示:


    客户端的读请求可以被集群中的任意一台机器处理,如果读请求在节点上注册了监听器,这个监听器也是由所连接的zookeeper机器来处理。对于写请求,这些请求会同时发给其他zookeeper机器并且达成一致后,请求才会返回成功。因此,随着zookeeper的集群机器增多,读请求的吞吐会提高但是写请求的吞吐会下降。

    有序性是zookeeper中非常重要的一个特性,所有的更新都是全局有序的,每个更新都有一个唯一的时间戳,这个时间戳称为zxid(Zookeeper Transaction Id)。而读请求只会相对于更新有序,也就是读请求的返回结果中会带有这个zookeeper最新的zxid。

    如何使用zookeeper实现分布式锁?

    在描述算法流程之前,先看下zookeeper中几个关于节点的有趣的性质:

    有序节点:假如当前有一个父节点为/lock,我们可以在这个父节点下面创建子节点;zookeeper提供了一个可选的有序特性,例如我们可以创建子节点“/lock/node-”并且指明有序,那么zookeeper在生成子节点时会根据当前的子节点数量自动添加整数序号,也就是说如果是第一个创建的子节点,那么生成的子节点为/lock/node-0000000000,下一个节点则为/lock/node-0000000001,依次类推。

    临时节点:客户端可以建立一个临时节点,在会话结束或者会话超时后,zookeeper会自动删除该节点。

    事件监听:在读取数据时,我们可以同时对节点设置事件监听,当节点数据或结构变化时,zookeeper会通知客户端。当前zookeeper有如下四种事件:1)节点创建;2)节点删除;3)节点数据修改;4)子节点变更。

    下面描述使用zookeeper实现分布式锁的算法流程,假设锁空间的根节点为/lock:

    客户端连接zookeeper,并在/lock下创建临时的且有序的子节点,第一个客户端对应的子节点为/lock/lock-0000000000,第二个为/lock/lock-0000000001,以此类推。

    客户端获取/lock下的子节点列表,判断自己创建的子节点是否为当前子节点列表中序号最小的子节点,如果是则认为获得锁,否则监听/lock的子节点变更消息,获得子节点变更通知后重复此步骤直至获得锁;

    执行业务代码;

    完成业务流程后,删除对应的子节点释放锁。

    步骤1中创建的临时节点能够保证在故障的情况下锁也能被释放,考虑这么个场景:假如客户端a当前创建的子节点为序号最小的节点,获得锁之后客户端所在机器宕机了,客户端没有主动删除子节点;如果创建的是永久的节点,那么这个锁永远不会释放,导致死锁;由于创建的是临时节点,客户端宕机后,过了一定时间zookeeper没有收到客户端的心跳包判断会话失效,将临时节点删除从而释放锁。

    另外细心的朋友可能会想到,在步骤2中获取子节点列表与设置监听这两步操作的原子性问题,考虑这么个场景:客户端a对应子节点为/lock/lock-0000000000,客户端b对应子节点为/lock/lock-0000000001,客户端b获取子节点列表时发现自己不是序号最小的,但是在设置监听器前客户端a完成业务流程删除了子节点/lock/lock-0000000000,客户端b设置的监听器岂不是丢失了这个事件从而导致永远等待了?这个问题不存在的。因为zookeeper提供的API中设置监听器的操作与读操作是原子执行的,也就是说在读子节点列表时同时设置监听器,保证不会丢失事件。

    最后,对于这个算法有个极大的优化点:假如当前有1000个节点在等待锁,如果获得锁的客户端释放锁时,这1000个客户端都会被唤醒,这种情况称为“羊群效应”;在这种羊群效应中,zookeeper需要通知1000个客户端,这会阻塞其他的操作,最好的情况应该只唤醒新的最小节点对应的客户端。应该怎么做呢?在设置事件监听时,每个客户端应该对刚好在它之前的子节点设置事件监听,例如子节点列表为/lock/lock-0000000000、/lock/lock-0000000001、/lock/lock-0000000002,序号为1的客户端监听序号为0的子节点删除消息,序号为2的监听序号为1的子节点删除消息。

    所以调整后的分布式锁算法流程如下:

    客户端连接zookeeper,并在/lock下创建临时的且有序的子节点,第一个客户端对应的子节点为/lock/lock-0000000000,第二个为/lock/lock-0000000001,以此类推。

    客户端获取/lock下的子节点列表,判断自己创建的子节点是否为当前子节点列表中序号最小的子节点,如果是则认为获得锁,否则监听刚好在自己之前一位的子节点删除消息,获得子节点变更通知后重复此步骤直至获得锁;

    执行业务代码;

    完成业务流程后,删除对应的子节点释放锁。

    Curator的源码分析

    虽然zookeeper原生客户端暴露的API已经非常简洁了,但是实现一个分布式锁还是比较麻烦的…我们可以直接使用curator这个开源项目提供的zookeeper分布式锁实现。

    我们只需要引入下面这个包(基于maven):

    org.apache.curator

    curator-recipes

    4.0.0

    然后就可以用啦!代码如下:

    publicstaticvoidmain(String[]args)throwsException{

    //创建zookeeper的客户端

    RetryPolicyretryPolicy=newExponentialBackoffRetry(1000,3);

    CuratorFrameworkclient=CuratorFrameworkFactory.newClient("10.21.41.181:2181,10.21.42.47:2181,10.21.49.252:2181",retryPolicy);

    client.start();

    //创建分布式锁, 锁空间的根节点路径为/curator/lock

    InterProcessMutexmutex=newInterProcessMutex(client,"/curator/lock");

    mutex.acquire();

    //获得了锁, 进行业务流程

    System.out.println("Enter mutex");

    //完成业务流程, 释放锁

    mutex.release();

    //关闭客户端

    client.close();

    }

    可以看到关键的核心操作就只有mutex.acquire()和mutex.release(),简直太方便了!

    下面来分析下获取锁的源码实现。acquire的方法如下:

    /*

    * 获取锁,当锁被占用时会阻塞等待,这个操作支持同线程的可重入(也就是重复获取锁),acquire的次数需要与release的次数相同。

    * @throws Exception ZK errors, connection interruptions

    */

    @Override

    publicvoidacquire()throwsException

    {

    if(!internalLock(-1,null))

    {

    thrownewIOException("Lost connection while trying to acquire lock: "+basePath);

    }

    }

    这里有个地方需要注意,当与zookeeper通信存在异常时,acquire会直接抛出异常,需要使用者自身做重试策略。代码中调用了internalLock(-1, null),参数表明在锁被占用时永久阻塞等待。internalLock的代码如下:

    privatebooleaninternalLock(longtime,TimeUnitunit)throwsException

    {

    //这里处理同线程的可重入性,如果已经获得锁,那么只是在对应的数据结构中增加acquire的次数统计,直接返回成功

    ThreadcurrentThread=Thread.currentThread();

    LockDatalockData=threadData.get(currentThread);

    if(lockData!=null)

    {

    // re-entering

    lockData.lockCount.incrementAndGet();

    returntrue;

    }

    //这里才真正去zookeeper中获取锁

    StringlockPath=internals.attemptLock(time,unit,getLockNodeBytes());

    if(lockPath!=null)

    {

    //获得锁之后,记录当前的线程获得锁的信息,在重入时只需在LockData中增加次数统计即可

    LockDatanewLockData=newLockData(currentThread,lockPath);

    threadData.put(currentThread,newLockData);

    returntrue;

    }

    //在阻塞返回时仍然获取不到锁,这里上下文的处理隐含的意思为zookeeper通信异常

    returnfalse;

    }

    代码中增加了具体注释,不做展开。看下zookeeper获取锁的具体实现:

    StringattemptLock(longtime,TimeUnitunit,byte[]lockNodeBytes)throwsException

    {

    //参数初始化,此处省略

    //...

    //自旋获取锁

    while(!isDone)

    {

    isDone=true;

    try

    {

    //在锁空间下创建临时且有序的子节点

    ourPath=driver.createsTheLock(client,path,localLockNodeBytes);

    //判断是否获得锁(子节点序号最小),获得锁则直接返回,否则阻塞等待前一个子节点删除通知

    hasTheLock=internalLockLoop(startMillis,millisToWait,ourPath);

    }

    catch(KeeperException.NoNodeExceptione)

    {

    //对于NoNodeException,代码中确保了只有发生session过期才会在这里抛出NoNodeException,因此这里根据重试策略进行重试

    if(client.getZookeeperClient().getRetryPolicy().allowRetry(retryCount++,System.currentTimeMillis()-startMillis,RetryLoop.getDefaultRetrySleeper()))

    {

    isDone=false;

    }

    else

    {

    throwe;

    }

    }

    }

    //如果获得锁则返回该子节点的路径

    if(hasTheLock)

    {

    returnourPath;

    }

    returnnull;

    }

    上面代码中主要有两步操作:

    driver.createsTheLock:创建临时且有序的子节点,里面实现比较简单不做展开,主要关注几种节点的模式:1)PERSISTENT(永久);2)PERSISTENT_SEQUENTIAL(永久且有序);3)EPHEMERAL(临时);4)EPHEMERAL_SEQUENTIAL(临时且有序)。

    internalLockLoop:阻塞等待直到获得锁。

    看下internalLockLoop是怎么判断锁以及阻塞等待的,这里删除了一些无关代码,只保留主流程:

    //自旋直至获得锁

    while((client.getState()==CuratorFrameworkState.STARTED)&&!haveTheLock)

    {

    //获取所有的子节点列表,并且按序号从小到大排序

    Listchildren=getSortedChildren();

    //根据序号判断当前子节点是否为最小子节点

    StringsequenceNodeName=ourPath.substring(basePath.length()+1);// +1 to include the slash

    PredicateResultspredicateResults=driver.getsTheLock(client,children,sequenceNodeName,maxLeases);

    if(predicateResults.getsTheLock())

    {

    //如果为最小子节点则认为获得锁

    haveTheLock=true;

    }

    else

    {

    //否则获取前一个子节点

    StringpreviousSequencePath=basePath+"/"+predicateResults.getPathToWatch();

    //这里使用对象监视器做线程同步,当获取不到锁时监听前一个子节点删除消息并且进行wait(),当前一个子节点删除(也就是锁释放)时,回调会通过notifyAll唤醒此线程,此线程继续自旋判断是否获得锁

    synchronized(this)

    {

    try

    {

    //这里使用getData()接口而不是checkExists()是因为,如果前一个子节点已经被删除了那么会抛出异常而且不会设置事件监听器,而checkExists虽然也可以获取到节点是否存在的信息但是同时设置了监听器,这个监听器其实永远不会触发,对于zookeeper来说属于资源泄露

    client.getData().usingWatcher(watcher).forPath(previousSequencePath);

    //如果设置了阻塞等待的时间

    if(millisToWait!=null)

    {

    millisToWait-=(System.currentTimeMillis()-startMillis);

    startMillis=System.currentTimeMillis();

    if(millisToWait<=0)

    {

    doDelete=true;// 等待时间到达,删除对应的子节点

    break;

    }

    //等待相应的时间

    wait(millisToWait);

    }

    else

    {

    //永远等待

    wait();

    }

    }

    catch(KeeperException.NoNodeExceptione)

    {

    //上面使用getData来设置监听器时,如果前一个子节点已经被删除那么会抛出NoNodeException,只需要自旋一次即可,无需额外处理

    }

    }

    }

    }

    具体逻辑见注释,不再赘述。代码中设置的事件监听器,在事件发生回调时只是简单的notifyAll唤醒当前线程以重新自旋判断,比较简单不再展开。




    总结:

    以上就是基于Zookeeper的分布式锁内容,在我的下一篇文章里,我会向大家阐述基于Redis的分布式锁,有兴趣的朋友可以点赞关注一下,实时获取最新的资料。

    关于分布式任务调度平台,数人云的经验都在这里了

    Dataman数人科技 发表了文章 • 0 个评论 • 2682 次浏览 • 2017-09-19 10:06 • 来自相关话题

    分布式任务调度平台是目前很多公司研究的方向,今天小数就给大家分享一下数人云分布式任务调度平台(Octopus)的一些思考与实践。 今天主要分享下批量处理平台的技术心得,批量处理从片面的角度讲类似于Linux系统中的Cron Table ...查看全部
    分布式任务调度平台是目前很多公司研究的方向,今天小数就给大家分享一下数人云分布式任务调度平台(Octopus)的一些思考与实践。

    今天主要分享下批量处理平台的技术心得,批量处理从片面的角度讲类似于Linux系统中的Cron Table,从大方向去看属于批量业务的调度平台,此次依托数人云3年来对容器技术的积累和对批量处理开源项目的整合过程,在这里和大家探讨一下实践分布式任务调度的心路历程。

    从理论上来说,做分布式系统并非企图加快单一任务处理速度,而是通过并行的方式合理利用资源,通过加大对任务的同时批量处理业务容量来加快业务的运营速度,举个典型的例子:当前视频直播中需要的视频文件解码,就是一种典型的批量处理业务。

    本来,批量处理业务和容器技术的关系并不紧密,我们通过跨领域的交叉设计,希望通过容器技术的封装能力帮助批处理系统快速建立更多的高密度批量处理运行时环境,更高效地利用资源。

    定时任务无处不在,在多任务处理时如何进行秒级调度?与容器如何碰撞?这是我比较关注的主题,个人的角度来看,批量处理系统的痛点有四个维度是用户比较关注的:

    • 弹性伸缩
    • 故障处理
    • 运维体系
    • 性能指标
    以金融行业为例,在金融行业高速发展的今天,业务规模快速扩展,随着业务的发展,需处理的数据量越来越大,后台批量处理业务占60%以上,如数据接口导入、数据预处理、估值表生成、凭证生成、对账、日终批处理、报表生成等。从用户关注的4个维度切入问题主要表现在:弹性扩缩:批量处理作业只能运行在一个服务节点上,无法适应业务发展,这是目前业务系统的最大性能瓶颈点。故障处理:当作业发生故障时,未实现故障自动转移,严重时影响业务进展。性能指标:[list=1]
  • 作业调度与作业执行线程耦合在一起,随着作业规模的增长,严重影响系统性能,也给开发和运维带来了一定的难度。
  • 联机业务和批量处理业务耦合,当在进行比较消耗资源的批处理或批量处理服务发生严重故障时直接影响到了在线业务。
  • 运维体系:不便于定位作业运行时问题,不便于了解作业运行进展、负载情况、也缺少作业运行时的性能指标,不便于对作业进行调优等。有了这些分析,在设计竖线数人云分布式任务调度平台过程中,采用作业调度与作业执行分离的架构来简化业务系统批量处理的开发和运维工作;采用中间件和平台化的思路提升其应用的范围及价值;采用Java系统作业的调度,执行与管控;最后产生的效果是实现了以分布式调度为理念而设计的——多服务节点协同并行处理能力及运行环境的适用能力。在弹性伸缩方面,数人云采用了基于容器平台目的是快速构建多节点的任务执行节点,容器的好处是封装了一整套完整的任务执行环境,并且可以快速扩容服务节点数量,这个批处理设计模型如何自己实现会需要大量的测试和业务打磨,所以在技术选型上,选用了Mesos作为企业级环境的底座,毕竟Apple、MS、Netflix、Uber等大厂都在基于此技术上构建了自己的业务平台。尤其国内开源项目当当的Elastic-Job批量处理平台(https://github.com/dangdangdotcom/elastic-job)受到很多厂家的采用,为数人云提供了一手的学习实践经验。数人云在此之上又进行了一些优化,从设计理念中,更多考虑了业务实际场景中的一种状况:即每次任务处理完毕后,是重置当前进程环境还是退出,虽然容器的额快速启停确实可以解决这个小问题,但仍然不够快。毕竟一个任务的环境初始化是需要消耗时间的,即使容器启动也有那么几十毫秒的损耗。另外,我们对Mesos集群的使用,在于提供可以FailOver的高容错环境,并没有直接让Mesos来调度批量处理任务,实际上,作业节点注册到ZK后,任务的分发和结果收集都是在JVM里面解决,和Mesos集群本身没有关系,减少了对集群系统的直接依赖,后面,我们还会对Kuberentes集群做支持。在故障处理方面,重要的并不是让任务永远不出错,在创建任务时,能提供立即执行一次的操作,让执行结果能即刻体现出来,这样给任务指定的时间去跑才不会出错,在创建任务的细节上,比如把跑批时间参入如:0/5 ? 预测出固定的时间,让用户看到直接的时间会更好。对于事后的历史结果留存也需要做到详细完整,保证故障拍错,这块使用统计面板来监控即可。以及作业预警面板也是需要的。在性能指标方面,大量的工作主要是定义好性能指标并大量压测并调优系统,比如数人云的产品定义:调度频率:定时作业最快支持5S的时间间隔,对比:公有云如阿里ScheduleX,都是分钟级间隔。支持作业容量:最多支持管理100个Zookeeper集群,作业总量支持到500K+。消息作业并发量:支持单节点100K TPS的并发。通过这个目标,根据本地搭建的作业系统环境就可以压测了,压测数据通过Grafana展现出来,测试样例如下:在运维体系中,能不能通过一个管理平台就囊括所有运维需要管理的事情,将其都考虑进去,比如组织关系的体现,暂停时间的管理,配置数据的备份和查询,ZK元数据的到处备份工作,调用链的跟踪设计实现,各种监控面板的实现,这个难度不大,但需要考虑的细节非常多,我们也是在参考和落地实践中摸索这些问题如何解决。最后,分布式任务调度平台在企业架架构体系里面必不可少少的组件,国内企业在经过这几年云计算的高速发展,开始意识到数字化转型过程中必须要经历架构方面的变革,传统的批处理系统已经不能适应业务发现的需要,不妨参考业内开源领域的最佳实践构建自己的分布式调度平台。 QAQ:是否开源?A: 数人云任务调度基于开源基础构建扩展了企业级功能。目前暂不开源,后续是否开源视社区反馈决定。Q:无中心化设计(由任务执行者获取执行计划,并自己触发执行)和集中式调度设计(由一个调度中心统一通过远程调用的方式触发执行者)有什么考虑,更偏向于哪一个?A:数人云任务调度包括控制台都在内都基于无中心化设计,中心控制是ZooKeeper集群。此种设计从分布式角度考虑,避免单点故障。Q:能否详细介绍下并行执行、分片、日志,终端处理的设计实现思路?A:l 并行基于分片粒度执行,目前支持几种粒度:多机器节点执行、同个节点多个进程执行、同个进程多个线程执行。进程粒度的并行执行基于ZooKeeper进行分布式协调处理。多线程基于Java Executors做线程池调度处理。l 日志支持写入ZK,同时支持对接LogStash将日志放入ELK。由于任务执行控制都在平台侧,所以平台可以获取拦截日志流,并将日志进行查处记录。Java和消息作业是拦截LogBack,Shell作业基于Apache Commons Exec的LogOutputStream进行进程输出流的拦截。Q:任务积压如何考虑?如何标记任务执行中或执行完成,具体如何实现?A:l 任务支持超时告警和Kill的配置控制。任务积压时可以对接告警系统,Dashboard也支持显示。l 当任务开始执行和结束时,会由平台侧将状态写入ZK。Q:选择平台独立调度系统主要基于哪些考虑?支持K8S后会考虑自带的Scheduler吗?另“任务结果收集”是如何实现的?A:任务调度可以立即为容器Scheduler上的另外一层独立调度,而后通过Kubernetes/Marathon等API启动容器,任务结果收集可以考虑ELK方式或对接APM系统。Q:分布式任务系统中,如何根据机器系统负载及当前执行任务列表进行任务动态负载?A:目前做法是给任务设定一个逻辑的负载,在调度的时尽可能做到各个机器的整体负载均衡。Q:若机器挂掉,如何进行有状态任务的故障转移?A: 利用Zookeeper特性,当一台机器掉线后,它上面的作业会被调度到其它机器来完成。Q:任务间可能存在一些依赖,如何解决这些任务间的传递问题?A: 目前通过配置的方式设置任务依赖,当停止、启动作业时会给予提醒。任务间的消息传递可利用消息作业的方式进行。Q:当用户上传的是危险操作,平台如何进行自我保护?A:目前利用容器做隔离,当然也可以通过Chroot的方式将作业运行目录隔离。Q:若任务堵住了Hang 任务进程会表现正常,但实际业务没有处理,如何解决?A:需要平台级别超时控制,超过一定阈值时发送告警,再过一定阀值杀掉任务。Q:消息作业支持哪些平台,消息作业的性能表现如何?A:目前支持Kafka,后续也会支持RabbitMQ。至于性能,内部测试可以达到20W的TPS。机器的配置为Mem:48G,CPU:16C*2.2G。Q:是否支持在容器化的平台运行?A:支持,目前支持发布到Marathon、数人云SWAN、以及Kubernetes平台。Q:开源版本如EJ和Saturn,不支持认证或提供BASIC认证。企业难以使用,如何处理?A:数人云调度平台提供基于角色的标准认证,也支持对接LDAP的方式。Q:针对开源版本,数人云调度平台提供哪些企业级功能?A:提供认证授权、审计、对接Prometheus的Metrics、历史配置与版本恢复。 关于数人云分布式任务调度平台Octopus数人云Octopus基于当当Elastic Job和唯品会的Saturn以及数人云DM/OS和容器云平台,打造的高效易用定时任务调度系统。支持多种任务类型和模式,具有资源动态平衡以及框架、业务隔离的功能,而且无缝融合物理机、虚拟机以及容器,从而实现调度任务统一监控管理,全面高可用。# 〓 应用场景无缝代替 Linux Cron Job:完美代替 Linux Cron Job,做到全局统一配置、统一管理、统一监控。分布式任务调度:分布式并行任务处理,如分库分表的批处理等。本地任务调度:可以根据任务量,任意调整处理资源。如电商商品图片扫描处理等。消息任务调度:通过消息调度作业处理。如日志处理、消息驱动数据库同步等。# 〓 功能特性
    • 资源动态平衡:人工指定运行节点,系统自动平衡负载,灵活的运维配置与部署,高效资源利用,简便的管理。
    • 认证与授权:提供 LDAP 集成,以及多角色权限管理。
    • 框架与业务隔离:框架代码与业务代码隔离,集中化动态增加与删除任务,简化开发,避免冲突,业务无侵入,易于发布与维护,框架升级与业务脱离,框架版本统一升级。
    • 分组以及依赖管理:严谨的管理模式以及灵活的设置。
    • 秒级调度:提供任务秒级触发。
    • 简单易用的 SDK:快速开发业务。
    • 资源混搭:支持物理机、虚拟机以及容器。
    • 优美的监控台:提供多维度 Dashboard 以及监控视图。
    • 多种任务类型与任务模式:
    1)Shell 任务:提供任意脚本语言,无缝迁移 Linux Cron Job;
    2)JAVA 任务:提供灵活编程模式,满足不同的业务需求;
    3)消息任务:提供消息驱动,支持任务间串接;
    4)分布式运行与本地任务模式:提供分片、分区处理模式,以及 Daemon 模式,满足业务灵活并行处理。

    zetcd:让应用解除对ZooKeeper的依赖

    hokingyang 发表了文章 • 0 个评论 • 5477 次浏览 • 2017-05-22 17:50 • 来自相关话题

    【编者的话】etcd团队最近发布了zetcd的beta版本,一款能够让etcd兼容Zookeeper协议的代理工具,用户可以通过它解除现有应用对Zookeeper服务的依赖。 分布式系统通常都依赖一个仲裁系统协同工作,一般这样的系统通 ...查看全部
    【编者的话】etcd团队最近发布了zetcd的beta版本,一款能够让etcd兼容Zookeeper协议的代理工具,用户可以通过它解除现有应用对Zookeeper服务的依赖。

    分布式系统通常都依赖一个仲裁系统协同工作,一般这样的系统通过仲裁来保证信息的准确传达,以避免出现脑裂。这类系统通过牺牲通用性换来了充分的设计余地,这种做法显然已经被不断扩散的各种具体实现所例证。这样的系统有很多,例如:chubbyZooKeeperetcdconsul等。尽管这些系统的理念和协议不同,但是提供的都是类似的基于key-value的分布式仲裁。作为将etcd打造成分布式系统最受瞩目的基础组件计划的一部分,etcd团队开发了一款全新的代理,zetcd,无需变动就可以让etcd集群消费ZooKeeper的服务请求。

    ZooKeeper是第一个开源实现的仲裁软件,这促使它成为众多分布式系统偏好的后端。理论上来说这些系统应该可以跟etcd兼容,但由于历史原因,事实并非如此;etcd集群无法替代ZooKeeper,其数据模型和客户端协议跟ZooKeeper应用不兼容;反之亦然。倘若该系统已经投产,那么几乎没什么动力可以推动它接入新的后端,引入新的复杂度。幸运的是,etcd v3 API正准备通过一个标准代理zetcd实现对ZooKeeper数据模型的模拟支持,zetcd是一个由etcd团队开发的全新开源项目,今天发布了zetcd第一个beta版本,v0.0.1,目标是在生产系统中管理和部署zetcd系统。

    zetcd 代理部署在etcd集群前面,服务于一个模拟的ZooKeeper客户端端口,使得ZooKeeper应用可以在上层原生调用etcd。总体上来说,`zetcd`接受ZooKeeper的客户请求,转化成etcd的数据模型和API,将请求转发到etcd,然后将返回信息以客户端可以理解的方式转发回去。zetcd的性能跟ZooKeeper不相上下,并且简化了ZooKeeper集群与etcd之间的管理和操作复杂性。本文将揭示如何使用zetcd,zetcd工作原理以及性能基准。
    #zetcd入门
    zetcd运行所需的是一个go编译器,从互联网上获取的源代码,以及一个可以运行etcd的系统。以下例子展示了从获取zetcd源码,一直到如何在zetcd上运行ZooKeeper命令。由于均是基于开发分支构建的etcd和zetcd,所以并不建议在生产环境这样做,这只是一个讲解如何使用的简单示例。

    首先,获得etcd和zetcd源码,并编译成二进制代码:​
    go get github.com/coreos/etcd/cmd/etcd 
    go get github.com/coreos/zetcd/cmd/zetcd

    其次,运行etcd,将zetcd连接到etcd客户服务端:
    #etcd uses localhost:2379 by default 
    etcd &
    zetcd -zkaddr localhost:2181 -endpoints localhost:2379 &​

    通过增加订阅和创建一个key来试用zetd:
    go install github.com/coreos/zetcd/cmd/zkctl 
    zkctl watch / &
    zkctl create /abc "foo"

    从概念上讲,上述例子即完成在一个单个的etcd实例上增加一层zetcd。
    01.png

    zetcd这层到底是做什么的呢?​
    #etcd3中对ZooKeeper的支持
    深入来讲,zetcd会将ZooKeeper的数据模型翻译成适配etcd API。对于key查找,zetcd将ZooKeeper层级式目录转换成etcd扁平的二分键值空间(flat binary keyspace)。为了管理元数据,当写入etcd后端时,zetcd利用内存级别的事务将信息安全、原子地更新为ZooKeeper znode信息。

    ZooKeeper以目录方式列出key(getChildren),而etcd则是通过间隔(Range)方式。下图讲解了zetcd如何对etcd下的key进行编码从而有效地支持以目录形式列出。所有在etcd中的zetcd key都有一个包括全目录名的前缀(例如:"/"和“/abc”分别代表深度为0 和1)。要列出一个目录时,zetcd发出一个带前缀的range请求(例如[“/zk/key/002/abc/”, “/zk/key/002/abc0”)来列出满足目录深度和路径的所有/abc/下的key。深度限制只针对目录本身;如果zetcd只使用路径而不使用深度,那么etcd将返回这个目录下的所有key,zetcd则会丢弃该结果,反之则只返回本目录下的key。
    02.png

    每个ZooKeeper key在ZNode里都带有一些元数据,即key的调整,版本和权限等。尽管etcd也有每个key的元数据,却比ZNode要简单很多,例如因为没有目录也就没有子版本,因为etcd使用的是基于角色认证的机制因此也就没有ACL,因为实际的时钟超出范畴所以没有时间戳。这些额外的元数据会被映射为对应的key,用来描述一个完整的ZNode。修改元数据时,zetcd利用内存级别的软事务来自动更新key的子集,确保ZNodes不需要昂贵的加锁机制就可以保持一致。

    此外,zetcd可以和一台授权的ZooKeeper服务器做动态校验。为了做比较,zetcd可以同时连到etcd和外部ZooKeeper服务器。当客户端发起请求给该模式下的zetcd时,请求会被同时转发到zetcd和ZooKeeper服务端。如果两个服务器响应的数据不一致,zetcd会给此响应标识一个警告。
    #性能基准
    由于数据的转换以及额外的网络开销,也许很容易觉得这样的模拟不切实际。尽管对于ZooKeeper或者etcd集群来说,zetcd有额外的花销,但是它仍然有一个优势,即某些etcd应用安装完毕后仍然需要ZooKeeper来协调的场景。例如,早期用户报告称在zetcd里通过etcd的TLS加密流量比一个类似的经典ZooKeeper配置更简单。在这些场景中,支持同一种ZooKeeper协议所带来的简单可靠性比性能更重要一些。

    跟etcd性能工具的接口及报告形式类似,zetcd命令行工具zkboom可以用来判断一个zetcd的性能基准是否满足要求。其它ZooKeeper性能工具应该也可以用在zetcd;zkboom为用户提供了便利,我们不妨试试用它来做下创建key的测试:
    go get github.com/coreos/zetcd/cmd/zkboom 
    zkboom --conns=50 --total=10000 --endpoints=localhost:2181 create​

    zetcd应当可以为小负载提供充分的性能保障。一个简单两节点配置的延迟基准表明zetcd是可以承受大量请求的。具体配置为两台Linux服务器通过一个千兆交换机互联,其中一台设备在RAID磁盘配置上运行代理和和服务端,另外一台设备用于产生客户请求。zkbook通过创建一个空的key存储,然后从中读取128Kbytes的键值对来进行测量。用户请求被限制在每秒2500个请求,然后逐渐增加并发客户端数量。ZooKeeper 3.4.10和etcd结果对比见下图。

    下图揭示了zetcd客户端并发数与创建key的平均延迟之间的关系。由于etcd在延迟上比zookeeper要有5-35ms的优势,zetcd有足够余地处理由于额外负载和网络跳转造成的延迟。比起ZooKeeper,zetcd代理始终还是存在20ms左右的差距,但是从处理2500请求的吞吐量数据来看,并没有出现队列堵塞。一种对zetcd写比较慢的解释是,与读不一样,由于数据模型上存在差异,所以在每个ZooKeeper key写时需要写多个key。
    03.png

    下图揭示了zetcd客户端并发数与key取值的平均延迟之间的关系。由于Zookeeper的取值延迟比etcd要快那么2ms左右,想要zetcd提供数据的速度快过Zookeeper的话恐怕还得依赖于etcd本身性能的进一步提升。然而,尽管zetcd需要从etcd请求额外的key来模拟Zookeeper znode的元数据,zetcd的命中延迟在等待etcd key提取数据方面只增加了大概1.5ms。zetcd在key的数据提取操作方面仅需一次往返,因为读请求会被打包到一个etcd事务中。
    04.png

    #未来
    zetcd承诺上述性能基准测试的结果是合理的,在可接受的延迟情况下,可以轻松支撑每秒上千个操作。以上模拟对于Mesos,Kafka和Drill替代ZooKeeper提供了一个替代选择。但是对于zetcd本身来说性能方面仍有不少的提升空间。与此同时测试更多的Zookeeper应用也会进一步推动zetcd成为Zookeeper服务器的替代品。

    zetcd从去年十月开始在开源社区发行,最近刚发布第一个版本:zetcd v0.0.1。尽管是第一个beta发行版,但是已经为未来生产系统提供稳定管理和部署。如果跟etcd配合起来使用,运行zetcd的系统将会一套自驱动的“ZooKeeper”集群,它可以自动后台升级,备份和TLS管理。如想了解更多,请参阅 https://github.com/coreos/zetcd/。

    原文链接:zetcd: running ZooKeeper apps without ZooKeeper(翻译:杨峰 审校:吴佳兴)
    Apache ZooKeeper是Apache软件基金会的一个软件项目,他为大型分布式计算提供开源的分布式配置服务、同步服务和命名注册。 ZooKeeper曾经是Hadoop的一个子项目,但现在是一个独立的顶级项目。 ZooKeeper的架构通过冗余服务实现高可用性。