基本概念

事件溯源(Event Sourcing)是一种设计模式,它记录并存储了应用程序状态变化的所有事件。

其核心思想是将系统中的每次状态变化都视为一个事件,并将这些事件以时间顺序的方式持久化存储。

这样,通过重放这些事件,我们可以重建系统在任何特定时间点的状态。

每个事件通常都包含了描述状态变化的必要信息,以及发生状态变化的原因和时间戳。

工作原理

工作原理方面,事件溯源主要依赖于两个关键部分:事件生成和事件存储。

当系统中发生状态变化时,会生成一个或多个事件,这些事件随后被存储到事件存储中。

事件存储需要设计成高可用、高一致且可伸缩的,以支持大规模的系统操作。

之后,当需要重建系统状态时,只需从事件存储中按顺序读取事件,并依次应用这些事件到系统状态即可。

使用场景

在Orleans7中,事件溯源主要应用在以下几个场景:

  1. 分布式系统状态同步:在分布式系统中,各个节点之间的状态同步是一个重要问题。通过事件溯源,每个节点都可以记录并发送自己的状态变化事件,其他节点则可以通过订阅这些事件来同步自己的状态。

  2. 历史数据追踪和审计:在某些业务场景下,需要追踪系统的历史操作记录,以进行审计或分析。事件溯源提供了完整的操作历史,可以方便地查询和回放历史事件。

  3. 容错和恢复:当系统发生故障时,通过事件溯源可以方便地恢复到故障发生前的状态,或者根据事件日志进行故障排查。

优势

事件溯源在Orleans7中带来了以下优势:

  1. 数据完整性和一致性:由于事件溯源记录了所有状态变化的历史,因此可以确保数据的完整性和一致性。

  2. 灵活性和可扩展性:事件溯源的设计使得系统可以很容易地添加新的状态变化事件,同时也支持大规模的系统扩展。

  3. 容错和恢复能力:通过事件溯源,可以轻松地恢复到系统的任何历史状态,大大提高了系统的容错和恢复能力。

  4. 清晰的业务逻辑:每个事件都代表了一个具体的业务操作,因此通过查看事件日志,可以清晰地了解系统的业务逻辑和操作流程。

总的来说,事件溯源是一种强大而灵活的设计模式,它在Orleans7中的应用为分布式系统带来了诸多优势。对于软件开发者来说,理解和掌握事件溯源机制,将有助于构建更加健壮、可靠和可扩展的分布式系统。

示例

下面使用事件溯源,来跟踪一个账户的变更记录。

首先需要安装必须的nuget包

<PackageReference Include="Microsoft.Orleans.EventSourcing" Version="8.0.0" />
<PackageReference Include="Microsoft.Orleans.Clustering.AdoNet" Version="8.0.0" />
<PackageReference Include="Microsoft.Orleans.Persistence.AdoNet" Version="8.0.0" />
<PackageReference Include="Microsoft.Orleans.Server" Version="8.0.0" />

然后设置Orleans,除了Orleans的常规设置外,还需要 siloHostBuilder.AddLogStorageBasedLogConsistencyProvider("LogStorage") 来设置LogConsistencyProvider

