分布式应用的各基本领域及开发技术概要

分布式系统技术概要前端

如今互联网应用,尤为是大型互联网公司的应用已经发展为大规模或超大规模的分布式的,集群化的应用。而中小规模的分布式应用也已普遍出如今各个领域。将来,随着云计算向社会生活的方方面面去***,分布式应用将更加地普及。因此,任何一个要从事服务器端应用开发的人员,都有具有对分布式应用的基本认识。java

本文将简要介绍分布式应用的各基本领域的相关技术。这些技术在一个分布式应用中都会有或多或少的设计,即使暂时没有涉及到,设计人员也要有所考虑,保证系统有进一步发展的空间。算法

1. 集群管理数据库

关键字:Apache Zookeeper、Paxos 算法、Etcd、Raft、Apache Curator编程

在一个分布式系统中,存在着一些和系统运行,以及重要业务紧密相关的数据,如节点相关的数据、应用服务和数据服务相关的数据等,这些数据对集群的正常运行相当重要。缓存

  • 服务器节点相关数据:服务器的地址、状态安全

  • 服务相关数据:服务的IP、端口、版本、协议、状态、主备节点信息服务器

  • 数据库相关数据:路由规则、分库分表规则网络

这些重要的数据在分布式系统中存在着多份拷贝,以保证高可用性。但这产生了另一个问题,就是如何保证这些数据的一致性。由于这些数据是如此重要,不一致的数据会产生严重甚至致命的错误。在一个小规模的分布式系统中,由于能够用一两台服务器去作集群管理,因此数据的一致性容易实现。可是对于一个大规模的分布式系统,一两台集群配置管理服务器没法支撑整个集群所带来的大量并发读写操做,因此要使用几台、十几台,甚至更多的服务器去支撑这些请求。此时,就须要一个保持这些服务器中集群配置数据的一致性的方案了。数据结构

这众多方案中,Paxos 算法算是最佳方案之一。关于 Paxos 算法的内容,不在这里详述了。简单描述就是集群中各节点相互以提议的方式通讯(对一项数据的修改),提议中带有不断增长的 ID 号,节点永远赞成当前 ID 号最大的提议,并拒绝其它提议。当有半数以上节点赞成一项提议以后,这个提议便被整个节点所接受并采纳。

1.1. Apache Zookeeper

Paxos 算法的语言表述看上去不难,可是其中的技术难点并很多。好在如今已经有了不少的解决方案,其中最为著名的即是 Apache Zookeeper。Zookeeper 不只能够用来存储配置数据,还能够用来实现集群 Master 选举、分布式锁等场景。Apache Curator 是 Zookeeper 的客户端,能够简化对 Zookeeper 的使用,实现各式的场景。

Zookeeper 是一个分布式的服务管理框架。Zookeeper 的典型的应用场景包括配置文件的管理、集群管理、分布式锁、Leader 选举、队列管理等。Zookeeper 可工做在集群模式下,zoo.cfg 中记录着集群中全部 Zookeeper 服务器的地址,每一个服务器有本身惟一的 ID。同时,每一个服务器在本身的 dataDir 目录下还要有一个 myid 文件,以标示本身的 ID。在 Zookeeper 中,数据以树状的结构存储,相似于 LDAP 数据库。

如今相似 Zookeeper 的项目还有使用 go 语言实现的 Etcd。

1.2. 参考:

  • Paxos算法

  • zookeeper节点数与watch的性能测试

  • etcd:从应用场景到实现原理的全方位解读

  • etcd v2.1 benchmarks

  • 分布式配置服务etcd VS 分布式协调服务zookeeper

2. 远程调用

关键字: NIO、Netty、epoll、Thrift、Protobuf

分布式系统中,模块间的调用一般须要用远程调用来实现。并且随着微服务架构模式的流行,使用远程调用的比例会愈来愈高。其实远程调用这种方式很早之前就出现了,早年的技术有诸如 COBRA、EJB、SOAP 等,但这些技术存在着用法复杂、性能差等缺点。这些缺点限制着远程调用的普及。这些年,随着异步 IO 技术、序列化技术的发展进步,以及像 Zookeeper 这样的集群管理服务的出现普及,妨碍远程调用普及的技术障碍逐渐被打破。

