当前位置:Java -> 编年史服务:无痛低延迟Java微服务

编年史服务:无痛低延迟Java微服务

低延迟?

在计算领域,延迟被定义为执行某个任务所需的时间长度。这可以是从硬件接收中断的时间,也可以是一个组件发送的消息可供其接收方使用的时间。

在许多情况下,延迟在设计应用程序时并不被视为主要的非功能性关注点,即使考虑性能。毕竟,大多数情况下,计算机好像以远远超出人类感知范围的速度工作,通常使用毫秒、微秒甚至纳秒的时间尺度。焦点通常更多地放在吞吐量上——衡量在给定时间内可以处理多少事件。然而,基本算术告诉我们,如果一个服务能够以低延迟(例如微秒)处理事件,那么它将能够在给定时间段内(比如1秒内)处理更多的事件,而不是具有毫秒级事件处理延迟的服务。这在许多情况下使我们能够避免实施服务的水平扩展(启动新实例)的需求,这种策略会给应用程序引入重大复杂性,对于某些工作负载甚至可能根本不可行。

此外,有许多应用领域,在这些领域,始终保持低延迟是应用成功的关键因素,例如:

  • 电子交易系统必须能够根据市场状况变化快速响应事件负载变化,以在竞争对手之前利用这些机会 - 通过这种方式可能获得巨额资金(或因错过这样的机会而损失)。在机会丢失之前,没有足够的时间来通过水平扩展来响应负载“峰值”——这可能需要多达一秒的时间。
  • 监控设备的系统,例如物联网领域中的系统,需要能够以最小的延迟对来自设备的指示作出反应。例如,警报,比如安全或环境警报,必须尽快通知并做出响应。监控本身引入的开销必须很小,以避免成为影响记录数据的因素。
  • 一些机器学习或人工智能算法需要对输入数据作出尽可能及时的反应,这使它们在定价、威胁检测、情感分析或买卖决策等领域更加有效。
  • 在线游戏软件必须能够接收来自大量用户的输入,尽可能地实时调整反馈和策略。

在Chronicle Software,我们的主要重点是开发最小化延迟的软件。通常认为Java不适合用于这类软件;然而,可以实现接近C++和Rust等低级语言的延迟数据。

构建低延迟软件的挑战

现代应用程序往往采用基于松散耦合组件(微服务)的架构方法,这些组件根据异步消息传递相互交互。存在许多工具包和框架可帮助实现Java中的这类微服务。

然而,采用这种方法构建真正的低延迟软件并不简单。延迟会在许多不同的层次出现。现有的微服务工具包往往侧重于提供的抽象质量,以保护其用户免受底层API和功能的影响。这种更高级别的抽象往往以创造大量中间对象为代价,对JVM的内存管理子系统施加重要负担——这对于低延迟编码是个大忌。

其他方法倾向于剥离几乎所有抽象,使开发人员暴露于最低级别的细节。虽然这显然消除了开销,但它会将更多的复杂性推入应用级代码,使其更容易出错并且更难维护和演变。

然而,即使在这个细节层次上,通常也需要理解并能够调整操作系统级别的参数以实现一致的低延迟。Chronicle Tune是一款产品,它可以根据Chronicle在该领域广泛的知识和经验来执行这种级别的分析和配置。

介绍Chronicle Services

多年来,Chronicle Software一直致力于构建在低延迟环境中运行的库、应用程序和系统,主要在金融领域。基于在此工作中获得的经验,我们已经开发出了一种基于事件驱动微服务构建低延迟应用程序的架构方法。

我们创建了Chronicle Services框架来支持这种方法,它负责必要的软件基础设施,并使开发人员能够专注于根据功能需求实现业务逻辑。

Chronicle Services

Chronicle Services提供了我们开发的几个专业库的观点,以支持低延迟应用程序。

理念

实现最小延迟严格要求的一个重要条件是消除意外复杂性。Spring Boot、Quarkus和Micronaut等框架提供了丰富的抽象集,以支持微服务的构建以及事件溯源和CQRS等模式。这些是必要设计用于支持通用应用程序的框架的有用组成部分,但是在构建高度专注的低延迟组件时,应该避免引入的复杂性。

