个性化阅读
专注于IT技术分析

CloudI:将Erlang的容错能力带给多语言开发

云必须高效以提供有用的容错和可伸缩性, 但它们也必须易于使用。

CloudI(发音为” cloud-e” /klaʊdi/)是在Erlang中构建的开源云计算平台, 与平台即服务(PaaS)云最为相关。 CloudI在几个关键方面有所不同, 最重要的是:软件开发人员不会被迫使用特定的框架, 缓慢的硬件虚拟化或特定的操作系统。通过允许在没有虚拟化的情况下进行云部署, CloudI可以不受阻碍地进行开发流程和运行时性能, 同时可以通过明确的责任感来控制服务质量。

是什么使云成为云?

在过去的几年中, “云”一词已经无处不在。它的真正含义已经有些失落了。从最基本的技术意义上讲, 这些是云计算平台必须具备的属性:

  • 容错
  • 可扩展性

这些是我们希望云具有的属性:

  • 易于整合
  • 部署简单

我构建CloudI的目标是将这四个属性结合在一起。

必须了解, 很少有编程语言可以提供真正的容错性和可扩展性。实际上, 我会说Erlang在这方面是一个人。

我首先看了Erlang编程语言(在其之上构建了CloudI)。 Erlang虚拟机提供容错功能和高度可扩展的体系结构, 而Erlang编程语言则使所需的源代码保持较小且易于遵循。

必须了解, 很少有编程语言可以提供真正的容错性和可扩展性。实际上, 我会说Erlang在这方面是一个人。让我绕道去解释原因和方式。

容错是云计算中的重要考虑因素。

什么是云计算中的容错?

容错是对错误的鲁棒性。即, 即使在(希望隔离)错误的情况下, 容错系统也能够相对正常地继续运行。

在这里, 我们看到服务C向服务A和B发送请求。尽管服务B暂时崩溃, 但系统的其余部分继续运行, 相对不受阻碍。

Erlang初学者教程

以实际的生产系统(在电信行业内), Erlang以实现9×9的可靠性(99.9999999%的正常运行时间, 因此每年少于31.536毫秒的停机时间)而闻名。如果运气好的话, 由于更新过程缓慢和系统完全故障, 普通的Web开发技术只能达到5×9秒的可靠性(正常运行时间为99.999%, 因此每年大约需要5.256分钟的停机时间)。 Erlang如何提供这一优势?

Erlang虚拟机实现所谓的”演员模型”, 即用于并行计算的数学模型。在Actor模型中, Erlang的轻量级过程是语言本身的并发原语。也就是说, 在Erlang中, 我们假设一切都是演员。根据定义, 参与者可以同时执行其动作。因此, 如果一切都是参与者, 我们将获得固有的并发性。 (有关Erlang的Actor模型的更多信息, 这里有较长的讨论。)

结果, Erlang软件由许多轻量级进程构建而成, 这些进程使进程状态保持隔离, 同时提供了极大的可伸缩性。当Erlang进程需要外部状态时, 通常会将消息发送到另一个进程, 以便消息排队可以为Erlang进程提供有效的调度。为了使Erlang进程状态保持隔离, Erlang虚拟机将分别为每个进程进行垃圾回收, 以便其他Erlang进程可以继续并发运行而不会被中断。

与Java虚拟机垃圾回收相比, Erlang虚拟机垃圾回收是一个重要的区别, 因为Java依赖于单个堆, 而该堆缺少Erlang提供的隔离状态。 Erlang垃圾回收和Java垃圾回收之间的区别意味着, 即使是在Java虚拟机之上开发了库或语言支持, Java也不能仅仅由于虚拟机垃圾回收而提供基本的容错保证。已经尝试了用Java和其他基于Java虚拟机的语言开发容错功能的方法, 但是由于Java虚拟机的垃圾回收, 它们仍然是失败的。

通过对Erlang和JVM中的垃圾回收方法进行比较,可以证明它们对容错的影响。

基本上, 从定义上说, 不可能在JVM之上构建实时的容错支持, 因为JVM本身不是容错的。

Erlang过程

