リアルタイムの共同編集を達成するために

リアルタイムの共同編集とは何ですか

ここでのリアルタイムの共同編集、人々が同時に文書を編集するために指し、最も典型的な例はGoogleドキュメントでは、手動でページを更新しなくても、他の人によって作られたリアルタイムの変化を見ることができます。

リアルタイム編集を実現するために、我々は2つの技術的なポイントを解決する必要があります:リアルタイム通信の問題が、リアルタイム通信は、あなたが長いプルかのWebSocketを使用することができ、問題を解決するために優れている競合を、編集、そうあまりにも多くの議論がない、焦点は解決方法になります競合を編集します。

オプション

次に、いくつかの実行可能な選択肢、すなわち導入するために困難に簡単にから:「分散操作変換」「編集ロック」、「GNU diffのパッチ」、「マイヤーの差分パッチ」、「操作変換」とし。

編集ロック

これは、相乗効果の編集ロック編集最も簡単な方法を達成することで、誰かが文書を編集しているとき、システムは、文書をロックします簡単なので、同じ時間を編集することから、他の人のを防ぐだけであるので、このプログラムは、最も広く使用されていますそのような一般的TWikiに使用される社内カンパニー制として、この方法が、あなたはある程度問題が上書きされないようすることができますが、その経験は良くない、「リアルタイム」を行うことができないので、私たちがここで説明されていません。

GNU diffのパッチ

Gitリポジトリや他のバージョン管理ソフトウェアは、実際に共同編集ツールで、誰もが編集を並行することができるので、同じファイルを編集する際に自動的に遭遇マージすることができますので、我々は、同様の原理を利用して共同編集を実装することができ、2つの特定があります方法:差分パッチおよびマージ。

限り、我々はJSでこれら2つのアルゴリズムを実現するように、差分とパッチは2 UNIXのコマンドを指し差分パッチで始まる2つのテキストの間の差分を出力することができるの違い、および、他のファイルを更新するためのパッチを使用共同編集プロセスによって達成することができます。

  1. 各ユーザーは、現在のドキュメントのコピーを保存するときに来た長い接続を確立しています
  2. 一時停止した場合、誰かが5秒(特定の製品戦略に応じて)編集したとき、それは既存のドキュメントと前回の行動デフ、サーバーへの結果、更新されたコピーをコピーします
  3. サーバーは、ドキュメントを更新し、同じ時間を編集して、他のユーザーへのdiff結果への長い接続を介して、これらのユーザーがta自分の文書を更新するために、パッチメソッドを使用します

GNU diffのが、行のマッチングに基づいているため、問題は、それが簡単に競合ですので、私たちは「Baiduのウェブ」と「BaiduのWebフロントエンド」の結果デフこれら2つのテキストをテストしてみましょう、があり、

[nwind@fex ~]$ diff old.txt other-new.txt > old-to-other-new.patch
[nwind@fex ~]$ cat old-to-other-new.patch
1c1
< 百度 Web
---
> 百度 Web 前端

この差分結果では、1c1最初の「1」の最初の行の前に「C」が後に「修飾された」行の背面を表し、変更を表す第二が「1」変形を表し、それは最初の行を言うことです「BaiduのWebフロントエンド『から』百度のウェブ」、変更されたコンテンツは、最初の行を配置します。彼らは両方の競合の行を変更するかどうかを意味し、以下の試験により確認することができます。

[nwind@fex ~]$ cat my-new.txt
Web

[nwind@fex ~]$ patch my-new.txt < old-to-other-new.patch
patching file b-new.txt
Hunk #1 FAILED at 1.
1 out of 1 hunk FAILED -- saving rejects to file my-new.txt.rej

[nwind@fex ~]$ cat my-new.txt.rej
***************
*** 1
- 百度 Web--- 1 -----
+ 百度 Web 前端

どこにmy-new.txt下記のように私の修正版があり、私は「ウェブ」、実際には、この2つの変更の競合ではありませんを残し、以前の「百度」を削除、彼らは、「Webフロントエンド」にまとめることができます

2833665-9d435c2c58262a3c.png
マージ

しかし、それは新たな紛争後のファイルを生成しますパッチを使用するよう部下に命じたmy-new.txt.rej失敗の理由を記述するために、この2つのファイル比較を開くために必要性を示すための直感的な方法ではありません、私たちはより良い、そのアクセスを実証する別の方法を使用します次のようにmergeコマンドを下に紹介し、その使用は次のとおりです。

[nwind@fex ~]$ merge my-new.txt old.txt other-new.txt
merge: warning: conflicts during merge