使用 HTTP + JSON 的方式一样能够实现模块之间的远程调用,但这种方式一般用来实现 Public API。在系统内部,远程调用要求更快的速度,更小的延迟,还有还有异步调用的需求,因此 HTTP + JSON 一般没法知足这样的要求。远程调用有两个重要的技术点,一个是 IO 技术、一个是序列化技术。另外,远程调用还引出来另两个问题:1. 服务注册、发现、路由的问题。这个问题的须要结合例如 Zookeeper 服务去解决;2. 如何简化远程调用的使用,使其如同本地调用同样简单。这个问题须要结合 AOP 之类的技术。这两个问题的具体解决不在本节讨论范围以内。

2.1. IO

(这里只说 Socket IO)常见的 IO 模型有阻塞 IO、非阻塞 IO 和异步 IO。阻塞 IO 指的是若是一个线程要在 Socket 链接上进行某种 IO 操做时(读或写数据),当没有操做不可执行时(没有数据可读或没法写数据),执行操做的线程便会被挂起,操做便会被阻塞,直到操做能够执行。这种方式的好处是业务代码编写起来很简单,缺点是资源利用率不高。由于一个链接必须有一个线程去处理。当有大量链接时,便会消耗大量的线程。这个缺点放在服务器端开发领域就显得很是严重了。

非阻塞 IO 实现了线程的多路复用,一个线程被用来能够处理多个链接;异步 IO 则是由操做系统来实现 IO 的读写操做。在数据 ready 以后,通知业务线程处理。

上面只是对阻塞 IO 和非阻塞 IO 的一个笼统的介绍。从具体的技术来看,Linux 经过 epoll 技术提供了对非阻塞 IO 的支持。epoll 是 Linux 内核的一个系统调用,最先在 2.5.44 版中被加入。epoll 的意思是 event poll。简单来讲就是当有一个 IO 事件发生时,Linux 内核便会通知用户。使用方式是在建立 epoll 句柄以后,用户在其上不断地循环以获取新的事件(当有事件发生时)。这些事件是来自多个链接的,从而实现了线程的多路复用。

在 Java 1.4 中,也引入了 NIO 的支持 (java.nio.*)。在 Java NIO API 中,用户的程序能够将一个链接 (SelectableChannel.register(Selector sel, int ops)) 注册到一个 Selector 上(一个 Selector 能够有多个链接注册)。注册以后,用户的程序即可以经过不断地循环调用 Selector.selectedKeys() 方法得到这个链接上的事件并进行处理(一般会使用另外的线程去处理事件,即 Reactor 模型)

虽然 Java 为 NIO 开发提供了良好的 API 支持(从 1.7 开始还支持了 AIO),可是 IO 开发依旧有很高的复杂性,且 Java NIO 类库的是 JDK 中 bug 较多的部分。故不推荐普通开发者直接基于 JDK 开发网络 IO 功能,而是建议使用 Netty 进行开发。关于 Netty 这里就不作介绍了。

2.2. 序列化技术

序列化技术是远程调用的通讯协议中的重要一部分,它定义了编程语言中的数据结构和数据传输协议中的数据结构之间如何相互转化。序列化技术的性能的好坏会影响到对远程调用性能的好坏在序列化方面。序列化技术性能的好坏主要包含两方面的含义:一个是序列化时占用的资源(CPU、内存、所需时间);另外一个是序列化以后数据的大小。SOAP WebService 和 REST WebService 一般会把数据序列化成 XML 格式或者 JSON 格式。这两种格式由于都是文本格式,因此有着良好的可读性,可是对于须要频繁使用的远程调用来讲,它们的体积偏大。因此边有了性能更好的序列化解决方案,被你们所熟知的有 Protocol Buffers 和 Apache Arvo。此外,Apache Thrift 的序列化的性能也很好,可是 Thrift 没法被当作一个单独的序列化技术被使用,而是一个完整的远程调用解决方案。其序列化部分不太容易被剥离出来,没有完整的 API 被开放使用。这里列出了常见的序列化技术的性能比较

2.3. Apache Thrift

Thrift 由 Facebook 贡献,它是一个高性能、跨语言的 RPC 服务框架,适合用来实现内部服务的 RPC 调用。Thrift 采用经过 IDL 接口描述语言定义并生成服务接口,再结合其提供的服务端和客户端调用栈实现 RPC 功能。

