分布式系统技术概要
现在互联网应用,尤其是大型互联网公司的应用已经发展为大规模或超大规模的分布式的,集群化的应用。而中小规模的分布式应用也已广泛出现在各个领域。未来,随着云计算向社会生活的方方面面去渗透,分布式应用将更加地普及。所以,任何一个要从事服务器端应用开发的人员,都有具备对分布式应用的基本认识。
本文将简要介绍分布式应用的各基本领域的相关技术。这些技术在一个分布式应用中都会有或多或少的设计,即便暂时没有涉及到,设计人员也要有所考虑,保证系统有进一步发展的空间。
fenbushi.jpeg
1 集群管理
在一个分布式系统中,存在着一些和系统运行,以及重要业务紧密相关的数据,如节点相关的数据、应用服务和数据服务相关的数据等,这些数据对集群的正常运行至关重要。
- 服务器节点相关数据:服务器的地址、状态
- 服务相关数据:服务的IP、端口、版本、协议、状态、主备节点信息
- 数据库相关数据:路由规则、分库分表规则
这些重要的数据在分布式系统中存在着多份拷贝,以保证高可用性。但这产生了另外一个问题,就是如何保证这些数据的一致性。因为这些数据是如此重要,不一致的数据会产生严重甚至致命的错误。在一个小规模的分布式系统中,因为可以用一两台服务器去做集群管理,所以数据的一致性容易实现。但是对于一个大规模的分布式系统,一两台集群配置管理服务器无法支撑整个集群所带来的大量并发读写操作,所以要使用几台、十几台,甚至更多的服务器去支撑这些请求。此时,就需要一个保持这些服务器中集群配置数据的一致性的方案了。
这众多方案中,Paxos 算法算是最佳方案之一。关于 Paxos 算法的内容,不在这里详述了。简单描述就是集群中各节点相互以提议的方式通信(对一项数据的修改),提议中带有不断增加的 ID 号,节点永远同意当前 ID 号最大的提议,并拒绝其它提议。当有半数以上节点同意一项提议之后,这个提议便被整个节点所接受并采纳
2 远程调用
分布式系统中,模块间的调用通常需要用远程调用来实现。而且随着微服务架构模式的流行,使用远程调用的比例会越来越高。其实远程调用这种方式很早以前就出现了,早年的技术有诸如 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 被开放使用。这里列出了常见的序列化技术的性能比较。
消息中间件
Apache Kafka
Apache Kafka 充分利用了机械磁盘顺序读写速度快的特点,在接受消息之后同步地写入到磁盘中,保证数据可靠性的同时,也保证了非常快的速度。每个 Kafka 集群上都有多个 Topic,Topic 相当于一个 category,消费者可以订阅一个或多个 Topic。每个 Topic 由多个 Partition 组成。消息被顺序的添加到 Partition 中,每条消息有一个唯一的、有序的 ID,这个 ID 被称为 Offset。Consumer 需要维护自己消费到的消息的位置 (Offset)。
Apache Kafka 不同于传统的消息中间件,它采用“拉”消息模式,而不是传统的“推”消息模式。即客户端需要主动从消息中间件获取消息,好处是客户端可以更好地控制请求量。
Queue 模式和 Topic 模式
传统消息队列服务中有队列模式和发布订阅模式两种模式,前者一条消息只会被一个消费者消费;后者一条消息会发布给所有的订阅这个 Topic 的消费者。在 Kafka 中,这两种模式是使用一种方式 —— 消费者组来实现的。在同一个消费者组中的不同消费者不会受到相同的消息。如果想实现发布订阅模式,消费者必须处于不同的消费者组中。
RabbitMQ
RabbitMQ 是一个使用 Erlang 开发的 AMQP (Advanced Message Queue Protocol) 实现。现在 RabbitMQ 是由 VMware 旗下的 SpringSource 负责开发。AMQP 是一个语言无关的消息队列协议。在 RabbitMQ 中,有三个概念:Exchange、Queue 和 Route key。Exchange 用来标示生产者,Queue 用来标示消费者,而 Route key 用来关联这两者。RabbitMQ 中这种方式提供了更灵活的应用模式。
分布式文件系统
块存储与对象存储
块存储是将一块裸盘提供给客户使用,但是这块裸盘可能是来自一块物理硬盘,也有可能是多块,或是来自不同服务器上的硬盘。对象存储提供了更高级的接口,通过这些接口可以读写文件以及相关的元数据。其中的元数据包含了文件每一个块的存储信息。通过文件元数据,文件可以被并行地操作。
分布式文件系统的高可用
为了保证数据的安全,分布式文件系统通常会将文件复制为三份。这三份数据会位于不同的服务器上,对应要求更高的系统,比如公有云存储。其中的一份数据会放置在另一个机房中,以保证即便整个机房出现故障,整个文件系统仍是可用的。
分布式数据库
关系型数据库
在大规模的分布式应用中,单库或者简单的读写分离已经无法满足要求,因此必须对数据库进行水平和垂直的划分和分库分表。在对数据库进行分库分表之后,应用对数据库的访问便不再是一件简单的事情了。应用在进行一次数据库操作时,其所对应的数据库的地址和表名必须通过某种逻辑运算才能得到。例如,ID从1到1,000,000的User数据是数据库1的User_1表中,ID从1,000,001到2,000,000的User数据在数据库1的User_2表中,而其它的User数据又会在不同的数据库的不同的表中。同时,还要考虑主从数据库,读写分离的问题。这样的数据库使用方式会使数据操作变得极为复杂,也会增加数据迁移,增容扩容时的难度。
对于这样复杂的问题,靠应用自己解决显然是不合适的。所以各家分布式应用的使用大户——互联网厂商,都自己实现了相应的解决方案。这些解决方案可分为中间间方式和框架方式,前者作为数据库访问的代理,使得分布式的数据库对应用是透明的。后者作为一个框架嵌入到应用中,也能起到类似的作用。这两种方式各有优劣,分别适合不同的场合。
NoSQL
大部分 NoSQL 虽然对分布式的支持是友好的,但这并不意味着使用这些 NoSQL 数据库就可以轻轻松松地实现一个集群。例如著名的 Key/Value 数据库 Redis。它 3.0 之前一直没有官方的集群方案,所以各个大规模使用 Redis 都需要自己实现分布式方案,例如 Twitter 的 Twemproxy、豌豆荚的 Codis 等等。
博客著作权归本作者所有,任何形式的转载都请联系作者获得授权并注明出处。
网友评论