[nwind@fex ~]$ cat my-new.txt
<<<<<<< my-new.txt
Web=======
百度 Web 前端>>>>>>> other-new.txt

あなたが書いた地域紛争に直接それを見ることができmy-new.txt、再び、これはパッチよりも便利であると思われる、この結果は、Gitのようマージコマンドやツールがあるため、ほとんどの学生が、見覚えすると推定されているアルゴリズムは同じであるマージ

我々は、我々は新しいパッチとリアで、転送量の差分を減らすために組み合わせることができ、完全なテキストの3つの部品の使用を必要と欠点が毎回すべてのテキストコンテンツの配信を避けるために、比較されるようにしているマージコマンドを使用して見ることができますテキスト。

差分かどうか、マージ、彼らはラインを比較するためのアルゴリズムに基づいているため、同じライン上避けられない競合エディタにつながる、この問題を解決するために、我々は、文字サイズに基づいて差分アルゴリズムに試すことができるということです、次はマイヤーの差分をご紹介します-patch。

マイヤーの差分パッチ

マイヤーアルゴリズムは、それが多くの持っている、別の差分パッチアルゴリズムである言語のオープンソース実装を次のように、その差分の結果を見て、すべての最初の、私たちはその効果をテストするために、ここでは、前の例を直接使用するアルゴリズムの詳細を紹介しません呼び出しコード:

var old_text = "百度 Web";
var new_text = "百度 Web 前端";

var dmp = new diff_match_patch();
var patch_list = dmp.patch_make(old_text, new_text);
patch_text = dmp.patch_toText(patch_list);

console.log(decodeURI(patch_text))

出力は、

@@ -1,6 +1,9 @@
 百度 Web
+ 前端

最初の行を特徴-+二つのシンボルは意味がない、という句は、変形1前開始位置を示し6の長さ(配列がゼロであるので、第1の縮小内部を計算する)、リア1,9示す開始位置9の長さに変更されます。次の二つのテキストの変更のローカル代表的に、後者はながら、文字列に直接添加され、等しいを表す正面、「百度のWeb」スペースに注意を払う+追加テキストを表し、具体的な詳細は、そのことができる実装のソースコードを理解します。

だから、私たちは前にそれが発生した競合を解決できるように、差分戦略が、一致する文字に基づいておりますことを確認してください?次のようにソースコードをテストする次の:

//相关代码同上
var patches = dmp.patch_fromText(patch_text);
var results = dmp.patch_apply(patches, "Web");

console.log(results[0]); //Web 前端

この出力は、それはそれは前に問題に良い解決策であると言うことです、正しいですが、それは何が起こるかの位置で修飾されている場合は?私はいくつかの実験を行うことを続行します。

var old_text = "百度 Web";
var other_new_text = "百度 Web 后端";
var my_new_text = "百度 Web 前端";
...
//结果为「百度 Web 前端 后端」

===
var old_text = "百度 Web 前端";
var other_new_text = "百度 Web 后端";
var my_new_text = "百度 Web 全端";
...
//结果为「百度 Web 后端」

===
var old_text = "百度 Web";
var other_new_text = "Web 前端";
var my_new_text = "百度 FE";
//结果为「FE 前」 

最初の例では、異なる文字の後ろに追加され、それは2つの加算の結果が反映され、第二の例は、同じ場所で別の文字に変更され、それを有効にするには他の誰かの変化の結果であるが、エラーの最後の例、「終わり」という言葉を失う、これは大丈夫に見えますが、コンテンツが豊富であれば、テキストは、次のような問題があるでしょう<b>以下は>受け入れられません。

全体的にマイヤー低コストのアルゴリズムはとてもいくつかのオンラインエディタのような共同編集機能、実装することを選択して、ほとんどの問題を解決することができCodeBox、そのクライアントコードこの中に、サーバー側のコードをそれに

マイヤーが、文字を失うことになるいくつかのケースでは、より良い方法はありますか?答えは、それが次に説明操作変換技術である、イエスです。

操作変換

操作変換(以下、略しOT)プログラムは、Googleドキュメントで使用される技術であり、その証明され、勉強の価値があります。

私はいつものプレゼンテーションの記事はのような、非常に長く書かれている非常にそれが関連しているためOTは、非常に複雑になり始めて感じたこのとウィキペディアの導入が、その原則の発見後だけ読んだ後に複雑ではありません、私はここになります簡単に説明。

まず、操作は、次の3種類(運用)に変換したテキストの内容を変更することができます。

  • (n)を保持します。それはそのままn文字を言うことである、n文字を開催
  • (STR)を挿入:strの挿入文字
  • (文字列)を削除:strの文字を削除

