《通过C#学Proto.Actor模型》之Persistence
Actor是有状态的,当每一步执行失败后,返回失败地方继续执行时,希望此时的状态是正确的,为了保证这一点,持久化就成了必要的环节了。
Proto.Actor提供了三种方式执久化:
- Event Sourcing事件溯源
- Snapshotting快照
- Event Sourcing with Snapshotting带快照的事件溯源
不管是那种持久化方式,首先要构造一个持久化的提供者,这个提者是内存也好,数据库也罢,本例中用Sqlite作为持久化的载体;在Actor中,实现持久化,首先要创建一个Persistence对象,用来将快照或事件保存起来,最重要的一点是,我们用事件溯源或快照,是帮我们保留住Actor在某刻的状,保留下来,以便我们再次启动时能延续这个状态,所以Persistence有一个很关键的作用就是能从持久化的载体中把原来的状态回复过来,这里,Event Source的把原来的状态步骤走再走一次,到达当前流程的点,但快照不然,直接取的是最后时刻的状态;带快照的事件溯源则是两者的结合。
码友看码:
NuGet安装
Proto.Actor
Proto.Persistence
Proto.Persistence.Sqlite
using Microsoft.Data.Sqlite;
using Proto;
using Proto.Persistence;
using Proto.Persistence.SnapshotStrategies;
using Proto.Persistence.Sqlite;
using System;
using System.Threading.Tasks; namespace P008_Persistence
{
class Program
{
static void Main(string[] args)
{
//用sqlite持久化后
var actorid = "myactorid";
var dbfile = @"C:\MyFile\Source\Repos\ProtoActorSample\ProtoActorSample\P008_Persistence\data.sqlite";
var sqliteProvider = new SqliteProvider(new SqliteConnectionStringBuilder() { DataSource = dbfile });
while (true)
{
Console.WriteLine("1、事件溯源 2、快照 3、带快照的事件溯源 4、退出");
switch (Console.ReadLine())
{
case "":
CallEventSource(actorid, sqliteProvider);
break;
case "":
CallSnapShoot(actorid, sqliteProvider);
break;
case "":
CallSnapShootEventSource(actorid, sqliteProvider);
break;
case "":
return;
}
}
}
/// <summary>
/// 事件溯源
/// </summary>
/// <param name="actorid"></param>
/// <param name="sqliteProvider"></param>
private static void CallEventSource(string actorid, SqliteProvider sqliteProvider)
{
var props = Actor.FromProducer(() => new EventSourceDataActor(sqliteProvider, actorid));
var pid = Actor.Spawn(props);
var result = true;
while (result)
{
Console.WriteLine("1、Tell 2、删除持久化 3、退出"); switch (Console.ReadLine())
{
case "":
var random = new Random();
var no = random.Next(, );
Console.WriteLine($"随机产生的数字:{no}");
pid.Tell(new Data { Amount = no });
break;
case "":
//完成处理后清理持久化的操作
sqliteProvider.DeleteEventsAsync(actorid, ).Wait();
break;
case "":
result = false;
break;
}
}
} /// <summary>
/// 快照
/// </summary>
/// <param name="actorid"></param>
/// <param name="sqliteProvider"></param>
private static void CallSnapShoot(string actorid, SqliteProvider sqliteProvider)
{
var props = Actor.FromProducer(() => new SnapShootDataActor(sqliteProvider, actorid));
var pid = Actor.Spawn(props);
var result = true;
while (result)
{
Console.WriteLine("1、Tell 2、删除持久化 3、退出"); switch (Console.ReadLine())
{
case "":
var random = new Random();
var no = random.Next(, );
Console.WriteLine($"随机产生的数字:{no}");
pid.Tell(new Data { Amount = no });
break;
case "":
//完成处理后清理持久化的操作
sqliteProvider.DeleteEventsAsync(actorid, ).Wait();
break;
case "":
result = false;
break;
}
} }
/// <summary>
/// 快照事件溯源
/// </summary>
/// <param name="actorid"></param>
/// <param name="sqliteProvider"></param>
private static void CallSnapShootEventSource(string actorid, SqliteProvider sqliteProvider)
{
var props = Actor.FromProducer(() => new SnapShootEventSourceDataActor(sqliteProvider, sqliteProvider, actorid));
var pid = Actor.Spawn(props);
var result = true;
while (result)
{
Console.WriteLine("1、Tell 2、删除持久化 3、退出"); switch (Console.ReadLine())
{
case "":
var random = new Random();
var no = random.Next(, );
Console.WriteLine($"随机产生的数字:{no}");
pid.Tell(new Data { Amount = no });
break;
case "":
//完成处理后清理持久化的操作
sqliteProvider.DeleteEventsAsync(actorid, ).Wait();
sqliteProvider.DeleteSnapshotsAsync(actorid, ).Wait();
break;
case "":
result = false;
break;
}
}
}
} public class Data
{
public long Amount { get; set; }
} #region 事件溯源
public class EventSourceDataActor : IActor
{
private long _value = ;
private readonly Persistence _persistence; public EventSourceDataActor(IEventStore eventStore, string actorId)
{
//事件溯源持久化方式
_persistence = Persistence.WithEventSourcing(eventStore, actorId, ApplyEvent);
}
private void ApplyEvent(Proto.Persistence.Event @event)
{
switch (@event.Data)
{
case Data msg:
_value = _value + msg.Amount;
Console.WriteLine($"累计:{_value}");
break;
}
}
public async Task ReceiveAsync(IContext context)
{
switch (context.Message)
{
case Started _:
await _persistence.RecoverStateAsync();
break;
case Data msg:
await _persistence.PersistEventAsync(new Data { Amount = msg.Amount });
break;
}
}
}
#endregion #region 快照
public class SnapShootDataActor : IActor
{
private long _value = ;
private readonly Persistence _persistence; public SnapShootDataActor(ISnapshotStore snapshotStore, string actorId)
{
//快照持久化方式
_persistence = Persistence.WithSnapshotting(snapshotStore, actorId, ApplySnapshot);
}
private void ApplySnapshot(Proto.Persistence.Snapshot snapshot)
{
switch (snapshot.State)
{
case long value:
_value = value;
Console.WriteLine($"累计:{_value}");
break;
}
}
public async Task ReceiveAsync(IContext context)
{
switch (context.Message)
{
case Started _:
await _persistence.RecoverStateAsync();
break;
case Data msg:
_value = _value + msg.Amount;
await _persistence.DeleteSnapshotsAsync();
await _persistence.PersistSnapshotAsync(_value);
break;
}
}
}
#endregion #region 事件溯源and快照
public class SnapShootEventSourceDataActor : IActor
{
private long _value = ;
private readonly Persistence _persistence; public SnapShootEventSourceDataActor(IEventStore eventStore, ISnapshotStore snapshotStore, string actorId)
{
//注释快照策略
//_persistence = Persistence.WithEventSourcingAndSnapshotting(eventStore, snapshotStore, actorId, ApplyEvent, ApplySnapshot, new IntervalStrategy(5), () => { return _value; });
//无快照策略
_persistence = Persistence.WithEventSourcingAndSnapshotting(eventStore, snapshotStore, actorId, ApplyEvent, ApplySnapshot);
}
private void ApplyEvent(Proto.Persistence.Event @event)
{
switch (@event.Data)
{
case Data msg:
_value = _value + msg.Amount;
Console.WriteLine($"事件溯源累计:{_value}");
break;
}
}
private void ApplySnapshot(Proto.Persistence.Snapshot snapshot)
{
switch (snapshot.State)
{
case long value:
_value = value;
Console.WriteLine($"快照累计:{_value}");
break;
}
}
public async Task ReceiveAsync(IContext context)
{
switch (context.Message)
{
case Started _:
await _persistence.RecoverStateAsync();
break;
case Data msg:
await _persistence.PersistEventAsync(new Data { Amount = msg.Amount });
//无快照策略时启用
await _persistence.PersistSnapshotAsync(_value);
break;
}
}
}
#endregion
}
通过代码看到,持久化是通过在Actor中定义Persistence时,关联一个参数为,Event或Snapshot的方法,并且Actor的Receive方法在Stared到达是恢复(从持久载体中读取数据来恢复),在具体消息到达时,调用Persistence.PersistEventAsync或Persistence.PersisSnapshotAsync来持久化状态数据,这两个方法,都会把调用似递到Persistence产生是关联的那个方法,并把消息实体类通过Event.Data或Snapshot.State传递进去。
此例分别演示了事件溯源,快照,带快照事件溯源,例子很简单,就是把每次产生的随机数累加起来
1、事件溯源

