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
复制代码
1 using Microsoft.Data.Sqlite;
2 using Proto;
3 using Proto.Persistence;
4 using Proto.Persistence.SnapshotStrategies;
5 using Proto.Persistence.Sqlite;
6 using System;
7 using System.Threading.Tasks;
8
9 namespace P008_Persistence
10 {
11 class Program
12 {
13 static void Main(string[] args)
14 {
15 //用sqlite持久化后
16 var actorid = "myactorid";
17 var dbfile = @"C:\MyFile\Source\Repos\ProtoActorSample\ProtoActorSample\P008_Persistence\data.sqlite";
18 var sqliteProvider = new SqliteProvider(new SqliteConnectionStringBuilder() { DataSource = dbfile });
19 while (true)
20 {
21 Console.WriteLine("1、事件溯源 2、快照 3、带快照的事件溯源 4、退出");
22 switch (Console.ReadLine())
23 {
24 case "1":
25 CallEventSource(actorid, sqliteProvider);
26 break;
27 case "2":
28 CallSnapShoot(actorid, sqliteProvider);
29 break;
30 case "3":
31 CallSnapShootEventSource(actorid, sqliteProvider);
32 break;
33 case "4":
34 return;
35 }
36 }
37 }
38 ///
39 /// 事件溯源
40 ///
41 ///
42 ///
43 private static void CallEventSource(string actorid, SqliteProvider sqliteProvider)
44 {
45 var props = Actor.FromProducer(() => new EventSourceDataActor(sqliteProvider, actorid));
46 var pid = Actor.Spawn(props);
47 var result = true;
48 while (result)
49 {
50 Console.WriteLine("1、Tell 2、删除持久化 3、退出");
51
52 switch (Console.ReadLine())
53 {
54 case "1":
55 var random = new Random();
56 var no = random.Next(5, 15);
57 Console.WriteLine($"随机产生的数字:{no}");
58 pid.Tell(new Data { Amount = no });
59 break;
60 case "2":
61 //完成处理后清理持久化的操作
62 sqliteProvider.DeleteEventsAsync(actorid, 100).Wait();
63 break;
64 case "3":
65 result = false;
66 break;
67 }
68 }
69 }
70
71 ///
72 /// 快照
73 ///
74 ///
75 ///
76 private static void CallSnapShoot(string actorid, SqliteProvider sqliteProvider)
77 {
78 var props = Actor.FromProducer(() => new SnapShootDataActor(sqliteProvider, actorid));
79 var pid = Actor.Spawn(props);
80 var result = true;
81 while (result)
82 {
83 Console.WriteLine("1、Tell 2、删除持久化 3、退出");
84
85 switch (Console.ReadLine())
86 {
87 case "1":
88 var random = new Random();
89 var no = random.Next(5, 15);
90 Console.WriteLine($"随机产生的数字:{no}");
91 pid.Tell(new Data { Amount = no });
92 break;
93 case "2":
94 //完成处理后清理持久化的操作
95 sqliteProvider.DeleteEventsAsync(actorid, 100).Wait();
96 break;
97 case "3":
98 result = false;
99 break;
100 }
101 }
102
103 }
104 ///
105 /// 快照事件溯源
106 ///
107 ///
108 ///
109 private static void CallSnapShootEventSource(string actorid, SqliteProvider sqliteProvider)
110 {
111 var props = Actor.FromProducer(() => new SnapShootEventSourceDataActor(sqliteProvider, sqliteProvider, actorid));
112 var pid = Actor.Spawn(props);
113 var result = true;
114 while (result)
115 {
116 Console.WriteLine("1、Tell 2、删除持久化 3、退出");
117
118 switch (Console.ReadLine())
119 {
120 case "1":
121 var random = new Random();
122 var no = random.Next(5, 15);
123 Console.WriteLine($"随机产生的数字:{no}");
124 pid.Tell(new Data { Amount = no });
125 break;
126 case "2":
127 //完成处理后清理持久化的操作
128 sqliteProvider.DeleteEventsAsync(actorid, 100).Wait();
129 sqliteProvider.DeleteSnapshotsAsync(actorid, 100).Wait();
130 break;
131 case "3":
132 result = false;
133 break;
134 }
135 }
136 }
137 }
138
139 public class Data
140 {
141 public long Amount { get; set; }
142 }
143
144 #region 事件溯源
145 public class EventSourceDataActor : IActor
146 {
147 private long _value = 0;
148 private readonly Persistence _persistence;
149
150 public EventSourceDataActor(IEventStore eventStore, string actorId)
151 {
152 //事件溯源持久化方式
153 _persistence = Persistence.WithEventSourcing(eventStore, actorId, ApplyEvent);
154 }
155 private void ApplyEvent(Proto.Persistence.Event @event)
156 {
157 switch (@event.Data)
158 {
159 case Data msg:
160 _value = _value + msg.Amount;
161 Console.WriteLine($"累计:{_value}");
162 break;
163 }
164 }
165 public async Task ReceiveAsync(IContext context)
166 {
167 switch (context.Message)
168 {
169 case Started _:
170 await _persistence.RecoverStateAsync();
171 break;
172 case Data msg:
173 await _persistence.PersistEventAsync(new Data { Amount = msg.Amount });
174 break;
175 }
176 }
177 }
178 #endregion
179
180 #region 快照
181 public class SnapShootDataActor : IActor
182 {
183 private long _value = 0;
184 private readonly Persistence _persistence;
185
186 public SnapShootDataActor(ISnapshotStore snapshotStore, string actorId)
187 {
188 //快照持久化方式
189 _persistence = Persistence.WithSnapshotting(snapshotStore, actorId, ApplySnapshot);
190 }
191 private void ApplySnapshot(Proto.Persistence.Snapshot snapshot)
192 {
193 switch (snapshot.State)
194 {
195 case long value:
196 _value = value;
197 Console.WriteLine($"累计:{_value}");
198 break;
199 }
200 }
201 public async Task ReceiveAsync(IContext context)
202 {
203 switch (context.Message)
204 {
205 case Started _:
206 await _persistence.RecoverStateAsync();
207 break;
208 case Data msg:
209 _value = _value + msg.Amount;
210 await _persistence.DeleteSnapshotsAsync(100);
211 await _persistence.PersistSnapshotAsync(_value);
212 break;
213 }
214 }
215 }
216 #endregion
217
218 #region 事件溯源and快照
219 public class SnapShootEventSourceDataActor : IActor
220 {
221 private long _value = 0;
222 private readonly Persistence _persistence;
223
224 public SnapShootEventSourceDataActor(IEventStore eventStore, ISnapshotStore snapshotStore, string actorId)
225 {
226 //注释快照策略
227 //_persistence = Persistence.WithEventSourcingAndSnapshotting(eventStore, snapshotStore, actorId, ApplyEvent, ApplySnapshot, new IntervalStrategy(5), () => { return _value; });
228 //无快照策略
229 _persistence = Persistence.WithEventSourcingAndSnapshotting(eventStore, snapshotStore, actorId, ApplyEvent, ApplySnapshot);
230 }
231 private void ApplyEvent(Proto.Persistence.Event @event)
232 {
233 switch (@event.Data)
234 {
235 case Data msg:
236 _value = _value + msg.Amount;
237 Console.WriteLine($"事件溯源累计:{_value}");
238 break;
239 }
240 }
241 private void ApplySnapshot(Proto.Persistence.Snapshot snapshot)
242 {
243 switch (snapshot.State)
244 {
245 case long value:
246 _value = value;
247 Console.WriteLine($"快照累计:{_value}");
248 break;
249 }
250 }
251 public async Task ReceiveAsync(IContext context)
252 {
253 switch (context.Message)
254 {
255 case Started _:
256 await _persistence.RecoverStateAsync();
257 break;
258 case Data msg:
259 await _persistence.PersistEventAsync(new Data { Amount = msg.Amount });
260 //无快照策略时启用
261 await _persistence.PersistSnapshotAsync(_value);
262 break;
263 }
264 }
265 }
266 #endregion
267 }
复制代码
通过代码看到,持久化是通过在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、带快照的事件溯源
与快照类似,上面代码我们是一个事件,一个快照。
官方给出带快照的事件可以通过快照策略来保存快照
在创建持久化对象时,可以添加快照策略
复制代码
1 _persistence = Persistence.WithEventSourcingAndSnapshotting(eventStore, snapshotStore, actorId, ApplyEvent, ApplySnapshot, new IntervalStrategy(5), () => { return _value; });
复制代码
您可以选择ISnapshotStrategy在保存事件时指定自动保存快照。提供的策略是:
EventTypeStrategy - 根据保存的事件类型保存快照
IntervalStrategy - 根据保存的事件数量,即每100个事件,定期保存快照
TimeStrategy - 根据时间以固定间隔保存快照,即在快照之间等待至少6小时
同时要在Actor的Receive把保存快照注释掉,Demo中我用的是5个事件后保存一次快照,如下图结果
绿色是第一次,要保存一下快照,然后之后第五个事件过来后保存第二次快照,如果在第四个事件后程序就退出,那快照保存的只有第一次的,不有担心,当再次调用时,因为记录下了所有事件,Actor会取出最后一次快照,再支执行快照后的事件,这是因为在保存快照和事件时,会把他们的索引保存起来,索引是一样的,就能用最后的快照+这个快照索引后的事件,恢复到退出的地方。
记得执行后查看Sqlite数据,有助于你更好的了解Proto.Actor的Persistence机制哦!
****欢迎关注我的asp.net core系统课程****
《asp.net core精要讲解》 https://ke.qq.com/course/265696
《asp.net core项目实战》 https://ke.qq.com/course/291868
《基于.net core微服务》 https://ke.qq.com/course/299524https://www.cnblogs.com/axzxs2001/p/9569899.html