例えば、ユーザA「百度のWeb」から「Webフロントエンド」は、以下の3つの動作に対応して生成されると仮定する。

delete('百度 '),  //删掉「百度 」
retain(3),       //跳过 3 个字符(也就是「Web」)
insert(' 前端')   //插入「 前端」

これらの抽出操作は、レーベンシュタイン距離(編集距離)アルゴリズムによって実現することができます。それは問題で競合を解決する方法?例えば以下のように、その後、「百度FE」にユーザB「百度のWeb」場合、プロシージャによって生成されるBは次のようになります。

retain(3),       //跳过 3 个字符(也就是「百度 」)
delete('Web'),
insert('FE')

Bは、第2の動作を行うので、我々最初のアプリケーションの動作Aの場合に失敗します、文字列は、「Webフロントエンド」となり、その後、Bの操作を適用しdelete('Web')ていないときに「ウェブ」そして第四から、文字は、「フロントエンド。」となっています開始します

そこで我々は、次の操作にチューニングするなど、新しい文字列に適応するために、動作Bを変換する必要があります。

delete('Web'),
insert('FE'),
retain(3)

変換アルゴリズムは、OTが実際にこれらの操作を変換する必要、人々が同時に動作した場合のアイデアは、編集操作(オペレーション)に最初に変換され、むしろ特定のアルゴリズムよりも、技術のクラスを指し、OTの中核でありますそれは操作変換と呼ばれる理由である、とにだけでなく、変換アルゴリズム分割すべき、具体的なアクション(変換)は、このような非テキスト編集クラスなどの共同編集アプリケーションの様々なサポートするので、OT柔軟にカスタマイズ可能です。

マイヤーアルゴリズムは、我々はOTを解決することができ、ここで私は、オープンソースのライブラリを使用かどうかを確認するために、その一例失った文字につながる戻る前のチェンジセットを、以下の例では、その実現の合併に基づいています。

var Changeset = require('changesets').Changeset;

var text = "百度 Web"
  , textA = "Web 前端"
  , textB = "百度 FE";

var csA = Changeset.fromDiff(text, textA);
var csB = Changeset.fromDiff(text, textB);

var csB_new = csB.transformAgainst(csA); //这里这就是操作转换

var textA_new = csA.apply(text);
console.log(csB_new.apply(textA_new)); //结果是「 前端FE」

結果が正しくない、右は、「フロントエンドFE」に見なければならないcsB_new内容と、それが実際に次のように変換したが見つかりました:

delete(3),   //注意 changesets 在这里的参数不是字符串而是数字,它会直接删掉 3 个字符,不够内容是什么
retain(3),
insert('FE')

完璧ではないものの、これが問題OT技術そのものではなく、問題が実装変換アルゴリズムをチェンジすることを注意しますが、以前のアルゴリズムマイヤーに比べて、少なくともModiu文字で、その後、私はいくつかのテストを行なったし、OTを見つけました技術的な正解率マイヤーよりも高いので、共同編集技術に最適です。

分散操作変換

あなたはハードリアルタイムの共同編集がいないよう、上記の記事を読んだと思うなら、私たちは、分散の問題を考慮していない前に、学界におけるOTの技術は20年以上研究してきたので、あなたは、間違っている、と誰もまとめていません最良の方法は、でGoogle Waveの元エンジニアShareJSは、最初のページに書いています:

残念ながら、実装OTは吸います。主に学術論文の中に閉じ込め異なるトレードオフを持つ百万アルゴリズムが、あります。アルゴリズムは本当に難しいと時刻を正しく実装するのにかかるものです。私は元Google Waveのエンジニアです。Waveは書くために2年かかったと我々は今日それを書き直した場合、それは二度目を書くためにほとんどの時間がかかるでしょう。

だから、実際にそれを行うことは非常に困難であり、最も厄介な問題の分散によって引き起こされるがあり、次は私が問題と解決策を考えることができる3つを紹介します。

1.順序の問題

第一の問題は、OTのアルゴリズムが異なる結果につながる異なる順序の順序に依存しているので、私たちは下の画像で示されている、一次の問題です:

2833665-cb2a6668d8939624.png
オーダー-問題

仮定は、Client Aおそらくネットワークの遅延の2番目の要求につながるが、最初の、そして最終的にはサーバのバージョンとにつながり、二つのリビジョンを行う際に2つの非同期リクエストをしたClient Aサーバに他のクライアントにするとき同じ要求、一貫性のない表示されます示すように、間違ったシーケンスの問題、Client Bまた問題。