service Calculator extends shared.SharedService {    /**    * A method definition looks like C code. It has a return type, arguments,    * and optionally a list of exceptions that it may throw. Note that argument    * lists and exception lists are specified using the exact same syntax as    * field lists in struct or exception definitions.    */     void ping(),     i32 add(1:i32 num1, 2:i32 num2),     i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch),     /**     * This method has a oneway modifier. That means the client only makes     * a request and does not listen for any response at all. Oneway methods     * must be void.     */    oneway void zip() }

Thrift 提供了工具,根据 IDL 文件,为各类编程语言(C++, Java, Python, PHP, Ruby 等)生成相应的接口和数据结构。Thrift 不只提供了传统的 Request/Response 方式的接口调用,也有单向的调用方式(用关键字 oneway 修饰)。

Thrift 的序列化部分和整个框架结合紧密,并无直接提供序列化和反序列化的接口,因此不容易和其它传输协议配合使用。

示例与解释

这里提供了一个 Thrift 的简单使用的示例。其中除了 ThriftClient、ThriftServer 和 CalculatorHandler 三个类,剩下的类都是从 *.thrift 文件,即 Thrift 的 IDL 文件生成的。Thrift 的 IDL 支持 namespace(即包空间)、继承等语法。

以 Java 为例,Thrift IDL 中的 service 将生成接口、服务器端栈和客户端栈,这三部分又都有同步和异步两种类型。即一个 IDL 文件将生成6个内部类。客户端经过这个调用栈,在配置了传输协议、地址信息和序列化协议以后,就能够调用服务器端了。

服务器端的实现也不复杂。固然开发人员须要实现相应的业务类,这个业务类要实现至少一种由 IDL 生成的接口,同步接口或异步接口,也可二者都实现。

基于 IDL 进行开发是使用 Thrift 这样 RPC 框架的一种方式。这种方式对于新开发的、须要被远程访问的服务、而且有多重语言的客户端的场景来讲是很合适的。可是对于已有的业务方法,若是要让其能够被远程访问的话,那这种方式就显得不方便了。因此 Facebook 有提供了另外一个项目 —— Swift(不是苹果的 Swift)。这个项目能够经过在 Java 代码上添加 Annotation,使得普通的 Java 方法调用转变成 Thrift 的远程调用。这种方式相似于 JAX-RS 或其它许多 REST 框架所提供的功能。这种方式对主要使用 Java 或其它一些 JVM 语言,如 Scala 和 Groovy 开发的项目来讲是很合适的。使用了 Thrift 的远程调用的同时,还下降了引入 IDL 所致使的复杂度的提升和可读性的降低。Thrift Swift 示例

2.4. 其它技术介绍

Protocol Buffers

Protobuf 是一个高性能序列化解决方案,有完善的文档,能够和例如 HTTP 这样的协议搭配使用。

Apache Avro

Apache Avro 是 Apache Hadoop 的一个子项目。它提供了两种序列化格式:JSON和二进制格式。JSON格式有良好的可读性,而二进制格式在性能上和 Protobuf 不相上下。

2.5. 参考

  • 序列化和反序列化

  • Netty系列之Netty高性能之道

  • Netty系列之Netty线程模型

  • Netty系列之Netty并发编程分析

消息中间件

关键字:Kafka、RabbitMQ 在分布式系统中,消息中间件的重要性愈来愈明显。消息中间件能够解耦模块、提供异步调用功能、消息持久化、消息削峰。已有的如 Apache ActiveMQ 没法知足新的须要,因而出现了如 RabbitMQ、Apache Kafka 等新型的消息中间件产品。

Apache Kafka

Apache Kafka 充分利用了机械磁盘顺序读写速度快的特色,在接受消息以后同步地写入到磁盘中,保证数据可靠性的同时,也保证了很是快的速度。每一个 Kafka 集群上都有多个 Topic,Topic 至关于一个 category,消费者能够订阅一个或多个 Topic。每一个 Topic 由多个 Partition 组成。消息被顺序的添加到 Partition 中,每条消息有一个惟一的、有序的 ID,这个 ID 被称为 Offset。Consumer 须要维护本身消费到的消息的位置 (Offset)。