从低层次看, 当我们在Erlang进程中遇到错误时会发生什么?语言本身使用消息在进程之间传递, 以确保任何错误的范围都受并发进程的限制。通过将数据类型存储为不可变的对象来工作。复制这些对象以限制进程状态的范围(大二进制文件是特殊的例外, 因为它们被引用为节省内存而被计数)。

从根本上讲, 这意味着如果我们要将变量X发送到另一个进程P, 则必须复制X作为其自己的不可变变量X’。我们无法从当前流程中修改X’, 因此, 即使触发某些错误, 我们的第二个流程P也不会受到影响。最终结果是对由于Erlang进程中的状态隔离而导致的任何错误范围进行了低级控制。如果我们想获得更多的技术知识, 我们会提到Erlang缺乏可变性, 因此它具有与Java不同的参照透明性。

这种类型的容错比仅添加try-catch语句和异常要深得多。在这里, 容错是关于处理意外的错误, 并且预期会出现异常。在这里, 即使其中一个变量意外爆炸, 你仍要尝试保持代码运行。

Erlang的进程调度可提供极高的可伸缩性, 以最少的源代码实现, 从而使系统更简单, 更易于维护。通过向库提供用户级线程(可能与内核级线程结合)和数据交换(类似于消息传递)来实现自己的Actor, 其他编程语言确实能够模仿Erlang内部固有的可伸缩性。对于并发计算的模型, 该工作无法复制Erlang虚拟机中提供的容错功能。

这使得Erlang既可扩展又可容错, 在编程语言中独树一帜, 使其成为云的理想开发平台。

利用Erlang

因此, 话虽如此, 我可以断言CloudI将Erlang的容错性和可伸缩性带到了其他各种编程语言(当前为C ++ / C, Erlang(当然), Java, Python和Ruby)中, 并在面向服务的体系结构(SOA)。

这种简单性使CloudI成为用于多语言软件开发的灵活框架, 无需程序员编写或理解一行Erlang代码即可提供Erlang的优势。

CloudI中执行的每个服务都与CloudI API进行交互。所有非Erlang编程语言服务均使用相同的内部CloudI Erlang源代码处理。由于所有非Errang编程语言都使用相同的最小Erlang源代码, 因此可以通过CloudI API的外部编程语言实现轻松添加其他编程语言支持。在内部, CloudI API仅对请求和响应进行基本序列化。这种简单性使CloudI成为用于多语言软件开发的灵活框架, 无需程序员编写或理解一行Erlang代码即可提供Erlang的优势。

服务配置指定启动参数和容错约束, 以便可以以受控和隔离的方式发生服务故障。启动参数明确定义了可执行文件及其所需的任何参数, 以及用于服务请求的默认超时, 查找服务的方法(称为”目标刷新方法”), 允许和拒绝简单访问控制列表(ACL)阻止传出的服务请求和可选参数, 以影响服务请求的处理方式。容错约束仅是两个整数(MaxR:最大重新启动, MaxT:最大时间段, 以秒为单位), 它们以与Erlang主管行为(Erlang设计模式)控制Erlang进程相同的方式控制服务。服务配置为服务的生命周期提供了明确的约束, 这有助于使服务的执行即使在发生错误时也易于理解。

为了使服务内存在运行时保持隔离状态, 将单独的操作系统进程用于每个非Erlang服务(称为”外部”服务), 并将其与关联的Erlang进程(用于每个非Erlang执行线程)调度在一起。 Erlang VM。 Erlang CloudI API创建了”内部”服务, 这些服务也与Erlang进程相关联, 因此”外部”服务和”内部”服务在Erlang VM中的处理方式相同。

该图描述了CloudI API与云计算实例和Erlang VM的交互。

在云计算中, 容错扩展到一台计算机之外(即分布式系统容错)也很重要。 CloudI使用分布式Erlang通信来交换服务注册信息, 因此可以通过为CloudI API发出的请求指定单个服务名称, 从而在CloudI的任何实例上透明地使用服务。所有服务请求都由发送服务进行负载平衡, 并且每个服务请求都是分布式事务, 因此同一服务在不同计算机上具有单独实例的能力可以在CloudI中提供系统容错能力。如有必要, 可以将CloudI部署在虚拟操作系统中, 以提供相同的系统容错能力, 同时促进稳定的开发框架。

