当前位置: 北京软件外包公司 » 资讯中心 » 新闻动态 » 北京软件外包公司通过CQRS/事件溯源框架Reveno实现高负载交易事

北京软件外包公司通过CQRS/事件溯源框架Reveno实现高负载交易事

发表于:2016-06-15 11:20 来源:北京软件开发公司宜天信达 点击:

  北京软件外包公司通过CQRS/事件溯源框架Reveno实现高负载交易事务处理,在当今世界,事务的处理每时每刻都在发生,其范围包括使用关系型数据库进行订购处理的个体零售网站,乃至每秒进行10多万次处理的实时交易系统。

  Reveno是一个全新的无锁事务处理框架,它支持JVM平台,并基于CQRS及事件溯源模式实现。虽然它只是一个简单而强大的工具,但在性能方面也毫不逊色。所有事务都被持久化为只读的日志,并且可以通过按顺序重演事件的方式恢复领域模型的最新状态。所有的运行时操作都是在内存中执行的,从而使其吞吐量可达到每秒种几百万次事务的数量级,平均延迟时间则限制在微秒级。虽然Reveno的功能如此强大,但它仍然是一个通用目的的框架,涵盖了大量不同种类的用例,并提供了丰富的引擎配置选项。举例来说,你可以调整它的持久性配置,在极其随意(为了提高系统吞吐量)与高度受限(对于数据丢失的情况具有较低的容忍度)之间进行选择。

  对于不同的用例来说,其需求也是千差万别的。作为一个通用框架,它需要考虑所有不同的可能性。在本文中,我将通过示例为读者展现如何使用Reveno框架开发一个简单的交易系统。

  首先让我们来了解一下Reveno如何处理你的领域模型。作为一个基于CQRS的框架,Reveno会将你的领域切分为事务模型与查询模型。对这两个模型的定义没有任何限制,你无需使用任何强制性的注解、基类,甚至不需要实现Serializable接口,只需使用最简单的POJO就可以完成任务。

  没有任何一种单一的途径能够应对所有不同的用例,因此应用程序的设计者需要决定如何处理事务模型的回滚操作。Reveno为实现模型对象提供了两种主要方式:可变与不可变模型。这两种方式各有不同的底层处理机制,并且各有其优缺点。在Java中,不可变对象的开销很低,并且无需进行同步操作,这种方式目前非常流行。Reveno能够非常高效地处理不可变对象,但每种使用不可变类型的领域都会产生额外的垃圾对象,Reveno也不例外。因此,如果你能够接受一些额外的GC开销,那么就应当将这种方式作为默认的选择。反之,如果使用可变模型,那么在进行事务运行时,就要为已使用的对象生成额外的序列化快照,而这将影响到你的性能。幸运的是,如果你坚持使用可变模型,并且仍需要保证性能的最大化及GC影响的最小化,那么你可以选择一种额外的可变模型特性,“补偿行为”(Compensating Actions)。简单来说,补偿行为是指在实现常规的事务处理函数时,一并实现的一种手动回滚行为。如果读者想了解这方面的更多细节,请参考Reveno的官方文档页面。

  现在,我们已经设计好了一些基本规则,那么让我们进行一些实际的编码工作吧。在我们所设计的交易系统中存在大量的帐户,每个帐户可以有零至多个订单。我们必须提供各种维护性操作,例如帐户的创建和订单的处理等等。在实际应用中,这种类型的系统可能需要应对大量的负载,例如每秒种处理10个直至50万个事务,甚至更多。更复杂的是,此类系统对于延迟非常敏感,而频繁出现的负载峰值可能会直接造成财务损失。

  安装

  如果你正在使用一些流行的构建工具,例如Maven、Gradle、Sbt等等,那么你可以在Maven Central中加入一个Reveno的依赖。目前为止,共有3个可用的库能够选择:

  reveno-core —— 包括所有Reveno核心功能包,负责引擎的初始化和事务处理等等。

  reveno-metrics —— 这个库中的包负责从运行中的引擎中收集各种指标,并将这些指标传递给Graphite、Slf4j等工具。

  reveno-cluster —— 支持在主-从架构的集群中运行Reveno,以提供故障转移的能力。

  你可以在Reveno安装页面中找到完整的安装指南与示例。

  定义事务模型

  让我们首先从领域模型的定义开始我们的开发过程。正如我们之前所说,领域模型将通过简单的POJO进行创建。我个人倾向于使用不可变对象,因为这将大大简化整个工作。它们不仅能够绕开各种并发问题,并且最重要的一点在于,由于不需要保留已访问对象的快照,因此它在Reveno中有非常出色的性能表现。Reveno允许我们直接使用不可变对象(可以说,Reveno也成为了一个帮助我们学习如何在常规的Java应用中处理不可变性的优秀教程)。

  让我们首先定义一个表现系统中典型的交易帐户的实体(为了简单起见,我们将实例变量都定义为public,但在实际应用中并不存在这种限制):

  public class TradeAccount {

  public final long id;

  public final long balance;

  public final String currency;

  private final LongSet orders;

  public TradeAccount(long id, String currency) {

  this(id, 0, currency, new LongOpenHashSet());

  }

  private TradeAccount(long id, long balance,

  String currency, LongSet orders) {

  this.id = id;

  this.balance = balance;

  this.currency = currency;

  this.orders = orders;

  }

  public LongSet orders() {

  return new LongOpenHashSet(orders);

  }

  }

  正如我们所见,这个类是不可变的。但这种值对象并不具备任何功能,往往因此被人称为“贫血”对象。因此,更好的方式是让TradeAccount类能够实现一些实用的功能,例如处理订单以及进行货币计算:

  public class TradeAccount {

  public final long id;

  public final long balance;

  public final String currency;

  private final LongSet orders;

  public TradeAccount(long id, String currency) {

  this(id, 0, currency, new LongOpenHashSet());

  }

  private TradeAccount(long id, long balance,

  String currency, LongSet orders) {

  this.id = id;

  this.balance = balance;

  this.currency = currency;

  this.orders = orders;

  }

  public TradeAccount addBalance(long amount) {

  return new TradeAccount(id, balance + amount, currency, orders);

  }

  public TradeAccount addOrder(long orderId) {

  LongSet orders = new LongOpenHashSet(this.orders);

  orders.add(orderId);

  return new TradeAccount(id, balance, currency, orders);

  }

  public TradeAccount removeOrder(long orderId) {

  LongSet orders = new LongOpenHashSet(this.orders);

  orders.remove(orderId);

  return new TradeAccount(id, balance, currency, orders);

  }

  public LongCollection orders() {

  return new LongOpenHashSet(orders);

  }

  }

  现在,这个类就变得非常实用了。在开始讲述实际的订单处理细节之前,首先要说明一下Reveno如何使用它的事务模型。所有的实体都会保存在某个repository中,任何类型的处理函数都可以访问该repository(我们稍后将对此进行详细地讲解)。这些实体相互之前通过ID进行引用,并通过ID在repository中进行访问。由于内部的性能优化机制,所有的ID都限制为long类型。

  Order类的定义也与之类似,为了简便起见,我们将忽略这部分源代码。不过,你可以在GitHub上下载完整的示例代码,并在本文的末尾找到更多的链接。

  定义查询模型

  我们已经简单地探索了如何创建Reveno中的事务模型。从逻辑上说,查询功能的定义也同样关键。在Reveno中,查询是通过“视图”的定义而创建的,每个视图都表现了事务模型中的某些实体。除了定义视图类之外,你还应当为每种视图类型提供映射器。我们稍后将对细节进行深入讲解。

  当一个事务成功地完成之后,Reveno将对所改变的实体进行映射操作,以保证视图的更新发生在命令完成之前。在默认情况下,Reveno中的查询模型是保存在内存中的。让我们为TradingAccount类定义一个视图:

  public class TradeAccountView {

  public final double balance;

  public final Set orders;

  public TradeAccountView(double balance, Set orders) {

  this.balance = balance;

  this.orders = orders;

  }

  }

  TradingAccountView类中还包括其他种类视图(在这个示例中对应着OrderView)的一个集合,在进行查询、序列化、JSON格式转换等操作时,这种方式能够带来很大的便利。Reveno映射器支持多种实用的方法,以简化将ID的集合映射到视图的集合等操作。我们稍后将进行一些实际操作。

  定义命令与事务行为

  为了在Reveno中执行事务,我们必须首先执行一个“命令”对象。命令对象本身可以是一个简单的POJO,它需要在系统中注册一个特定的处理函数。通常来说,命令将用于执行某些聚合与校验逻辑,以只读方式访问repository。但最重要的是,命令需要履行它的职责,以发送各种“事务行为”(因此命令也被称为“状态转变器”)。

  事务行为是用于在领域模型中进行状态改变的组件,它通过对repository的读-写访问以执行。事务行为对象本身可以表现为一个POJO,并在系统中注册对应的处理函数。所有行为组合在一起成为一个单一的原子性事务,它包含在当前所执行命令的范围内。在成功执行完成之后,事务行为将被持久化至底层的存储引擎中,并且在重启或发生任何故障之后重演其状态。