Chronicle Services提供了一组更小的抽象,大大简化了框架,降低了底层JVM的负载,因此在处理事件时的开销要小得多。这导致单个服务每秒处理100万事件。我们还能够帮助客户重构系统,这些系统需要在多台服务器上运行,现在可以在一台服务器上运行(加上一个服务器以确保在发生故障时保持连续性)。

工作原理

Chronicle Services有两个关键概念:服务和事件。

服务是一个自包含的处理组件,从一个或多个源接收输入,并输出到一个单一的汇。服务输入和输出以事件的形式存在,其中事件是某事发生的指示。

默认情况下,事件在服务之间使用Chronicle Queue传输,这是一种持久化的低延迟消息框架,可以发送延迟低于1微秒的消息。事件以紧凑的专有二进制格式传输。编码和解码在时间和空间上都非常高效,并且在发送端或接收端不需要任何额外的代码生成。

服务和事件

构建服务

服务的公共接口由其期望的事件类型作为输入和输出的事件类型来定义。服务实现本身提供了每个输入事件的处理程序的实现。

服务与事件传递的基础设施有着明确的分离,因此开发人员可以专注于实现事件处理程序中封装的业务逻辑。服务在单个线程中处理所有传入事件,消除了处理并发性的需要,这是意外复杂性的另一个常见来源。

通过强大的测试框架可以进行详细的功能测试,其中输入事件与预期输出事件一起作为YAML提供。

服务的配置可通过API或基于外部文件的声明性方法进行,甚至可以通过事件进行动态配置更新。以下是一个简单服务应用的静态配置文件示例:

!ChronicleServicesCfg {

 queues: {

  sumServiceIn: { path: data/sumServiceIn },

  sumServiceOut: { path: data/sumServiceOut },

  sumServiceSink: { path: data/sumServiceSink },

 },
  services: {

  sumService: {

   inputs: [ sumServiceIn ],

   output: sumServiceOut,

   implClass: !type software.chronicle.services.ex1.services.SumServiceImpl,

  },

  sumUpstream: {

   inputs: [ ],

   output: sumServiceIn,

   implClass: !type software.chronicle.services.ex1.services.SumServiceUpstream,

  },

  sumDownstream: {

   inputs: [ sumServiceOut ],

   output: sumServiceSink,

   implClass: !type software.chronicle.services.ex1.services.SumServiceDownstream,

  }

 }

}


每个服务都以其实现类和用于事件传输的编年史队列的形式定义。这里有足够的信息,用于Chronicle服务运行时创建和启动每个服务。

在图表上,上述文件描述的应用程序将如下所示:

应用程序

部署服务

Chronicle服务支持多种部署服务的选项。多个服务可以共享单个线程,可以在多个线程上运行,或者分布在多个进程中。编年史队列是一种基于共享内存的IPC机制,因此在不同进程中的服务之间的消息交换非常快速。

服务还可以进一步封装到容器中,这可以简化部署,特别是在云环境中。

企业级特性

Chronicle服务基于编年史队列的企业版,提供基于集群的事件存储复制,以及其他企业级功能。复制基于单个领导者/多个跟随者模型,故障时集群领导者的活动/被动和活动/活动方法均可用于提供高可用性。

Chronicle服务应用还可以集成行业标准的监控和可观测性组件,例如Prometheus和Grafana,以提供其运行的可视化。

例如,我们可以获取应用程序的整体状态快照:

应用程序的整体状态

或来自各个服务的特定延迟统计信息:

各个服务的延迟统计信息监控解决方案的详细描述见本文

结论

为了获取应用程序的最佳延迟数据,通常需要摒弃所选语言的成语技术。有效地进行此操作需要时间,并且即使可以在代码的业务逻辑层中完成,支持框架并不总是提供相同级别的专业化。

Chronicle服务是一个高度主观的框架,利用Chronicle Software开发的库中实现的概念,支持具有市场领先的延迟性能的异步消息传递应用程序的开发。它的目标不是竞争Spring Boot或Quarkus等通用微服务框架。相反,它提供了一个低延迟平台,可以在其上使用简单的计算模型进行业务逻辑的层叠,使低延迟的优点无需付出痛苦。

推荐阅读: 数据工程师必备技能

本文链接: 编年史服务:无痛低延迟Java微服务