例如, 如果HTTP请求需要在数据库中存储一些帐户数据, 则可以使用已配置的HTTP服务(由CloudI提供), 该服务会将请求发送到帐户数据服务进行处理(基于HTTP URL (用作服务名称), 然后将帐户数据存储在数据库中。每个服务请求在创建时都会收到一个通用唯一标识符(UUID), 该标识符可用于跟踪服务请求的完成情况, 从而使CloudI中的每个服务请求成为分布式事务。因此, 在帐户数据服务示例中, 可以同步或异步发出其他服务请求, 并在使用数据库之前使用服务请求UUID处理响应数据。明确跟踪每个单独的服务请求有助于确保在请求的超时时间内交付服务请求, 并且还提供了一种唯一标识响应数据的方法(服务请求UUID在所有连接的CloudI节点之间都是唯一的)。

在典型部署中, 每个CloudI节点都可以包含帐户数据服务的已配置实例(可以使用具有多个线程且每个线程都具有CloudI API对象的多个操作系统进程)和HTTP服务的实例。外部负载平衡器可以轻松地在CloudI节点之间拆分HTTP请求, 并且HTTP服务会将每个请求作为服务请求路由到CloudI中, 以便帐户数据服务可以轻松在CloudI中扩展。

CloudI在行动

CloudI允许你获取不可扩展的旧版源代码, 将其与瘦的CloudI服务包装在一起, 然后执行具有明确容错约束的旧版源代码。这种特殊的开发工作流程对于充分利用多核计算机, 同时提供在实时请求处理期间具有容错能力的分布式系统非常重要。在CloudI中创建”外部”服务只是在服务配置中已配置的所有线程中实例化CloudI API对象, 以便每个线程能够同时处理CloudI请求。一个简单的服务示例可以利用单个主线程创建单个CloudI API对象, 例如以下Python源代码:

import sys
sys.path.append('/usr/local/lib/cloudi-1.2.3/api/python/')
from cloudi_c import API

class Task(object):
    def __init__(self):
        self.__api = API(0) # first/only thread == 0

    def run(self):
        self.__api.subscribe('hello_world_python/get', self.__hello_world)
        self.__api.poll()

    def __hello_world(self, command, name, pattern, request_info, request, timeout, priority, trans_id, pid):
        return 'Hello World!'

if __name__ == '__main__':
    assert API.thread_count() == 1 # simple example, without threads
    task = Task()
    task.run()

该示例服务仅返回” Hello World!”。首先通过订阅服务名称和回调函数来访问HTTP GET请求。当服务开始在CloudI API轮询功能内处理传入的CloudI服务总线请求时, 由于提供了订阅, 来自提供HTTP服务器的”内部”服务的传入请求将根据服务名称路由到示例服务。如果请求需要类似于提供发布/订阅功能的典型分布式消息传递API中的发布消息, 则该服务也可能没有返回任何数据作为响应。该示例服务希望提供响应, 以便HTTP服务器可以将响应提供给HTTP客户端, 因此该请求是典型的请求/答复事务。利用服务的两种可能的响应(数据或无数据), 服务回调函数控制使用的消息传递范式(而不是调用服务), 因此该请求能够路由尽可能多的服务, 以在必要时提供响应, 在指定的超时内发生请求。超时对于实时事件处理非常重要, 因此每个请求都指定一个整数超时, 该超时在请求通过任何数量的服务进行路由时都遵循该请求。最终结果是, 实时约束与容错约束一起被强制执行, 以在存在许多软件错误的情况下提供可靠的服务。

任何软件中源代码错误的存在都应理解为一个明确的事实, 只有通过容错约束才能缓解。软件开发可以在维护软件时减少错误的出现, 但也可以在添加软件功能时添加错误。 CloudI提供的云计算能够在实时分布式系统软件开发中解决这些重要的容错问题。正如我在此CloudI和Erlang教程中演示的那样, CloudI提供的云计算是最小的, 因此不会牺牲效率以获取云计算的好处。

赞(0)
未经允许不得转载:srcmini » CloudI:将Erlang的容错能力带给多语言开发

评论 抢沙发

评论前必须登录!