事件溯源(Event Sourcing)和命令查询责任分离(CQRS)经验

img

这篇文章是实现一个基于 CQRS 和事件溯源原则的应用程序,描述这个过程的方式,我相信分享我面临的挑战和问题可能对一些人有用。特别是如果你正在开始自己的旅程。

业务背景

项目的背景与空中交通管理(ATM)领域相关。我们为一个 ANSP(航空导航服务提供商)设计了一个解决方案,负责控制特定地理区域。这个应用程序的目标很简单:计算并持久化飞行数据。流程大致如下。

在飞机穿越其领空之前的几个小时,ANSP会收到来自 Eurocontrol 的信息,这个组织负责管理整个欧洲的航空交通。这些信息包含计划数据,如飞机类型、起飞地点、目的地、请求的航路等。一旦飞机到达了 ANSP 的 AOR(责任区域,ANSP负责控制和监控航班的区域),我们就可以从各种来源接收输入:航迹更新(飞行当前位置是什么)、修改当前航路的请求、由轨迹预测系统触发的事件、来自冲突检测系统的警报等。

img

虽然我们可能需要同时处理多个潜在的并发请求,但在吞吐量方面,与 PayPal 或 Netflix 没法相提并论。

尽管如此,该应用程序是安全关键环境的一部分。在发生关键故障的情况下,我们不会损失金钱或客户,但我们可能会失去人的生命。因此,实现一个可靠、响应迅速且具有弹性的系统,以保证数据一致性和完整性,显然是首要任务。

CQRS、事件溯源

这两种模式实际上都相当容易理解。

CQRS

CQRS(命令查询责任分离)是一种将写入(命令)和读取(查询)分离的方式。这意味着我们可以有一个数据库来管理写入部分。而读取部分(也称为视图或投影)是从写入部分派生出来的,可以由一个或多个数据库来管理(取决于我们的用例)。大多数情况下,读取部分是异步计算的,这意味着两个部分并不严格一致。我们稍后会回到这一点。

CQRS 背后的想法之一是数据库很难同时高效地处理读写操作。这可能取决于软件供应商的选择、应用的数据库调优等。例如,Apache Cassandra 在持久化数据方面效率很高,而 Elasticsearch 对搜索非常出色。使用 CQRS 真的是利用解决方案的优势的一种方式。

此外,我们还可以决定处理不同的数据模型。再次强调,这取决于需求。例如,在报告视图上使用的模型,另一个在写入部分持久化期间效率高的非规范化模型等。

关于视图,我们可能决定实现一些与消费者无关的视图(例如公开特定的业务对象),或者一些专门针对消费者的视图。

事件溯源

根据 Martin Fowler 对事件溯源的定义:

确保应用状态的所有更改都存储为事件序列

这意味着我们不存储对象的状态。相反,我们存储影响其状态的所有事件。然后,要检索对象状态,我们必须读取与该对象相关的不同事件,并逐个应用它们。

CQRS + 事件溯源

这两种模式经常被组合在一起。在 CQRS 之上应用事件溯源意味着将每个事件持久化在我们应用程序的写入部分。然后,读取部分是从事件序列派生出来的。

在我看来,实现 CQRS 时并不需要事件溯源。然而,反之则不一定成立。

事实上,对于大多数用例,在实现事件溯源时需要 CQRS,因为我们可能希望以 O(1) 的时间复杂度检索状态,而不必计算 n 种不同的事件。一个例外是简单的审计日志用例。在这里,我们不需要管理视图(也不需要状态),因为我们只对检索日志序列感兴趣。

领域驱动设计

领域驱动设计(DDD)是一种处理与领域模型相关的软件复杂性的方法。它由 Eric Evans 在 2004 年的《Domain-Driven Design: Tackling Complexity in the Heart of Software》一书中引入。

我们不会介绍所有不同的概念,但如果你对此不熟悉,我强烈建议你去看一看。不过,我们将只介绍在 CQRS/事件溯源应用程序环境中有用的概念。

DDD 带来的第一个概念是聚合(Aggregate)。聚合是一组领域对象,从数据变更角度来看,它们被视为一个单元。在聚合内部的事务必须保持原子性。

与此同时,聚合通过不变式来强制执行自己的数据一致性和完整性。不变式就是一个规则,无论如何变化,它都必须保持为真。例如,标准终端到达航线(STAR,基本上是着陆前的预定义航线)始终与给定机场相关联。一个不变式必须强制执行这样一个规则:目的机场不能在没有更改 STAR 的情况下被修改,并且这个 STAR 与该机场是有效的。