三个绿色箭头,意思是进了三次“1、事件溯源”这个选项
三次蓝色箭头,意思是调用了三次Tell方法,用来获取三次随机数,蓝色椭圆是产生的三个数字,分别是7,7,6,蓝色方框是累计结果,从上往下,第一次是(0+7)7,第二次是(7+7)14,第三次是(14+6)20
红色箭头是退出事件溯源的方法,返回上一级
绿色方框是绿色箭头再次进入,自动恢复,事件溯源后的结果(即从持久化载体中把之前的所有事件重新走一次)还是之前退出时的结果,累计20,所以不管这个Actor在什么地方退出,再次运行,都会把之前的补运行回来。
也可以打开sqlite库进行查看保存的事件结果
2、 快照

快照与事件溯源类似,差别在于每次再次进来,只取上次退出时的结果,同时,在数据里,只保存了最后一次的结果。
3、带快照的事件溯源

与快照类似,上面代码我们是一个事件,一个快照。
官方给出带快照的事件可以通过快照策略来保存快照
在创建持久化对象时,可以添加快照策略
_persistence = Persistence.WithEventSourcingAndSnapshotting(eventStore, snapshotStore, actorId, ApplyEvent, ApplySnapshot, new IntervalStrategy(), () => { return _value; });
您可以选择ISnapshotStrategy在保存事件时指定自动保存快照。提供的策略是:
- EventTypeStrategy - 根据保存的事件类型保存快照
- IntervalStrategy - 根据保存的事件数量,即每100个事件,定期保存快照
- TimeStrategy - 根据时间以固定间隔保存快照,即在快照之间等待至少6小时
同时要在Actor的Receive把保存快照注释掉,Demo中我用的是5个事件后保存一次快照,如下图结果

