当我们谈论创建一个可扩展的应用程序时,微服务是一个热门词汇。与任何软件架构决策一样,它有权衡和挑战。其中一个挑战是环境的分布式性质。数据访问是另一个挑战。由于许多解决方案使用的数据是以与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 的集合的情况下,这是一个重要的调整。
默认情况下,拦截器只处理Map 和Iterable 类型的变量(如List 类型)。 如果你想存储整个根对象,包括所有非集合类型的变量,请使用成员root
@Store(root = true)
这个例子的代码也可以在MicroStream资源库中找到。
总结
通过MicroStream框架,你可以快速有效地查询和操作应用程序的数据,作为常规的Java类实例,不涉及任何映射。你还可以以安全的方式将数据存储到任何类型的blob存储中,不会出现Java序列化的安全漏洞。
CDI扩展是该框架第7版的一个新功能,允许你在Open Liberty运行时抽象出定义和处理。它使用CDI设施来删除对MicroStream代码的任何显式代码引用,除了一些注释,并使用MicroProfile配置设施来配置嵌入式存储管理器。
利用Open Liberty的所有功能来创建微服务,利用MicroStream在纯Java中进行超快的内存数据处理,微服务架构的一些挑战被这种技术组合成功地克服。