此外,作为聚合的外观对象(处理输入并将业务逻辑委托给子对象的对象)被称为聚合根。

关于组成聚合的对象,我们需要区分实体和值对象。实体是具有标识的对象,它不是由其属性定义的。一个人的年龄会随着时间的推移而改变,但他/她仍然是同一个人。另一方面,值对象仅由其属性定义。不同城市的地址是不同的地址。前者是可变的,而后者是不可变的。此外,实体可以有自己的生命周期。例如,一个航班首先准备起飞,然后是空中飞行,最后着陆。

在模型定义中,实体应尽可能简单,并专注于其标识和其生命周期。在 CQRS/事件溯源应用程序的上下文中,实体是一个关键元素,因为大多数情况下,在聚合内进行的更改是基于它们的生命周期。例如,至关重要的是确保每个实体实现了一个函数,用于确定它是否与另一个实体实例相等。这可以通过比较标识符或一组相关属性来实现,从而保证了一个标识。

既然我们已经了解了实体的概念,让我们回到不变式。为了定义它们,我们使用了受 BDD(行为驱动开发)格式启发的语言:

Given [entity] at state [state]
When [event] occurs
We shall [rules]

领域驱动设计(DDD)是一种处理与领域模型相关的软件复杂性的方法。它由 Eric Evans 在 2004 年的《Domain-Driven Design: Tackling Complexity in the Heart of Software》一书中引入。

我们不会介绍所有不同的概念,但如果你对此不熟悉,我强烈建议你去看一看。不过,我们将只介绍在 CQRS/事件溯源应用程序环境中有用的概念。

DDD 带来的第一个概念是聚合(Aggregate)。聚合是一组领域对象,从数据变更角度来看,它们被视为一个单元。在聚合内部的事务必须保持原子性。

与此同时,聚合通过不变式来强制执行自己的数据一致性和完整性。不变式就是一个规则,无论如何变化,它都必须保持为真。例如,标准终端到达航线(STAR,基本上是着陆前的预定义航线)始终与给定机场相关联。一个不变式必须强制执行这样一个规则:目的机场不能在没有更改 STAR 的情况下被修改,并且这个 STAR 与该机场是有效的。

此外,作为聚合的外观对象(处理输入并将业务逻辑委托给子对象的对象)被称为聚合根。

关于组成聚合的对象,我们需要区分实体和值对象。实体是具有标识的对象,它不是由其属性定义的。一个人的年龄会随着时间的推移而改变,但他/她仍然是同一个人。另一方面,值对象仅由其属性定义。不同城市的地址是不同的地址。前者是可变的,而后者是不可变的。此外,实体可以有自己的生命周期。例如,一个航班首先准备起飞,然后是空中飞行,最后着陆。

在模型定义中,实体应尽可能简单,并专注于其标识和其生命周期。在 CQRS/事件溯源应用程序的上下文中,实体是一个关键元素,因为大多数情况下,在聚合内进行的更改是基于它们的生命周期。例如,至关重要的是确保每个实体实现了一个函数,用于确定它是否与另一个实体实例相等。这可以通过比较标识符或一组相关属性来实现,从而保证了一个标识。

既然我们已经了解了实体的概念,让我们回到不变式。为了定义它们,我们使用了受 BDD(行为驱动开发)格式启发的语言:

img

应用程序设计

简而言之,应用程序接收命令并发布内部事件。这些事件被持久化到事件存储中,并发布给处理程序,这些处理程序负责更新视图。我们还可以决定在视图之上实现一个服务层(称为读处理程序)。

现在,让我们详细看看不同的场景。

聚合创建

命令处理程序接收一个 CreateFlight 命令,并在领域存储库中检查实例是否存在。这个领域存储库管理聚合实例。它首先在缓存中进行检查,如果对象不存在,则会在事件存储中进行检查。事件存储是一个用于持久化事件序列的数据库。我会稍后详细说明我认为一个好的事件存储是什么。在这种情况下,事件存储仍然为空,因此存储库不会返回任何内容。

命令处理程序负责触发不变式。在出现失败的情况下,我们可以同步抛出异常来指示业务问题。否则,命令处理程序将发布一个或多个事件到事件总线。事件的数量取决于内部数据模型的粒度。在我们的场景中,我们假设发布了一个单一的 FlightCreated 事件。