builder.Host.UseOrleans(static siloHostBuilder =>
{
var invariant = "System.Data.SqlClient"; var connectionString = "Data Source=localhost\\SQLEXPRESS;Initial Catalog=orleanstest;User Id=sa;Password=12334;"; siloHostBuilder.AddLogStorageBasedLogConsistencyProvider("LogStorage"); // Use ADO.NET for clustering
siloHostBuilder.UseAdoNetClustering(options =>
{
options.Invariant = invariant;
options.ConnectionString = connectionString;
}).ConfigureLogging(logging => logging.AddConsole()); siloHostBuilder.Configure<ClusterOptions>(options =>
{
options.ClusterId = "my-first-cluster";
options.ServiceId = "SampleApp";
}); // Use ADO.NET for persistence
siloHostBuilder.AddAdoNetGrainStorage("GrainStorageForTest", options =>
{
options.Invariant = invariant;
options.ConnectionString = connectionString;
//options.GrainStorageSerializer = new JsonGrainStorageSerializer() });
});

定义账户的存储和提取事件类

// the classes below represent events/transactions on the account
// all fields are user-defined (none have a special meaning),
// so these can be any type of object you like, as long as they are serializable
// (so they can be sent over the wire and persisted in a log). [Serializable]
[GenerateSerializer]
public abstract class Transaction
{
/// <summary> A unique identifier for this transaction </summary>
[Id(0)]
public Guid Guid { get; set; } /// <summary> A description for this transaction </summary>
[Id(1)]
public string Description { get; set; } /// <summary> time on which the request entered the system </summary>
[Id(2)]
public DateTime IssueTime { get; set; }
} [Serializable]
[GenerateSerializer]
public class DepositTransaction : Transaction
{
[Id(0)]
public uint DepositAmount { get; set; }
} [Serializable]
[GenerateSerializer]
public class WithdrawalTransaction : Transaction
{
[Id(0)]
public uint WithdrawalAmount { get; set; }
}

再定义账户的Grain,其中有存钱,取钱,获取余额,与变更记录操作

Grain类必须具有 LogConsistencyProviderAttribute 才能指定日志一致性提供程序。 还需要 StorageProviderAttribute设置存储。

/// <summary>
/// An example of a journaled grain that models a bank account.
///
/// Configured to use the default storage provider.
/// Configured to use the LogStorage consistency provider.
///
/// This provider persists all events, and allows us to retrieve them all.
/// </summary> /// <summary>
/// A grain that models a bank account
/// </summary>
public interface IAccountGrain : IGrainWithStringKey
{
Task<uint> Balance(); Task Deposit(uint amount, Guid guid, string desc); Task<bool> Withdraw(uint amount, Guid guid, string desc); Task<IReadOnlyList<Transaction>> GetTransactionLog();
} [StorageProvider(ProviderName = "GrainStorageForTest")]
[LogConsistencyProvider(ProviderName = "LogStorage")] public class AccountGrain : JournaledGrain<AccountGrain.GrainState, Transaction>, IAccountGrain
{
/// <summary>
/// The state of this grain is just the current balance.
/// </summary>
[Serializable]
[Orleans.GenerateSerializer]
public class GrainState
{
[Orleans.Id(0)]
public uint Balance { get; set; } public void Apply(DepositTransaction d)
{
Balance = Balance + d.DepositAmount;
} public void Apply(WithdrawalTransaction d)
{
if (d.WithdrawalAmount > Balance)
throw new InvalidOperationException("we make sure this never happens"); Balance = Balance - d.WithdrawalAmount;
}
} public Task<uint> Balance()
{
return Task.FromResult(State.Balance);
} public Task Deposit(uint amount, Guid guid, string description)
{
RaiseEvent(new DepositTransaction()
{
Guid = guid,
IssueTime = DateTime.UtcNow,
DepositAmount = amount,
Description = description
}); // we wait for storage ack
return ConfirmEvents();
} public Task<bool> Withdraw(uint amount, Guid guid, string description)
{
// if the balance is too low, can't withdraw
// reject it immediately
if (State.Balance < amount)
return Task.FromResult(false); // use a conditional event for withdrawal
// (conditional events commit only if the version hasn't already changed in the meantime)
// this is important so we can guarantee that we never overdraw
// even if racing with other clusters, of in transient duplicate grain situations
return RaiseConditionalEvent(new WithdrawalTransaction()
{
Guid = guid,
IssueTime = DateTime.UtcNow,
WithdrawalAmount = amount,
Description = description
});
} public Task<IReadOnlyList<Transaction>> GetTransactionLog()
{
return RetrieveConfirmedEvents(0, Version);
}
}

最后即可通过client生成grain,并获取账户变动记录

var palyer = client.GetGrain<IAccountGrain>("zhangsan");
await palyer.Deposit(1000, Guid.NewGuid(), "aaa");
var logs = await palyer.GetTransactionLog();
return Results.Ok(logs);

.NET分布式Orleans - 6 - 事件溯源的更多相关文章

  1. Newbe.Claptrap - 一套以 “事件溯源” 和“Actor 模式”作为基本理论的服务端开发框架

    本文是关于 Newbe.Claptrap 项目主体内容的介绍,读者可以通过这篇文章,大体了解项目内容. 轮子源于需求 随着互联网应用的蓬勃发展,相关的技术理论和实现手段也在被不断创造出来.诸如 “云原 ...

  2. ENode框架Conference案例分析系列之 - 事件溯源如何处理重构问题

    前言 本文可能对大多数不太了解ENode的朋友来说,理解起来比较费劲,这篇文章主要讲思路,而不是一上来就讲结果.我写文章,总是希望能把自己的思考过程尽量能表达出来,能让大家知道每一个设计背后的思考的东 ...

  3. InfluxDB 开源分布式时序、事件和指标数据库

    InfluxDB 是一个开源分布式时序.事件和指标数据库.使用 Go 语言编写,无需外部依赖.其设计目标是实现分布式和水平伸缩扩展. 特点 schemaless(无结构),可以是任意数量的列 Scal ...

  4. 从壹开始微服务 [ DDD ] 之终篇 ║当事件溯源 遇上 粉丝活动

    回首 哈喽~大家好,时间过的真快,关于DDD领域驱动设计的讲解基本就差不多了,本来想着周四再开一篇,感觉没有太多的内容了,剩下的一个就是验证的问题,就和之前的JWT很类似,就不打开一个章节了,而且这个 ...

  5. 事件溯源模式(Event Sourcing Pattern)

    此文翻译自msdn,侵删. 原文地址:https://msdn.microsoft.com/en-us/library/dn589792.aspx 本文介绍了一种有利于物化(materialize)领 ...

  6. Linux服务器应急事件溯源报告

    Linux服务器应急事件溯源报告 小博博 · 2016/02/18 17:43 Author:Inn0team 0x00 目录 关于目标环境的中间进度检测报告 一:情况概述 二:取证情况 2.1 目标 ...

  7. Orleans之EventSourcing

    Orleans之EventSourcing 这是Orleans系列文章中的一篇.首篇文章在此 引入: 如果没有意外,我再这篇文章中用ES代替EventSourcing,如果碰到"事件回溯&q ...

  8. Spring分布式事务实现概览

    分布式事务,一直是实现分布式系统过程中最大的挑战.在只有单个数据源的单服务系统当中,只要这个数据源支持事务,例如大部分关系型数据库,和一些MQ服务,如activeMQ等,我们就可以很容易的实现事务. ...

  9. CAP定理在分布式系统设计中的最新应用

    本文翻译自国外InfoQ和计算机杂志上一篇2012年旧文,本文就有关数据同步进行了讨论,特别关注业务事务的不变性与一致性如何在分布式系统中巧妙保证,探讨了长时间运行的事务的补偿机制.这些对分布式系统设 ...

  10. 分布式事务 | 使用 dotnetcore/CAP 的本地消息表模式

    本地消息表模式 本地消息表模式,其作为柔性事务的一种,核心是将一个分布式事务拆分为多个本地事务,事务之间通过事件消息衔接,事件消息和上个事务共用一个本地事务存储到本地消息表,再通过定时任务轮询本地消息 ...

随机推荐

  1. win32-ReadProcessMemory 的使用

    std::vector<std::byte> ReadBytes(PVOID address, SIZE_T length) { std::vector<std::byte> ...

  2. 2024-02-24:用go语言,给你一个 n 个点的带权无向连通图,节点编号为 0 到 n-1, 同时还有一个数组 edges ,其中 edges[i] = [fromi, toi, weighti

    2024-02-24:用go语言,给你一个 n 个点的带权无向连通图,节点编号为 0 到 n-1, 同时还有一个数组 edges ,其中 edges[i] = [fromi, toi, weighti ...

  3. ROS2 humble交叉编译环境配置

    目录 配置 toolchain文件: 库文件 交叉编译指令: 问题点: 1.find_package找不到自定义依赖包的问题 2.libarmadillo.so.10: undefined refer ...

  4. 【LeetCode贪心#08】根据身高重建队列(还是涉及处理两个维度的信息)

    根据身高重建队列 力扣题目链接(opens new window) 假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序).每个 people[i] = [hi ...

  5. 【Azure 应用服务】Azure Function (PowerShell) 执行时报错 "out of memory"

    问题描述 在Azure Function App服务中,创建一个PowerShell脚本的函数,遇见了OOM(Out Of Memory)的异常报错: 2022-01-10T07:44:37 Welc ...

  6. [linux 爬坑] 几个linux发行版尝试和令人崩溃的ssr安装体验

    最近电脑上的manjaro好像出了问题,长时间不用就会死机.也懒得追究原因了,正好决定尝试几个发行版.首先尝试安装银河麒麟    这个发行版实际上就是ubuntu,甚至源什么的都是ubuntu的,也不 ...

  7. 解密Spring中的Bean实例化:推断构造方法(上)

    在Spring中,一个bean需要通过实例化来获取一个对象,而实例化的过程涉及到构造方法的调用.本文将主要探讨简单的构造推断和实例化过程,让我们首先深入了解实例化的步骤. 实例化源码 protecte ...

  8. 从源码看webpack3打包流程

    在javascript刚刚流行时,前端项目通常比较简单,不需要考虑项目的开发效率.性能和扩展性等. 随着前端项目越来越复杂,需要更正式的软件开发实践,比如单元测试(unit testing).代码检查 ...

  9. Android\C++\C#\Java

    关于:(38条消息) 千万不能错过的Android NDK下载安装及配置_石子君的博客-CSDN博客_android ndk下载 (38条消息) Android扩展知识 - so文件生成及其使用_L- ...

  10. RocketMQ(6) offset管理

    这里的offset指的是Consumer的消费进度offset. 消费进度offset是用来记录每个Queue的不同消费组的消费进度的.根据消费进度记录器的不同,可以分为两种模式:本地模式和远程模式. ...