LMAX 的架构(一)

这是一篇关于"The LMAX Architecture"的译文,译者为bonq

LMAX是一种新型零售金融交易平台,它能够以很低的延迟(latency)产生大量交易(吞吐量). 这个系统是建立在JVM平台上,核心是一个业务逻辑处理器,它能够在一个线程里每秒处理6百万订单. 业务逻辑处理器完全是运行在内存中(in-memory),使用事件源驱动方式(event sourcing). 业务逻辑处理器的核心是Disruptors,这是一个并发组件,能够在无锁的情况下实现网络的Queue并发操作。他们的研究表明,现在的所谓高性能研究方向似乎和现代CPU设计是相左的。(见另外一篇文章:JVM伪共享)      

过去几年我们不断提供这样声音:免费午餐已经结束。我们不再能期望在单个CPU上获得更快的性能,因此我们需要写使用多核处理的并发软件,不幸的是, 编写并发软件是很难的,锁和信号量是很难理解的和难以测试,这意味着我们要花更多时间在计算机上,而不是我们的领域问题,各种并发模型,如Actors 和软事务STM(Software Transactional Memory), 目的是更加容易使用,但是按下葫芦飘起瓢,还是带来了bugs和复杂性.

我很惊讶听到去年3月QCon上一个演讲, LMAX是一种新的零售的金融交易平台。它的业务创新 - 允许任何人在一系列的金融衍生产品交易。这就需要非常低的延迟,非常快速的处理,因为市场变化很快,这个零售平台因为有很多人同时操作自然具备了复杂性,用户越多,交易量越大,不断快速增长。

鉴于多核心思想的转变,这种苛刻的性能自然会提出一个明确的并行编程模型 ,但是他们却提出用一个线程处理6百万订单,而且是每秒,在通用的硬件上。

通过低延迟处理大量交易,取得低延迟和高吞吐量,而且没有并发代码的复杂性,他们是怎么做到呢?现在LMAX已经产品化一段时间了,现在应该可以揭开其神秘而迷人的面纱了。

结构图:

从最高层次看,架构有三个部分:
业务逻辑处理器business logic processor[5]
输入input disruptor
输出output disruptors

业务逻辑处理器处理所有的应用程序的业务逻辑,这是一个单线程的Java程序,纯粹的方法调用,并返回输出。不需要任何平台框架,运行在JVM里,这就保证其很容易运行测试环境。

业务逻辑处理器全部驻留在内存中

业务逻辑处理器有次序地取出消息,然后运行其中的业务逻辑,然后产生输出事件,整个操作都是在内存中,没有数据库或其他持久存储。将所有数据驻留在内存中有两个重要好处:首先是快,没有IO,也没有事务,其次是简化编程,没有对象/关系数据库的映射,所有代码都是使用Java对象模型。

使用基于内存的模型有一个重要问题:万一崩溃怎么办?电源掉电也是可能发生的,“事件”(Event Sourcing )概念是问题解决的核心,业务逻辑处理器的状态是由输入事件驱动的,只要这些输入事件被持久化保存起来,你就总是能够在崩溃情况下,根据事件重演重新获得当前状态。(NOSQL存储的基于事件的事务实现)

要很好理解这点可以通过版本控制系统来理解,版本控制系统提交的序列,在任何时候,你可以建立由申请者提交一个工作拷贝,版本控制系统是一个复杂的商业逻辑处理器,而这里的业务逻辑处理只是一个简单的序列。

因此,从理论上讲,你总是可以通过后处理的所有事件的商业逻辑处理器重建的状态,但是实践中重建所有事件是耗时的,需要切分,LMAX提供业务逻辑处理的快照,从快照还原,每天晚上系统不繁忙时构建快照,重新启动商业逻辑处理器的速度很快,一个完整的重新启动 - 包括重新启动JVM加载最近的快照,和重放一天事件 - 不到一分钟。

快照虽然使启动一个新的业务逻辑处理器的速度,但速度还不够快,业务逻辑处理器在下午2时就非常繁忙甚至崩溃,LMAX就保持多个业务逻辑处理器同时运行,每个输入事件由多个处理器处理,只有一个处理器输出有效,其他忽略,如果一个处理器失败,切换到另外一个,这种故障转移失败恢复是事件源驱动(Event Sourcing)的另外一个好处。

通过事件驱动(event sourcing)他们也可以在处理器之间以微秒速度切换,每晚创建快照,每晚重启业务逻辑处理器, 这种复制方式能够保证他们没有当机时间,实现24/7.

事件方式是有价值的因为它允许处理器可以完全在内存中运行,但它有另一种用于诊断相当大的优势:如果出现一些意想不到的行为,事件副本们能够让他们在开发环境重放生产环境的事件,这就容易使他们能够研究和发现出在生产环境到底发生了什么事。

这种诊断能力延伸到业务诊断。有一些企业的任务,如在风险管理,需要大量的计算,但是不处理订单。一个例子是根据其目前的交易头寸的风险状况排名前20位客户名单,他们就可以切分到复制好的领域模型中进行计算,而不是在生产环境中正在运行的领域模型,不同性质的领域模型保存在不同机器的内存中,彼此不影响。

性能优化

正如我解释,业务逻辑处理器的性能关键是按顺序地做事(其实并不愚蠢 并行做就聪明吗?),这可以让普通开发者写的代码处理10K TPS. 如果能精简代码能够带来100K TPS提升. 这需要良好的代码和小方法,当然,JVM Hotspot的缓存微调,让其更加优化也是必须的。

--省略未译部分