在此事件上触发的第一个组件是领域处理程序。这个组件负责根据实现的逻辑更新领域聚合。通常,逻辑被委托给聚合根(充当外观,但也可以将底层逻辑委托给子域对象)。请记住,聚合必须始终保持一致,并且还必须通过验证不变式来强制执行数据完整性。

如果处理程序成功(未引发业务错误),则事件将被持久化到事件存储中,并且缓存将使用最新的聚合实例进行更新。

然后,触发视图处理程序来更新其对应的视图。就像在普通的发布-订阅模式中一样,视图可以只订阅它感兴趣的事件。也许在我们的情况下,视图 2 是唯一对 FlightCreated 事件感兴趣的视图。

聚合更新

第二种情景是更新现有的聚合。在接收到 UpdateFlight 命令时,命令处理程序会请求存储库返回最新的聚合实例(如果有的话)。

如果实例已经在缓存中,则无需与事件存储交互。否则,存储库将触发所谓的重新装载过程。

这个过程是根据存储的事件序列计算聚合实例的当前状态的一种方式。从事件存储中检索的每个事件(比如 FlightCreatedDepartureUpdatedArrivalUpdated)都会被发布到事件总线。第一个领域处理程序触发 FlightCreated 时会实例化一个新的聚合(根据事件本身提供的信息,在内存中创建一个新的对象实例)。然后其他领域处理程序(由 DepartureUpdatedArrivalUpdated 事件触发)将更新刚刚创建的聚合实例。最终,我们能够根据存储的事件计算出状态。

一旦计算出状态,对象实例就会被放入缓存并返回给命令处理程序。然后,其余的流程与聚合创建情景相同。

关于重新装载过程还有一件事需要补充。如果一个聚合不在缓存中,而我们为一个特定的聚合实例存储了 1000 个事件,那么会花费很长时间来计算其状态。有一个已知的缓解措施叫做快照。

我们可以决定在每 n 个事件中持久化聚合的当前状态作为一个快照。这个快照也会包含在事件存储中的位置。然后,重新装载过程将简单地从最新的快照开始,并从指定的位置继续。快照还可以根据其他策略类型创建(如果重新装载时间超过某个阈值等)。

如何处理事件?

我想再回顾一下我们对命令和事件的区分。首先,有必要区分内部事件和外部事件。外部事件是由另一个应用程序产生的,而内部事件是由我们的应用程序生成的(基于外部命令)。

我们就如何在我们的应用程序中技术性地处理外部事件进行了一场有趣的辩论。我的意思是,真正的事件指的是已经在过去发生的事情(比如雷达轨迹)。

实际上有两种可能的处理方法:

  • 第一种方法是将事件视为命令。这意味着我们必须首先通过一个命令处理程序,验证不变式,然后生成一个内部事件。
  • 第二种方法是绕过命令处理程序,直接将事件持久化到事件存储中。毕竟,如果我们谈论的是一个真实事件,那么验证不变式等操作实际上是没有什么用的。然而,检查事件的语法仍然很重要,以确保我们不会污染事件存储。

如果我们选择第二个选项,可能会有兴趣在聚合重新装载期间实现规则。

让我们举一个雷达轨迹发布飞行位置的例子。如果生产者无法保证消息的顺序,我们还可以持久化一个时间戳(由生产者生成),并以这种方式计算状态:

