5.6 同时发生的突变之间的调解实例
你:你能给我举个例子,说明不冲突的并发突变之间的调解吗?
乔:当然,让我们从一个没有用户的图书馆和一个只有一本书的目录开始。Watchmen,如清单5.5所示。
清单5.5 一个小型图书馆的数据
var library = {
"name": "The smallest library on earth",
"address": "Here and now",
"catalog": {
"booksByIsbn": {
"978-1779501127": {
"isbn": "978-1779501127",
"title": "Watchmen",
"publicationYear": 1987,
"authorIds": ["alan-moore", "dave-gibbons"],
"bookItems": [
{
"id": "book-item-1",
"rackId": "rack-17",
"isLent": true
}
]
}
},
"authorsById": {
"alan-moore": {
"name": "Alan Moore",
"bookIsbns": ["978-1779501127"]
},
"dave-gibbons": {
"name": "Dave Gibbons",
"bookIsbns": ["978-1779501127"]
}
}
},
"userManagement": {
// omitted for now
}
}
乔:更新一本书的出版年份的突变将不会与更新同一本书的标题的突变相冲突。如清单5.6所示,调解将导致标题和出版年份都被更新的系统状态。
清单5.6 两个不冲突的突变:一个突变更新出版年份,另一个突变更新标题
var previous = library;
var next = _.set(library,
["catalog", "booksByIsbn", "978-1779501127", "publicationYear"],
1986);
var current = _.set(library,
["catalog", "booksByIsbn", "978-1779501127", "title"],
"The Watchmen");
你:previous和current的区别是什么样子的?
乔:它是一个嵌套的Map,有一个单独的字段,用来存放更新的标题(见清单5.7)。
清单5.7 previous 和current 的差异是一个嵌套的Hash Map,有一个字段。
DataDiff.diff(previous, current);
{
"catalog": {
"booksByIsbn": {
"978-1779501127": {
"title": "The Watchmen"
}
}
}
}
你:我猜上一个和下一个之间的差异也是由一个字段组成的?
乔:对。更新出版年份的单一字段(见清单5.8)。
清单5.8 previous 和next 之间的差异是一个嵌套的Hash Map,有一个单一的字段
DataDiff.diff(previous, next);
{
"catalog": {
"booksByIsbn": {
"978-1779501127": {
"publicationYear": 1986
}
}
}
}
你:很好! 现在我清楚地看到了代码是如何检测出这两个突变不冲突的:这两个嵌套图没有共同的叶子
乔: 没错! 调解的结果是一个library ,其中标题和出版年份都被更新,如清单5.9所示。
清单5.9 调解后的图书馆目录数据包含来自两个突变的变化
_.get(SystemConsistency.reconcile(current, previous, next),
"catalog");
{
"booksByIsbn": {
"978-1779501127": {
"isbn": "978-1779501127",
"title": "The Watchmen",
"publicationYear": 1986,
"authorIds": ["alan-moore", "dave-gibbons"],
"bookItems": [
{
"id": "book-item-1",
"rackId": "rack-17",
"isLent": true
}
]
}
},
"authorsById": {
"alan-moore": {
"name": "Alan Moore",
"bookIsbns": ["978-1779501127"]
},
"dave-gibbons": {
"name": "Dave Gibbons",
"bookIsbns": ["978-1779501127"]
}
}
}
你:那么一个矛盾突变的例子呢?
乔:两个更新同一本书出版年份的突变会发生冲突(见清单5.10)。
清单5.10 两个冲突的突变:两个突变都更新了同一本书的出版年份
var previous = library;
var next = _.set(library,
["catalog", "booksByIsbn", "978-1779501127", "publicationYear"],
1984);
var current = _.set(library,
["catalog", "booksByIsbn", "978-1779501127", "publicationYear"],
1986);
你:让我看一下这两个差异(见清单5.11)。
清单 5.11 在发生冲突的情况下,这两个差异包含相同的字段。
DataDiff.diff(previous, current);
{
"catalog": {
"booksByIsbn": {
"978-1779501127": {
"publicationYear": 1986
}
}
}
}
DataDiff.diff(previous, next);
{
"catalog": {
"booksByIsbn": {
"978-1779501127": {
"publicationYear": 1984
}
}
}
}
乔:在这种情况下,调解会失败。
你:正如你之前所说的,不应该过多地发生在同一时间,两个图书管理员试图更新同一本书的出版年份。如果发生这种情况,我认为可以中止突变,并要求图书管理员重新尝试。
5.7 总结
我们已经说明了在DO中,我们是如何用无锁的优化并发控制策略来管理并发的突变,从而实现读写的高吞吐量。我们在并发突变之间进行协调的方式类似于git处理两个分支之间合并的方式。
让我们的系统管理并发性所需的变化只在提交阶段。调解算法的实现并不简单,但在最后,它只涉及数据操作。这个调解算法是通用的,因为它可以用在任何系统中,其中系统数据被表示为一个不可变的Hash Map。
调解算法的实现是高效的,因为它利用了系统状态的后续版本是通过结构共享创建的这一事实。