分享

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

 集微笔记 2015-09-17

分布式系统技术概要

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

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

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 功能。

  1. service Calculator extends shared.SharedService { 
  2.  
  3.   /** 
  4.    * A method definition looks like C code. It has a return type, arguments, 
  5.    * and optionally a list of exceptions that it may throw. Note that argument 
  6.    * lists and exception lists are specified using the exact same syntax as 
  7.    * field lists in struct or exception definitions. 
  8.    */ 
  9.  
  10.    void ping(), 
  11.  
  12.    i32 add(1:i32 num1, 2:i32 num2), 
  13.  
  14.    i32 calculate(1:i32 logid, 2:Work w) throws (1:InvalidOperation ouch), 
  15.  
  16.    /** 
  17.     * This method has a oneway modifier. That means the client only makes 
  18.     * a request and does not listen for any response at all. Oneway methods 
  19.     * must be void. 
  20.     */ 
  21.    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并发编程分析

内容导航
 第 1 页:分布式系统技术概要  第 2 页:消息中间件

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多