if event.date > latestEventDate {  // Compute the statelatestEventDate = event.date} else {  // Discard the event}

这个规则将确保状态仅基于最新生成的事件。这意味着持久化一个事件不一定会影响当前状态。

在第一种方法中,在持久化事件之前会实现这样的规则。

事件模型

在事件存储中持久化的事件是否需要创建一个统一的模型?在我看来,答案是否定的(至少大部分情况下是)。

首先,因为我们可能希望随着时间推移持久化不同的模型版本。在这种情况下,我们必须实现一种策略,将一个模型版本的事件映射到另一个模型版本。

我想用一个具体的例子来说明另一个好处。假设一个应用程序接收来自系统 A 和系统 B 的事件。这两个系统基于各自的数据模型发布飞行事件。如果我们创建一个通用数据模型 C,我们需要在持久化事件之前将 A 转换为 C 和 B 转换为 C。然而,在项目的某个阶段,我们只对来自 A 和 B 的某些信息感兴趣。这意味着 C 只是 A 和 B 的一个子集。

但是如果以后我们需要对应用程序进行一些改进,并管理来自 A 和 B 的额外元素怎么办?因为事件是使用 C 格式持久化的,所以这些元素就会被简单地丢失。另一方面,如果我们决定持久化 A 和 B 格式,我们可以简单地对命令处理程序进行一些改进,以管理这些元素。

最终一致性

理论

最终一致性是由 CQRS(大多数情况下)引入的一个概念。理解其影响和后果非常重要。

首先,值得一提的是有不同的一致性级别。

最终一致性是一个模型,我们可以确保数据会被复制(从 CQRS 应用程序的写入部分到读取部分)。问题在于我们无法确切保证何时复制完成。这会受到各种因素的影响,比如整体吞吐量、网络延迟等。这是最弱的一致性形式,但提供了最低的延迟。

在 CQRS 应用程序中应用最终一致性意味着在某个时刻,写入部分可能与读取部分不同步。

相反地,我们可以找到强一致性模型。除非我们在分布式系统中使用相同的数据库来管理读取和写入,或者我们通过使用两阶段提交向恶魔出卖了我们的灵魂,否则在分布式系统中我们不应该达到这种一致性级别。

最接近的实现方法是,如果我们有两个不同的数据库,那就在单个线程中管理所有操作。这个线程将负责将数据持久化到写入数据库和读取数据库(们)。一个线程还可以专门用于单个聚合实例,并按顺序处理传入的命令。然而,如果在同步视图时发生瞬态错误,会有什么影响?我们需要补偿其他视图和 CQRS 应用程序的写入部分吗?我们需要实现错误重试循环吗?我们需要通过暂停命令处理程序来停止新的传入事件,应用断路器模式吗?解决显然会发生的瞬态错误是很重要的(凡是可能出错的地方迟早会出错)。

在最终一致性和强一致性两种一致性模型之间,我们可以找到许多不同的模型:因果一致性、顺序一致性等。举例来说,客户端单调一致性模型仅在会话(应用程序或服务实例)内保证强一致性。因此,实现 CQRS 应用程序并不只是在最终一致性和强一致性之间做出选择。

我个人的观点是:由于我们几乎无法保证强一致性,让我们尽可能地接受最终一致性。然而,前提是要精确理解其对系统其余部分的影响。

例子

让我们看一个我在项目中遇到的具体例子。

其中一个挑战是管理每架飞机的唯一标识符。我们不得不处理来自外部系统(公司外部)的事件,这些系统中的标识符并不相同。对于一个通道,标识符是一个复合标识符(出发机场 + 出发时间 + 飞机标识符 + 到达机场),而另一个通道则发送每架飞机的唯一标识符(但第一个通道不知道)。我们的目标是管理我们自己的唯一标识符(称为 GUFI,即全局唯一飞行标识符),并确保每个事件都对应于正确的 GUFI。

最简单的解决方案是确保每个传入的事件都在我们应用程序的特定视图中进行查找,以关联相应的 GUFI。但如果这个视图是最终一致的呢?在最坏的情况下,我们可能会有与同一飞行相关的事件,但使用不同的 GUFIs 进行存储(相信我,这是一个问题)。

一个解决方案可能是将这个 GUFI 的管理委托给另一个强一致性的服务。

在一次问答环节中,Greg Young 提供了另一个解决方案。我们可以实现一种缓冲区,其中只包含我们应用程序处理的 n 个最新事件。如果视图中不包含我们正在寻找的数据,我们必须在这个缓冲区中检查,以确保它不是刚刚在视图之前接收到的。n 越大,减轻写入和读取之间的这种不一致性窗口的机会就越大。

这个缓冲区可以使用像 Hazelcast、Redis 等解决方案进行分布式处理,也可以局部于应用程序实例。在后一种情况下,我们可能需要实现一个分片机制,使用哈希函数将相关对象的事件始终分发到相同的应用程序实例(最好是使用一种一致性哈希函数,以便轻松扩展)。

并发管理

几个月前我已经创建了一篇文章,描述了使用事件源管理并发更新的好处。

简而言之,拥有事件存储可能会帮助我们找到比悲观或乐观方法更聪明的解决方案来处理并发更新。

此外,在数据模型中应用正确的粒度也是项目成功的关键。

选择事件存储

我们可以决定使用任何类型的数据库来持久化事件序列。然而,最优解往往是为事件源构建的解决方案。

例如,隔离一个聚合实例是必须考虑的事情。假设所有事件都存储在一个单一表中。这个表会随着时间不断增长,在聚合重建时,我们将不得不过滤与一个特定聚合实例相关的事件。重建一个聚合的时间将取决于持久化的事件总数,即使其中一些事件与我们感兴趣的实例无关。一个好的解决方案可能是为每个聚合实例拥有一个表/存储桶,以隔离事件。我们称这个概念为流(stream)。一个流总是与一个聚合实例相关联(在大多数用例中)。

以下是我们考虑选择事件存储时的要求:

写入:

  • 恒定的写入延迟:无论流的大小如何,持久化事件的延迟都必须保持恒定
  • 原子性:可以在单个事务中追加多个事件
  • TTL 管理:根据创建日期自动丢弃事件
  • 无模式:可以存储多种事件类型和版本

读取:

  • 按写入顺序读取事件
  • 从特定序列号读取(因为快照)
  • 在给定流中保持恒定的读取性能,不受其他流的影响
  • 图形用户界面(GUI)
  • 缓存管理

并发:

  • 乐观并发模型
  • 幂等性管理

产品监控

解决方案支持

安全性:

  • 加密(传输)
  • 身份验证
  • 授权管理

扩展性

备份

每个上下文都是独特的,我相信你会有自己的要求,但这至少可能是一个起点。

结论

CQRS 和事件源并非魔法。在开始你的旅程之前,理解这两种模式的许多影响至关重要。否则,在技术和功能层面都很容易造成彻底的混乱。

然而,一旦你对约束和缺点有了明确的理解,CQRS 和/或事件源可能是许多问题的很好解决方案。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/156299.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++优先队列的使用

1. 什么是priority_queue priority_queue是C中的容器&#xff0c;实现优先队列。由于底层采用堆实现&#xff0c;所以插入和删除操作的时间复杂度为O(logn)&#xff0c;查找队首元素的时间复杂度为O(1)。 2. 构造priority_queue 【1】使用priority_queue需要先包含头文件<…

C++学习 --list

目录 1&#xff0c; 什么是list 2&#xff0c; 创建 2-1&#xff0c; 标准数据类型 2-2&#xff0c; 自定义数据类型 2-3&#xff0c; 其他创建方式 3&#xff0c; 操作list 3-1&#xff0c; 赋值 3-2&#xff0c; 添加元素 3-2-1&#xff0c; 添加元素(assign) 3-2-…

动手学深度学习——循环神经网络的简洁实现(代码详解)

文章目录 循环神经网络的简洁实现1. 定义模型2. 训练与预测 循环神经网络的简洁实现 # 使用深度学习框架的高级API提供的函数更有效地实现相同的语言模型 import torch from torch import nn from torch.nn import functional as F from d2l import torch as d2lbatch_size, …

卷积核的形象化解释

卷积核是在卷积神经网络&#xff08;CNN&#xff09;中使用的一种重要工具&#xff0c;它可以对输入数据进行特征提取和特征映射。在图像处理中&#xff0c;卷积核通常用于检测图像中的边缘、纹理、颜色等特征&#xff0c;从而帮助网络识别图像中的物体或模式。 为了形象化地解…

【蓝桥杯单片机(27)】超声波测距

一、简要说明 超声波测距传感器,一个发出超声波(A),另一个接收超声波(B) A如何发出超声波?给A的引脚一个持续的高低电平(方波)即可发出超声波。 B如何确认超声波已收到?B的一个引脚由低电平变为高电平即是收到了超声波。 因此,超声波测距分为两步,第一步发出超声波…

【Linux】文件操作

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;那个传说中的man的主页 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;题目大解析&#xff08;3&#xff09; 目录 &#x1f449;&#x1f3fb;文件是什么&#xff1f;&am…

艾泊宇产品战略:用户满意度是衡量产品成功的关键指标

在当今的商业环境中&#xff0c;企业做产品面临着巨大的挑战。 他们需要创造出满足用户需求的产品&#xff0c;同时还要考虑如何实现商业目标。 企业在做产品过程中必须时刻保持警惕&#xff0c;避免陷入“自嗨”的境地。 自嗨是指企业在设计和开发产品时&#xff0c;过于关…

Python3.11+Pyside6开发电影下载程序

VideoSave是一款使用Python3.11Pyside6编写的提供下载电影/电视剧的软件&#xff0c;支持注册、登录、搜索、下载、查看日志等功能&#xff0c;提供了Window、Mac系统安装包。 先上效果图 提供功能 节省寻找资源的时间 ⌚️模糊搜索指定影片 &#x1f434;查看影片下载日志 &…

【spring】如何解决循环依赖

概念 Spring循环依赖是指两个或多个Bean之间相互依赖&#xff0c;形成了双向依赖关系&#xff0c;导致Spring无法正确地完成Bean的创建和初始化。 Spring框架为了解决循环依赖问题&#xff0c;采用了三级缓存的方式来解决。 第一级缓存&#xff1a;单例池中的三级缓存 每个B…

Mac如何搭建Vue项目

目录 一、安装node 二、安装NPM 1、本地安装和全局安装 2、通过Node.js官方安装程序安装 3、通过Homebrew安装 三、NPM常用命令 1、查看模块的版本号 2、安装指定版本 3、卸载模块 4、更新模块 5、查看模块信息 6、查看模块地址 7、更新命令 8、卸载NPM 四、安装…

设计模式 - 概览

一、概念 分为三大类、23中具体设计模式。 类型原理具体模式创建型封装了具体类的信息&#xff0c;隐藏了类的实例化过程。 单例模式&#xff08;Singleton&#xff09; 工厂方法模式&#xff08;Factory Method&#xff09; 抽象工厂模式&#xff08;Abstract Factory&#xf…

观光奶牛 (01分数规划、负环)

01分数规划问题&#xff1a;类似于观光奶牛这个题中的&#xff0c;求的路径上的点权值和与边权值和的商最大最小。 当前问题的推到如下&#xff1a; 该问题其实可以用二分图来解决&#xff0c; 在不断的二分答案中获取符合条件的最大值。然后问题就转化为如何是否存在和为mid的…

Vue3中的pinia使用,入门教程

文章目录 文章目录 pinia组成部分 pinia使用流程 注意Store获取到后不能解构&#xff0c;否则失去响应式 一、pinia原理&#xff1f; 功能&#xff1a;管理全局共享数据&#xff0c;pinia与vuex功能一样优势&#xff1a;pinia相对于vuex增加了对ts的支持&#xff0c;对响应式的…

程序员如何“升级打怪”?我用了这几个“歪瓜”!

不会吧&#xff1f;不会吧&#xff1f;计算机本命专业出身、以及半路出家的&#xff0c;混了几年了&#xff0c;还在新手村&#xff1f;对得起这几年摸的鱼&#xff1f; 思考一下&#xff1a;如何从小白一跃为大师&#xff0c;从此走上人生巅峰、迎娶白富美&#xff1f;变强只…

Java --- JVM之垃圾回收相关知识概念

目录 一、System.gc() 二、内存溢出与内存泄漏 2.1、内存溢出 2.2、内存泄漏 三、Stop the world 四、垃圾回收的并行与并发 4.1、并发 4.2、并行 4.3、并行 vs 并发 4.4、垃圾回收的并发与并行 五、安全点与安全区域 5.1、安全点 5.2、安全区域 六、引用 6.1…

3.基于多能互补的热电联供微网优化运行复现(matlab代码)

0.代码链接 基于多能互补的热电联供微电网/综合能源系统优化运行&#xff08;Matlab程序Yalmip&#xff0b;Cplex求解&#xff09;_工业综合能源系统资源-CSDN文库 2. 主要内容&#xff1a;代码主要做的是多能互补的热电联供型微网优化运行模型&#xff0c;在需求侧对负荷类型…

hive数据库将非分区表数据迁移到分区表

文章目录 一、非分区表数据迁移到分区表 一、非分区表数据迁移到分区表 业务运行一段时间后非分区表的数据量非常大&#xff0c;需要创建一张分区表并将数据迁移到分区表中。 原表建表语句&#xff1a; create table user(id String default null comment 主键id,name St…

c# 文件操作

文件操作 namespace demo1;class proj {/// <summary>///文件&#xff1a;///注册表是Micrsoft windows 中的一个重要的数据库&#xff0c;用于存储系统和应用恒旭的设置信息///主要了解文件和注册表的创建、打开、读取、写入、修改、删除/// </summary>//////文件…

Android Studio 引入Xui框架-简单应用

Android Studio Flamingo | 2022.2.1 Patch 2 Android 11开发、Gradle Version 8.0、 jdk17 源代码&#xff1a;GitHub - xuexiangjys/XUI: &#x1f48d;A simple and elegant Android native UI framework, free your hands! (一个简洁而优雅的Android原生UI框架&#xff…

【Chrony】一个多功能的NTP实现

在现代的计算机系统中&#xff0c;准确的时间同步对于许多关键应用和系统的正常运行至关重要。Linux操作系统提供了多种时间同步工具&#xff0c;其中Chrony是一款备受推崇的工具&#xff0c;为Linux系统提供了更准确、更稳定的时间同步功能。本文将介绍Chrony的工作原理、优点…