关于实现一个基于文件持久化的EventStore的核心构思

  • 时间:
  • 浏览:0

另外刚刚统统刷磁盘的现象。亲们儿知道,通过文件流写入数据到文件后,意味不Flush文件流,那数据有意味还没刷到磁盘。统统时需定时Flush文件流,出于性能和可靠性的权衡,选用定时1s刷一次磁盘,通过异步应用线程刷盘。实际上,大次要NoSQL产品都在没法,比如Redis的fsync还才能指定为每隔1s刷一次AOF日志到磁盘。刚刚做唯一的现象是断电后意味丢失1s的数据,但这俩还才能通过在服务器上配置UPS备用电源确保断电后服务器还能工作,来确保断电后还能支持足够的时间确保亲们儿把文件流的数据刷到磁盘。刚刚既处里性能现象,才能保证不丢失数据。

这篇文章洋洋洒洒,都在思路性的东西,希望亲们儿看过无需枯燥,呵呵。欢迎亲们儿提出或者 人的意见和建议!

在调研的过程中,无意中发现LevelDB的插入性能非常高。它是由Google的MapReduce和BigTable的作者设计的刚刚基于key/value特征的轻量级的非常高效的开源的NoSQL数据库。它才能支持10亿级别的数据量存储。LevelDB 是单应用应用线程的服务,性能非常之高,在一台刚刚Q660 0的CPU机器上,每秒钟写数据超过40w,而随机读的性能每秒钟超过10w,足见性能之高。正意味他的高效,统统现在统统或者 NoSQL都使用它来作为底层的数据持久化,比如淘宝的Tair支持用LevelDB来持久化缓存数据。统统有时间研究下LevelDB的设计与实现非常有必要。或者LevelDB只提供最简单的key/value的操作。对于顺序插入事件的需求,还才能调用LevelDB的put操作。或者这里的put操作不支持并发冲突的检测,也统统意味连续put了刚刚key相同的value,则前刚刚value就会被后刚刚value所覆盖,这都在亲们儿你都能能的。统统亲们儿意味使用LevelDB,对于一块儿候聚合根没法或者版本号相同的事件这俩需求仍然时需亲们儿或者 人来保证,还才能通过里边DEMO中的思路来实现。也统统说,亲们儿仅仅用LevelDB来代替日志。未必刚刚意味省去亲们儿统统的工作量,意味亲们儿或者 人写日志以及记录每个事件的存储位置和长度都在一件容易的事情,要求对算法和逻辑非常学深悟,或者假如有一天刚刚bit错位了,意味读取出来的所有数据都错了。而LevelDB帮亲们儿完成了最繁复和头疼的事情了。但不幸的是,LevelDB没法官方的windows版本。让我 找到.net平台下的实现,但要在生产环境使用,还是要多做统统验证才行。另外,意味要用LevelDB来持久化事件,刚刚们的key还才能是聚合根ID+事件版本号的字符串拼接。这点应该先要理解吧!

或者,亲们儿主要要确保的是,对一块儿候聚合根实例,产生的事件意味版本号相同,则没法或者事件能保存成功,或者 的认为并发冲突,时需告诉内部管理有并发冲突了,或者由内部管理决定接下来该怎么做。没法怎么保证这俩点呢?

前面说到,所有聚合根的事件都在顺序的方法append到一块儿候文件,append事件到文件这俩步骤一种生活没方法检查与非 有并发冲突,文件没法帮亲们儿持久化数据,不负责检查与非 有并发冲突。那怎么检查并发冲突呢?思路统统在内存设计刚刚Dictionary,Dictionary的key为聚合根ID,value保存当前聚合根产生的事件的最大版本号,也统统最后刚刚事件的版本号。

里边还或者现象我还没提及,那统统光用刚刚文件来存储所有的事件还过低的,亲们儿还时需刚刚文件存储每个事件在文件中的位置和长度,或者亲们儿没方法知道每个事件存储在文件的哪里。也统统在当事件写入到文件后,亲们儿时需知道当前写入的起始位置,或者亲们儿还才能将这俩起始位置信息再写入到刚刚为宜索引作用的文件。这俩现象下次有意味在全部分析吧,总体思路和淘宝开源的高性能分布式消息队列metaq的消息存储架构非常类似于于。淘宝的metaq未必能高性能,很大一方面意味也是设计为顺序写文件,随机读文件的思路。如下图所示:

首先,每个聚合根实例有多个事件,每个时刻,每个聚合根意味都在产生多个事件或者要保存到eventstore中。为哪哪几个呢?意味亲们儿的domain model所在的应用服务器一般是集群部署的,统统全部有意味一块儿候聚合根在不同的机器上在被一块儿在做不同的修改,或者产生的事件的版本号是相同的,从而就会意味并发修改一块儿候聚合根的情況了。

或者或者方法还才能实现并发冲突的检测:

经过了一番调研,发现用文件存储事件非常为宜。要确保高性能,亲们儿还才能顺序写文件(append),或者随机读文件。未必要随机读文件是意味在当或者 command意味操作一块儿候聚合根而遇到并发冲突的刚刚,框架时需获取该聚合根的所有最新的事件,或者通过event sourcing重建出最新的聚合根,或者再重试哪哪几个遇到并发冲突的command。经过测试,顺序写文件和随机读文件都非常高效,每秒60 W次顺序写和每秒10W次随机读在我的笔记本上都在现象;意味在enode中,domain是基于in-memory架构的,统统亲们儿很少会从eventstore读取事件。统统重点是要优化持久化事件的性能。而读事件没法在command遇到并发冲突的刚刚或系统重启的刚刚,才有意味时需从eventstore读取事件。统统每秒10W次随机读取应该都在现象。当然,关于文件怎么写,见下面的遗留现象的分析。

亲们儿知道enode框架的架构是基于ddd+event sourcing的思想。亲们儿持久化的都在聚合根的最新情況,统统聚合根产生的领域事件。最近我在思考怎么实现刚刚基于文件的eventstore。目标或者:

1.时需要高性能;

2.支持聚合根事件的并发持久化,要确保单个聚合根实例无需保存版本号相同的事件;

上图中的commitlog文件为宜我里边提到的用来存储事件的文本文件,commitlog在metaq消息队列中是用来存储消息的。index文件为宜用来存储事件在commitlog中的位置和长度。在metaq中,则是用来存储消息在commitlog中的位置和长度。统统,从存储特征的深度图来看,metaq的消息存储和eventstore的事件存储的特征一致;但不一样的是,metaq在存储消息时,不时需做并发控制,所有消息假如有一天append消息到commitlog即可,所有的index文件也假如有一天append写入即可,关于metaq具体更全部的设计我还没深入研究,有兴趣的亲们也还才能和我交流。而eventstore则时需对事件的版本号做并发控制,这是最大的区别。另外,实际上,事件的索引信息还才能只时需维护在内存中即可,意味哪哪几个索引信息在eventstore启动时一直还才能通过commitlog还原出来。当然亲们儿维护一份Index文件也还才能,统统会增加事件持久化时的繁复度,这里到底与非 时需这俩Index文件,我时需再研究下metaq后才能更进一步明确。

从上图还才能看出,开启刚刚应用线程,并行操作刚刚聚合根,每个聚合根产生10个不同版本的事件(事件版本号连续递增),每个事件重复产生10W次,只花了为宜1s时间。另外,最后每个聚合根的当前版本号以及所对应的事件也都在正确的。统统,还才能看出,性能还不错。刚刚应用线程并行处里,每秒还才能处里60 W个事件(当然实际肯定没没法高,这里是意味大次要处里都被CompareExchange方法判断掉了。统统,没法没并发的情況,才是理想情況下的最快的性能点,意味每个事件都在做持久化和更新当前版本的逻辑,里边的代码主统统为了验证并发情況下与非 会产生重复版本的事件这俩功能。),且能保证无需持久化重复版本的事件。明天有空把持久化事件替换为真实的写文件流的方法,看看性能会有哪几个,理论上假如有一天写文件流够快,那性能应该依旧很高。