绿色是第一次,要保存一下快照,然后之后第五个事件过来后保存第二次快照,如果在第四个事件后程序就退出,那快照保存的只有第一次的,不有担心,当再次调用时,因为记录下了所有事件,Actor会取出最后一次快照,再支执行快照后的事件,这是因为在保存快照和事件时,会把他们的索引保存起来,索引是一样的,就能用最后的快照+这个快照索引后的事件,恢复到退出的地方。
记得执行后查看Sqlite数据,有助于你更好的了解Proto.Actor的Persistence机制哦!
《通过C#学Proto.Actor模型》之Persistence的更多相关文章
- 《通过C#学Proto.Actor模型》之 HelloWorld
在微服务中,数据最终一致性的一个解决方案是通过有状态的Actor模型来达到,那什么是Actor模型呢? Actor是并行的计算模型,包含状态,行为,并且包含一个邮箱,来异步处理消息. 关于Actor的 ...
- 《通过C#学Proto.Actor模型》之Mailbox
邮箱是Actor模型的一个重要组成部分,负责接收发过来的消息,并保存起来,等待Actor处理.邮箱中维护着两种队列,一种是存系统消息,另一个是存用户消息,系统省是指Started,Stoping,St ...
- 《通过C#学Proto.Actor模型》之Prpos
在第一篇Proto.Actor博文中,HelloWorld的第一行真正代码是: var props = Actor.FromProducer(() => new HelloActor()) ...
- 通过C#学Proto.Actor模型》之Remote
Proto.Actor中提供了基于tcp/ip的通迅来实现Remote,可以通过其Remot实现对Actor的调用. 先来看一个极简单片的远程调用. 码友看码: 引用NuGet包 Proto.Acto ...
- 《通过C#学Proto.Actor模型》之Behaviors
Behaviors就是Actor接收到消息后可以改变处理的方法,相同的Actor,每次调用,转到不同的Actor内方法执行,非常适合按流程进行的场景.Behaviors就通过在Actor内部实例化一个 ...
- 《通过C#学Proto.Actor模型》之Supervision
Supervision,字面意思是监督,是父Actor发现子Actor有异常发生后,对子Actor产用保种策略处理的机制,如果父Actor不处理,则往上传递. 子Actor发生异常后处理的策略有: R ...
- 《通过C#学Proto.Actor模型》之PID
PID对象是代表Actor对象的进程,是能过Actor.Spawn(props)获取的:它有什么成员呢?既然代理Actor,首先有一个ID,标识自己是谁,Actor在Spawn时可以命名这个ID,否则 ...
- 《通过C#学Proto.Actor模型》之Spawning
Props是配置Actor和实例化Actor,那实例化后,就应该访问了,Props.Actor提供了Actor.Spawn(),Actor.SpawnPrefix(),Actor.SpawnNamed ...
- Proto.Actor模型
Proto.Actor模型 http://proto.actor/ https://github.com/axzxs2001/ProtoActorSample https://www.cnblogs. ...
随机推荐
- java 虚拟机内存划分,类加载过程以及对象的初始化
涉及关键词: 虚拟机运行时内存 java内存划分 类加载顺序 类加载时机 类加载步骤 对象初始化顺序 构造代码块顺序 构造方法 顺序 内存区域 java内存图 堆 方法区 虚拟机栈 本地 ...
- SpringBoot整合系列-整合MyBatis
原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9971036.html SpringBoot整合Mybatis 步骤 第一步:添加必要的j ...
- Linux之数据库操作
一.mysql基本操作 ,连接数据库 mysql -u root -p -h 127.0.0.1 mysql -u root -p -h 192.168.12.56 ,授予远程连接的权限 grant ...
- DAL分页
using System;using System.Collections.Generic;using LModel.DTO;using Newtonsoft.Json;using System.Da ...
- .NET Core Cache [MemoryCache]
参考资料:long0801的博客.MemoryCache微软官方文档 添加对Microsoft.Extensions.Caching.Memory命名空间的引用,它提供了.NET Core默认实现的M ...
- C#实现窗体全屏
方法一:设置窗体属性 //程序启动路径,与生成程序的exe文件在同一目录下 public String exePath = Application.StartupPath; //定义窗体宽高 ; ; ...
- 基本数据类型 int float str
一.数字型1.整型 int======================================基本使用======================================1.用途 用来 ...
- jsp基础语言-jsp声明
jsp声明的语法格式:<%! 声明代码 %> jsp声明的意义:用来定义在程序中使用到的变量.方法等.最后要以“:”结尾. jsp声明举例: <%! int a=100,b=200; ...
- jsp基础语言-jsp注释
JSP注释可分为客户端注释和服务器端注释. 客户端注释:用户可通过浏览器中的源代码查看,且这种注释可以加入JSP表达式. 语法:<!-- 客户端注释[<%=表达式%>] --> ...
- Oracle DB 12c first glance
单机上装了个12c,只看了看EM的界面……