利用MicroStream CDI扩展在Open Liberty中轻松快速地访问数据

56 阅读9分钟

当我们谈论创建一个可扩展的应用程序时,微服务是一个热门词汇。与任何软件架构决策一样,它有权衡和挑战。其中一个挑战是环境的分布式性质。数据访问是另一个挑战。由于许多解决方案使用的数据是以与Java对象完全不同的格式存储的,如带有表和列的数据库,由于需要映射,访问数据的速度会很慢。

Open Liberty是一个非常适合托管微服务的运行时间。它能与许多数据访问库很好地互动。在这篇博客中,我们将看看Open Liberty如何与MicroStream框架很好地合作,在微服务中提供快速的数据访问。 首先让我们快速浏览一下MicroStream。

MicroStream简介

MicroStream允许你非常有效和快速地将一个Java对象图写入数据存储中。这种存储可以是磁盘、数据库的blob存储,或任何其他媒介。由于你的数据存储在内存中,它提供微秒级的查询时间,低延迟的数据访问,以及巨大的数据吞吐量,因为数据只是Java实例。不需要映射和转换到一个数据库系统。

每当StorageManger 实例被启动时,先前存储的内容就会被加载到内存中。从那一刻起,所有的数据访问都只是访问内存中的Java实例,这是非常快速和高效的。MicroStream存储也是高度安全的。它只存储实例变量和一个类的标识。它不能像你用Java序列化那样重构一些任意的类实例。

在你更新或改变需要存储的数据后,你要执行一个存储操作,这样数据就会再次放在外部资源上,在你下次启动应用程序时就可以读取。

MicroStream CDI扩展是MicroStream第7版的一个新功能,它帮助你存储数据,只需要你添加一些注释。在你的代码中不需要MicroStream特定的语句。它被设计为与MicroProfile运行时一起工作,除了CDI,它还使用MicroProfile配置规范。

这篇博客将告诉你,当你在Open Liberty上开发你的微服务时,如何使用MicroStream CDI扩展。

MicroStream基础知识

让我们回顾一下MicroStream框架的基本用途,以编程方式配置数据存储。

        // Application-specific root instance
        final DataRoot root = new DataRoot();

        // Initialize a storage manager ("the database") with the given directory and defaults for everything else.
        final StorageManager storageManager = EmbeddedStorage.start(root, Paths.get("data"));

        // print the root to show its loaded content (stored in the last execution).
        System.out.println(root);

        // Set content data to the root element, including the time to visualize changes on the next execution.
        root.setContent("Hello World! @ " + new Date());

        // Store the modified root and its content.
        storageManager.storeRoot();

EmbeddedStorage 实例为你提供了所有的MicroStream功能。它使用磁盘上的一个目录来存储和检索你的数据的二进制表示,整个对象图,在这里由DataRoot 类表示。 每次你用store(Object) 存储根或属于根的对象图的实例时,内容就会被保存下来,供你下次启动应用程序时使用。

我们可以在一个MicroProfile应用程序中使用Microstream来实现快速和安全的数据访问。首先,让我们快速浏览一下MicroProfile。

MicroProfile

MicroProfile为微服务架构优化了企业Java。它基于Java EE/Jakarta EE标准,加上专门为微服务设计的MicroProfile API,如Rest Client、Configuration和Open API。这些规范可用于许多应用场景,不限于微服务。

有几个运行时可以实现MicroProfile规范。Open Liberty是其中的佼佼者,它为你提供了一个轻量级的框架来构建快速有效的云原生Java微服务。

在这篇博客中,我们将通过一个基本的例子来展示Open Liberty上新的MicroStream CDI扩展,该例子展示了一些CDI扩展功能。

要想轻松入门,你可以使用Open Liberty Starter创建一个Maven项目骨架,该项目已配置好并可在Open Liberty上运行。

前往Open Liberty Starter网站,完成以下步骤。

  • 选择Jakarta EE 8.0作为版本

  • 根据你的喜好更新组ID和工件ID

  • 点击 "生成项目"按钮。

屏幕应该看起来像下面的例子。

你会得到一个包含Maven POM文件和配置的ZIP文件,可以用Open Liberty运行应用程序。

您也可以看看MicroStream资源库中的示例代码。

CDI扩展

CDI扩展使您无需定义StorageManager ,通过使用Open Liberty运行时提供的CDI设施,明确调用存储方法。

要使用它,只需在你的Mavenpom.xml 文件中添加MicroStream CDI扩展的依赖项。

<dependency>
        <groupId>one.microstream</groupId>
        <artifactId>microstream-integrations-cdi</artifactId>
        <version>07.00.00-MS-GA</version>
</dependency>

该扩展使用嵌入式存储管理器,因此数据被存储在磁盘上。

配置StorageManager

嵌入式存储管理器是通过一些MicroProfile配置值配置的。 对于了解MicroProfile配置的人来说,只要这些值是在默认或配置的源中定义的,应用程序就能在启动时读取它们。如果你想了解更多关于MicroProfile配置的信息,请看一下规范文件

