我有多个线程将事件写入
MySQL表事件.
该表有一个tracking_no列,配置为auto_increment,用于强制执行事件的排序.
不同的读者正在消耗事件,他们定期轮询表以获取新事件并保留最后消耗事件的值以获得每次轮询时的所有新事件.
事实证明,当前的实现有可能遗漏一些事件.
这就是发生的事情:
> Thread-1开始“插入”事务,它从auto_increment列(1)获取下一个值,但需要一段时间才能完成
> Thread-2开始“插入”事务,它接受下一个auto_incremente值(2)并完成Thread-1之前的写入.
> Reader轮询并询问tracking_number大于0的所有事件;它得到了事件2,因为Thread-1仍然落后.
事件被消耗,Reader将其跟踪状态更新为2.
> Thread-1完成插入,事件1出现在表中.
>读者在2之后再次轮询所有事件,并且在插入事件1时,它将永远不会被再次拾取.
似乎可以通过更改auto_increment策略来锁定整个表直到事务完成,但是如果可能的话我们会避免它.
最佳答案 我可以想到两种可能的方法.
1)如果您的事件插入保证成功(即,您永远不会回滚事件插入,因此您的tracking_no中永远不会有任何持续的间隙),那么您可以重写您的读者,以便他们跟踪最后一个连续事件看到 – 也就是成功处理的最后一个事件.
读者查询事件存储,按顺序开始处理事件,然后在找到间隙时停止.其余事件将被丢弃.下一个查询使用上次成功处理的事件的序列号.
尽管如此,回滚会弄乱这一点 – 并发写入的情况会在流中留下持久的空白,这会导致读者阻塞.
2)您可以使用及时表示的最大事件重写您的查询.有关设置时间戳列的机制,请参阅MySQL create time and update time timestamp.
接下来的想法是,您的读者查询序列号比上次成功处理的事件更高的所有事件,但时间戳小于now() – 一些合理的SLA间隔.
如果事件流的投影在时间上略微落后,则通常无关紧要.因此,您可以利用此功能,阅读过去的事件,从而保护您免受当前尚未完成的写入.
但是,这对域模型不起作用 – 如果您要加载一个事件流来准备写入,那么从过去的可测量间隔流开始工作并不会有太多乐趣.好消息是作者知道他们当前正在处理的对象的版本,因此他们生成的事件所属的序列在哪里.因此,您可以跟踪架构中的版本,并将其用于冲突检测.
注意我并不完全清楚序列号应该用于排序.见https://stackoverflow.com/a/9985219/54734
Synthetic keys (IDs) are meaningless anyway. Their order is not significant, their only property of significance is uniqueness. You can’t meaningfully measure how “far apart” two IDs are, nor can you meaningfully say if one is greater or less than another.
所以这可能是一个错误的问题.