Apache Kafka 不一样于传统的消息中间件,它采用“拉”消息模式,而不是传统的“推”消息模式。即客户端须要主动从消息中间件获取消息,好处是客户端能够更好地控制请求量。

Queue 模式和 Topic 模式

传统消息队列服务中有队列模式和发布订阅模式两种模式,前者一条消息只会被一个消费者消费;后者一条消息会发布给全部的订阅这个 Topic 的消费者。在 Kafka 中,这两种模式是使用一种方式 —— 消费者组来实现的。在同一个消费者组中的不一样消费者不会受到相同的消息。若是想实现发布订阅模式,消费者必须处于不一样的消费者组中。

Kafka 集群

RabbitMQ

RabbitMQ 是一个使用 Erlang 开发的 AMQP (Advanced Message Queue Protocol) 实现。如今 RabbitMQ 是由 VMware 旗下的 SpringSource 负责开发。AMQP 是一个语言无关的消息队列协议。在 RabbitMQ 中,有三个概念:Exchange、Queue 和 Route key。Exchange 用来标示生产者,Queue 用来标示消费者,而 Route key 用来关联这二者。RabbitMQ 中这种方式提供了更灵活的应用模式。

分布式文件系统

块存储与对象存储

块存储是将一块裸盘提供给客户使用,可是这块裸盘多是来自一块物理硬盘,也有多是多块,或是来自不一样服务器上的硬盘。对象存储提供了更高级的接口,经过这些接口能够读写文件以及相关的元数据。其中的元数据包含了文件每个块的存储信息。经过文件元数据,文件能够被并行地操做。

分布式文件系统的高可用

为了保证数据的安全,分布式文件系统一般会将文件复制为三份。这三份数据会位于不一样的服务器上,对应要求更高的系统,好比公有云存储。其中的一份数据会放置在另外一个机房中,以保证即使整个机房出现故障,整个文件系统还是可用的。

Ceph

Ceph 目前是 OpenStack 的一个组件,提供了块存储和对象存储的功能。

GridFS

GridFS 是 MongoDB 的一部分。用来存储超过 BSON 大小限制(16MB)的文件。GridFS 将文件分红一个个部分,分开存储。

FastDFS

FastDFS 是一个轻量的分布式文件系统,适合存储中小文件(对象存储)。FastDFS 的跟踪服务器不负责记录文件的元信息。文件的具体存储位置等信息包含在返回给用户的 File ID 中。

分布式内存

内存是新的硬盘,硬盘是新的磁带 -- Jim Gray

Hazelcast

Hazelcast 是一个面向 Java 的分布式内存解决方案,提供了丰富的功能特性。实现了诸如分布式 Java 集合类、分布式锁、分布式 ExecutorService 实现等等。但现实每每是残酷的,Hazelcast 在实际应用中存在大量的缺陷。详见 “hazelcast的坑爹事”

Memcached

Memcached 是老牌的“分布式”缓存解决方案。分布式之因此加引号,是由于 Memcached 服务器端自己并不支持分布式,服务器端每一个节点之间并不会相互通讯。分布式的支持须要客户端来实现。早期的内存分布式是经过节点之间复制来实现的,但这种方式却限制了可伸缩性。这也是由于诸如 Terrecotta 这样的内存分布式解决方案没有成为主流的缘由。

Redis

Gemfire

分布式数据库

关系型数据库

在大规模的分布式应用中,单库或者简单的读写分离已经没法知足要求,所以必须对数据库进行水平和垂直的划分和分库分表。在对数据库进行分库分表以后,应用对数据库的访问便再也不是一件简单的事情了。应用在进行一次数据库操做时,其所对应的数据库的地址和表名必须经过某种逻辑运算才能获得。例如,ID 从1到1,000,000的User数据是数据库1的User_1表中,ID从1,000,001到2,000,000的User数据在数据库1的 User_2表中,而其它的User数据又会在不一样的数据库的不一样的表中。同时,还要考虑主从数据库,读写分离的问题。这样的数据库使用方式会使数据操做变得极为复杂,也会增长数据迁移,增容扩容时的难度。