所有标准的MicroStream属性,如参考手册中列出的,通过使用以下语法惯例来支持。

All dashes are replaced by periods and the property is prefixed by ‘one.microstream.’

例如,one.microstream.storage.directory 属性键指的是存储-目录属性。

要将数据存储在磁盘上的某个目录中,你可以在microprofile-config.properties文件中添加以下一行。也支持绝对路径。你可以将该属性与server.xml文件一起存储为<variable name="one.microstream.storage.directory" value="target/data" /> 。另外,你可以把这个属性存储为环境变量或系统属性或其他配置源,因为这种配置应该在你的应用程序之外指定。

one.microstream.storage.directory=target/data

如果你想以编程方式访问一些方法,存储管理程序也可以作为CDI bean使用。但你还不需要这样做,因为我们将在稍后讨论CDI扩展的一些额外功能。

@Inject
Private StorageManager storageManager

定义根实例

正如我们在基本用法中所看到的,我们必须向框架提供一个实例,这样它就可以在整个对象图上循环,并确定在启动时需要存储和装载的实例。

由于我们不再自己实例化存储管理器,我们需要一种方法来指示根实例。使用下面的注解。

one.microstream.integrations.cdi.types.Storage

这个标记标识了表示根实例的类。一个实例被自动实例化,被定义为CDI bean,并与存储管理器链接,这样数据就可以被持久化了。我们只能在我们的应用程序或项目的依赖中使用一次该标记。

我们在GitHub上的Open Liberty例子中拥有以下的Root存储定义。

@Storage
public class Inventory {
    private final Set<Product> products = new HashSet<>();

    public void add(final Product product) {
        Objects.requireNonNull(product, "product is required");
        this.products.add(product);
    }

    public Set<Product> getProducts() {
        return Collections.unmodifiableSet(this.products);
    }

    public Optional<Product> findById(final long id) {
        return this.products.stream().filter(this.isIdEquals(id)).limit(1).findFirst();
    }

通过注释,我们可以将这个对象和整个对象图,包括Set中收集的每一个Product ,标记为可以被持久化的数据库。除了标记这个类,我们还可以实现所有的方法,对我们的产品库存进行添加、搜索、更新和删除等操作。

标明商店的行动

最后,我们需要指明我们何时要将对象图存储在磁盘上。 一个CDI拦截器是理想的选择,CDI扩展为此定义了@Store 注解。

        @Inject
        private Inventory inventory;


        @Store
        public Product save(final Product item)
        {
                this.inventory.add(item);
                return item;
        }

每当save() 方法被执行时,拦截器就会确保根实例的存储。在我们的例子中,根实例是Inventory 。CDI扩展试图进行一些优化,但是不能确切地知道它需要坚持的东西。例如,假设我们有下面的对象图。

Root
-> Set<Person>; Person has reference to Address
-> Set<Product>
-> Set<Order>; Order has reference to Person and Product

当你有一个方法updateAddress(Person, Address) ,你最好只存储Person,因为那是唯一的变化。但是我们不能通过注解来表明这一要求。如果你有一个非常大的对象图,建议注入StorageManager ,并自己触发单个实例的持久化。

MicroStream可以完美地处理部分更新,工作方式与Git类似。 你可以存储整个对象图,以后只存储更新的实例。 在启动时,当你在上一次运行中存储了一些东西时,它就会组装所有的碎片来重建最新的情况。 也会对碎片进行持续的清理,这样旧的blobs就会被移除,其他的则会被重组,以从存储中移除多余的信息。你可以配置花在这种房屋清理上的时间,以平衡对应用程序吞吐量的影响。

@Store 注解可以在一定程度上表明哪些内容需要被CDI拦截器持久化。

        @Store(fields = "products")
        public Product save(final Product item)
        {
                this.inventory.add(item);
                return item;
        }

在这种情况下,只有我们根的products 字段被存储。在库存的例子中,products 是唯一的字段。然而,在我们的根包含Person,products, 和Order 的集合的情况下,这是一个重要的调整。

默认情况下,拦截器只处理MapIterable 类型的变量(如List 类型)。 如果你想存储整个根对象,包括所有非集合类型的变量,请使用成员root

        @Store(root = true)

这个例子的代码也可以在MicroStream资源库中找到。

总结

通过MicroStream框架,你可以快速有效地查询和操作应用程序的数据,作为常规的Java类实例,不涉及任何映射。你还可以以安全的方式将数据存储到任何类型的blob存储中,不会出现Java序列化的安全漏洞。

CDI扩展是该框架第7版的一个新功能,允许你在Open Liberty运行时抽象出定义和处理。它使用CDI设施来删除对MicroStream代码的任何显式代码引用,除了一些注释,并使用MicroProfile配置设施来配置嵌入式存储管理器。

利用Open Liberty的所有功能来创建微服务,利用MicroStream在纯Java中进行超快的内存数据处理,微服务架构的一些挑战被这种技术组合成功地克服。