この問題を解決するには、我々はそのような要求の終了後に発行元の下で要求として、その要求の順序を保証するために、クライアントとサーバー側ですべてのキューを追加することができ、簡単です。

アトミック操作の2.ストレージ

あなたが要求を処理すると同時に、複数のサーバまたは複数のスレッド/プロセスを持っている場合は、読み書きはアトミックデータベース操作ではありませんので、次の例のような、カバレッジの問題が発生します。

2833665-2fe33db13a353e13.png
データ・アトミック

Web Server Aそして、Web Server Bデータベースへの同時アクセスは、結果としてWeb Server Aカバーの修正。

幸いなことに、この問題は、次の3つのソリューションがありますが、まだ比較的一般的です。

  • 保证操作只在一个线程中执行,比如某个文档的更新只在某个固定的机器,使用 Node 这样的单线程模型提供服务,这样就不可能并行修改了
  • 如果数据库支持事务(transaction),可以通过事务来解决
  • 如果数据库不支持事务,就只能用分布式锁了,如 ZooKeeper

从实现角度来看,第一和第二种方法都比较简单,而第三种方法会带来很多问题,比如可能导致文档被锁死,假如上锁后由于种种原因没有执行解锁操作,这个文档就会永远被锁住,所以还得加上超时限制等策略。

然而在解决了原子操作后,我们将发现一个新的问题,那就是版本管理问题。

3. 版本管理问题

在前面的例子中,两段新文本的修改都是基于同一个旧版本的,如果旧版本不一样,就有可能出错,具体可以通过下面这张图来解释:

2833665-0a33d59389b2ce62.png
version-problem

在这个例子中,Web Server A 接收到操作命令是将「a」文本改成「aa」,Web Server B 接收到操作命令是将「a」文本改成「ab」,这里我们加上了锁机制来避免同时读写数据,Web Server A 首先得到了锁,然后修改并更新数据,而 Web Server B 需要先等待数据解锁,等 Web Server B 拿到数据后它已经从「a」变成了「aa」,如果还按照 retain(1), insert('b') 进行修改,数据将变成「ab」,而不是正确的「aab」,引起这个问题的原因就是旧版本不一致,Web Server B 需要根据 Web Server A 的操作进行操作转换,变成 retain(2), insert('b'),然后才能对数据进行修改。

因此想要解决这个问题,就必须引入版本,每次修改后都需要存储下新版本,有了版本我们就能使用 diff 功能来计算不同版本的差异,得到其它人修改的内容,然后通过 OT 合并算法合并两个操作,如下所示:

2833665-fb4cc990eac978c9.png
version-problem

Web Server A 操作前数据版本是 v=1,操作后变成了 v=2,等到 Web Server B 处理的时候,它通过版本比较发现不一致,所以就首先通过编辑距离算法算出 Web Server A 所做的操作,然后用这个操作来对自己的操作进行转换,得到正确的新操作,从而避免了覆盖问题。

如果保存所有版本会导致数据量大大增加,所以还需要再优化,比如每个服务器保存一个数据副本,但这里就不再展开了,可以看要支持分布式 还是挺麻烦的,不过目前出现了一些前后端整合的方案,如 ShareJSOpenCoweb Framework,可以参考。

另外之前提到的 Myer’s diff 算法也有分布式解决方案,具体细节可以参考这篇文档

初步结论

  • 如果你只是一个内部小项目,实时性要求不高,但对准确性要求比较高
    • 推荐用 merge 或 diff3 工具,出现同一行冲突时由用户来解决,这样能避免自动合并有可能出错的问题
  • 如果想具备一定的实时性,流量不大,不想实现太复杂,且对少量的冲突可以忍受
    • 推荐用 Myer’s diff,后端只开一个 Node 进程
  • 如果想具备实时性,且有多台后端服务同时处理
    • 可以用 Operational Transformation 或 Myer’s diff,但需要注意分布式带来的问题
  • 如果需要很精细的控制,如支持富文本编辑等非单纯文本格式
    • 只能使用 Operational Transformation,但要自己实现操作合并算法,比如 XML 可以参考这篇文章

后续

除了文本合并,真正要做在线编辑还有很多细节处理,感兴趣的同学可以继续研究:

  • サポート選挙は、他の人はもちろん、これはまた、合併の問題であり、テキストセグメントが選ば見ます
  • より正確な位置に移動して、テキスト・ポインタを変更するには
  • サポート元に戻します

ます。https://www.jianshu.com/p/a3b350063cb5で再現

おすすめ

転載: blog.csdn.net/weixin_34000916/article/details/91201522