对于这样复杂的问题,靠应用本身解决显然是不合适的。因此各家分布式应用的使用大户——互联网厂商,都本身实现了相应的解决方案。这些解决方案可分为中间间方式和框架方式,前者做为数据库访问的代理,使得分布式的数据库对应用是透明的。后者做为一个框架嵌入到应用中,也能起到相似的做用。这两种方式各有优劣,分别适合不一样的场合。

搜狗 Compass,阿里 TDDL、Cobar

NoSQL

大部分 NoSQL 虽然对分布式的支持是友好的,但这并不意味着使用这些 NoSQL 数据库就能够轻轻松松地实现一个集群。例如著名的 Key/Value 数据库 Redis。它 3.0 以前一直没有官方的集群方案,因此各个大规模使用 Redis 都须要本身实现分布式方案,例如 Twitter 的 Twemproxy、豌豆荚的 Codis 等等。

在实现数据的分布式解决方案的时候,有一个算法是最常被使用的 —— 一致性哈希算法,这里只是简单提一下,不作进一步介绍。

虚拟化技术

关键字:OpenStack、Docker、容器技术虚拟化技术是提升硬件利用率的重要手段。虚拟化技术是实现云计算的重要技术。虚拟化技术的最底层是各类硬件的虚拟化,如 CPU 虚拟化、内存虚拟化、存储虚拟化、网络虚拟化等等。而后再基于这些技术,构建出各类虚拟机技术。而后各个厂商又基于虚拟机技术和其它虚拟化技术构建出 IaaS、PaaS 和 SaaS 等平台和软件产品。

OpenStack

OpenStack 这个开源项目包含了一系列用于 IaaS 平台搭建的组件的合称。这些组件包含用于网络虚拟化的 Neutron、提供存储虚拟化的 Ceph 和 Swift、以及提供例如镜像管理、控制面板等功能的诸多组件。OpenStack 自己并不提供虚拟化技术,而是经过支持诸多现有的虚拟化技术,例如 KVM,并在此之上提供一系列构建 IaaS 解决方案的技术。OpenStack 中的组件能够灵活搭配使用,而且由于开源的缘由,使用者能够对其进行自定义或二次开发。可是也是由于这个缘由,任何厂商想要成功使用 OpenStack 必须有一个强大的技术团队作后盾。这也是目前 OpenStack 技术发展遇到的最大困难。

Docker

严格说来 Docker 并非一个虚拟化技术,可是由于 Docker 可以提供给使用者一种轻量化的虚拟机的使用体验,因此也将 Docker 列在这里。Docker 是一个容器技术,它经过 Linux 内核的支持,使不一样的进程能够相互隔离并作到资源的限制,从而实现了部分的虚拟机资源隔离的须要。Docker 相比较虚拟机的优点在于轻量和系统资源使用效率接近于实体机。由于如今随着需求的发展和技术的进步,服务器端应用向着一种轻量化和愈来愈分布式的方向发展。虚拟机这样重量级的技术对于小而多的应用来讲便再也不合适,这也是 Docker 这样的容器技术近些年迅速发展并呈现火热状态的重要缘由。

Docker 和前面提到的 OpenStack 是两个不一样层面的技术,二者并不冲突。如今 OpenStack 和 Docker 社区正在紧密合做(容器不会取代OpenStack,但两者如何深度整合?)。

分布式系统之负载均衡

HAProxy

HAProxy 是一个高性能的 TCP 和 HTTP 反向代理代理和负载均衡服务器。可用反向代理和负载均衡还有 Nginx。Niginx 更偏向于 HTTP 协议。另外 Varnish 和 Squid 能够做为前端的代理,可是它们更偏重缓存功能

更上一层

服务编排:注册、发现和路由

结合技术:配置管理、远程调用等

有些相似早年的 JNDI。即一个应用去远程访问另一个应用时,只需知道它所要访问的应用的名称、版本等信息,便可调用成功。不须要考虑它所要调用的应用的具体地址。

云操做系统

结合技术:虚拟机、容器技术、网络虚拟化、配置管理、消息队列

Apache Mesos、Google Berg、腾讯 Gaia、百度 Matrix

总结

就像上面所提到的,上面的这些技术之间都是你中有我,我中有你的关系,或者有着相相似的设计思想。掌握它们,基本不去使用,也会对你设计开发能力的提升大有裨益。

相关文章
相关标签/搜索