It took a bit more cleverness to go up another order of magnitude. There are several things that the LMAX team found helpful to get there. One was to write custom implementations of the java collections that were designed to be cache-friendly and careful with garbage[8]. An example of this is using primitive java longs as hashmap keys with a specially written array backed Map implementation (LongToObjectHashMap). In general they've found that choice of data structures often makes a big difference, Most programmers just grab whatever List they used last time rather than thinking which implementation is the right one for this context.[9]


Another technique to reach that top level of performance is putting attention into performance testing. I've long noticed that people talk a lot about techniques to improve performance, but the one thing that really makes a difference is to test it. Even good programmers are very good at constructing performance arguments that end up being wrong, so the best programmers prefer profilers and test cases to speculation.[10] The LMAX team has also found that writing tests first is a very effective discipline for performance tests.

编程模型

以一个简单的非LMAX的例子来说明。想象一下,你正在为糖豆使用信用卡下订单。一个简单的零售系统将获取您的订单信息,使用信用卡验证服务,以检查您的信用卡号码,然后确认您的订单 - 所有这些都在一个单一过程中操作。当进行信用卡有效性检查时,服务器这边的线程会阻塞等待,当然这个对于用户来说停顿不会太长。

在MAX架构中,你将此单一操作过程分为两个,第一部分将获取订单信息,然后输出事件(请求信用卡检查有效性的请求事件)给信用卡公司. 业务逻辑处理器将继续处理其他客户的订单,直至它在输入事件中发现了信用卡已经检查有效的事件,然后获取该事件来确认该订单有效。

这种异步事件驱动方式确实不寻常,虽然使用异步提高应用程序的响应是一个熟悉的技术。它还可以帮助业务流程更弹性,因为你必须要更明确的思考与远程应用程序打交道的不同之处。

这个编程模型第二个特点在于错误处理。传统模式下会话和数据库事务提供了一个有用的错误处理能力。如果有什么出错,很容易抛出任何东西,这个会话能够被丢弃。如果一个错误发生在数据库端,你可以回滚事务。

LMAX的内存模式(in-memory structures)在于持久化输入事件,如果有错误发生也不会从内存中离开造成不一致的状态。但是因为没有回滚机制,LMAX投入了更多精力,确保输入事件在实施任何内存状态影响前有效地持久化,他们发现这个关键是测试,在进入生产环境之前尽可能发现各种问题,确保持久化有效。

尽管业务逻辑是在单个线程中实现的,但是在我们调用一个业务对象方法之前,有很多任务需要完成. 原始输入来自于消息形式,这个消息需要恢复成业务逻辑处理器能够处理的形式。事件源Event Sourcing依赖于让所有输入事件持久化,这样每个输入消息需要能够存储到持久化介质上,最后整个架构还有赖于业务逻辑处理器的集群. 同样在输出一边,输出事件也需要进行转换以便能够在网络上传输。

如图复制和日志是比较慢的。所有业务逻辑处理器避免做任何IO处理,所有这些任务都应该相对独立,他们需要在业务逻辑处理器处理之前完成,它们可以以任何次序方式完成,这不同意业务逻辑处理器需要根据交易自然先后进行交易,这些都是需要的并发机制。

为了这个并发机制,他们开发了disruptor的开源组件。

Disruptor可以看成一个事件监听或消息机制,在队列中一边生产者放入消息,另外一边消费者并行取出处理. 当你进入这个队列内部查看,发现其实是一个真正的单个数据结构:一个ring buffer. 每个生产者和消费者都有一个次序计算器,以显示当前缓冲工作方式.每个生产者消费者写入自己次序计数器,能够读取对方的计数器,生产者能够读取消费者的计算器确保其在没有锁的情况下是可写的,类似地消费者也要通过计算器在另外一个消费者完成后确保它一次只处理一次消息。

    输出disruptors也类似于此,但是只有两个有顺序的消费者,转换和输出。输出事件被组织进入几个topics, 这样消息能够被发送到只有感兴趣的topic中,每个topic有自己的disruptor.

disruptor不但适合一个生产者多个消费者,也适合多个生产者。

disruptor设计的好处是能够容易让消费者快速抓取,如果发生问题,比如在15号位置有一个转换问题,而接受者在31号,它能够从16-30号一次性批量抓取,这种数据批读取能力加快消费者处理,降低整体延迟性。

ring buffer是巨大的: 输入2千万号槽;4百万输出. 次序计算器是一个64bit long 整数型,平滑增长(banq注:大概这里发现了JVM的伪共享),象其他系统一样disruptors过一个晚上将被清除,主要是擦除内存,以便不会产生代价昂贵的垃圾回收机制启动(我认为重启是一个好的习惯,以便你应付不时之需。)

日志工作是将事件存储到持久化介质上,以便出错是重放,但是他们没有使用数据库来实现,而是文件系统,他们将事件流写到磁盘上,在现代概念看来,磁盘对于随机访问是非常慢,但是对于流操作却很快,也就是说,磁盘是一种新式的磁带。

之前我提到LMAX运行在集群多个系统拷贝能够支持失败回复,复制工作负责这些节点的同步,所有节点联系是IP广播, 这样客户端能够不需要知道主节点的IP地址. 只有主节点直接听取输入事件,然后运行一个复制工作者,复制工作者将把输入事件广播到其他次要节点. 如果主节点当机,心跳机制将会发现, 另外一个节点就成为主节点,开始处理输入事件,启动复制工作者,每个节点都有自己的输入disruptor这样它有自己的日志处理和格式转换。

未完成,后续转到:LMAX 的架构(二)

猜你喜欢

转载自huihuilou.iteye.com/blog/1973036
今日推荐