我有多个线程将事件写入MySQL表events
。
该表的tracking_no
列配置为auto_increment
,用于强制执行事件排序。
不同的读者正在从events
消费,他们定期轮询该表以获取新事件并保留最后消费事件的值以获得每次轮询时的所有新事件。
事实证明,当前的实现有可能遗漏一些事件。
这就是发生的事情:
Thread-1
开始“插入”事务,它从auto_increment列(1)获取下一个值,但需要一段时间才能完成Thread-2
开始“插入”事务,它接受下一个auto_incremente值(2)并在Thread-1
之前完成写入。Reader
轮询并询问tracking_number大于0的所有事件;它得到了事件2,因为Thread-1
仍然落后。
这些事件被消耗,Reader
将其跟踪状态更新为2。Thread-1
完成插入,事件1出现在表格中。Reader
再次轮询2之后的所有事件,并且在插入事件1时,它将永远不会被再次拾取。似乎可以通过更改auto_increment
策略来锁定整个表直到事务完成,但是如果可能的话我们会避免它。
答案 0 :(得分:1)
我可以想到两种可能的方法。
1)如果您的事件插入保证成功(即,您永远不会回滚事件插入,因此您的tracking_no中永远不会有任何持续的间隙),那么您可以重写您的读者,以便他们跟踪看到最后一个连续的事件 - 也就是最后一个成功处理的事件。
读者查询事件存储,按顺序开始处理事件,然后在找到间隙时停止。其余事件将被丢弃。下一个查询使用上次成功处理的事件的序列号。
但是,回滚会弄乱这一点 - 并发写入的情况会在流中留下持续的空白,这会导致读者阻止。2)您可以使用及时表示的最大事件重写您的查询。有关设置时间戳列的机制,请参阅MySQL create time and update time timestamp。
接下来的想法是,您的读者查询序列号高于上次成功处理事件的所有事件,但时间戳小于now() - 一些合理的SLA间隔。
如果事件流的投影在时间上略微落后,通常无关紧要。因此,您可以利用这一点,阅读过去的事件,从而保护您免受当前尚未完成的写入。
但是,对于域模型不起作用 - 如果您要加载事件流以准备写入,那么从过去的可测量间隔流开始工作是不会的很开心好消息是作者知道他们当前正在处理的对象的版本,因此他们生成的事件所属的序列在哪里。因此,您可以跟踪架构中的版本,并将其用于冲突检测。
注意我并不完全清楚序列号应该用于排序。见https://stackoverflow.com/a/9985219/54734
无论如何,合成键(ID)毫无意义。他们的命令并不重要,他们唯一的重要属性是独特性。你无法有意义地衡量相距甚远的方式。两个ID,也不能有意义地说一个是大于还是小于另一个。
所以这可能是一个错误的问题。