面接官は私に尋ねました。「分散トランザクションとは何ですか?」

面接官は私に尋ねました。「分散トランザクションとは何ですか?」

[[403411]]

取引

トランザクションは、実際には誰にとっても、特にプログラマーにとっては馴染み深いものです。トランザクションについて聞いたことがなくても問題ありません。なぜなら、あなたは賢くて知的な私に会ったからです。トランザクションは実際には複数の混合操作を処理するために使用され、複数のビジネス シナリオが含まれます。

重要な点は、トランザクションアプリケーションのシナリオは、複数のトランザクションを同時に完了する必要がある、または同時に完了できないシナリオを解決することであり、つまり「共に生きる、共に死ぬ」の真の意味を実現することです。

厳密に言えば、トランザクションには実際には 4 つの特性があります。原子性、一貫性、独立性、永続性です。これは誰もが話題にする ACID です。

  • アトミック性とは、トランザクション内のすべての操作が実行される、または実行されない、という意味です。
  • 一貫性は、整合性制約を満たすデータ、つまりデータの中間状態が存在しないものとして理解できます。たとえば、あなたの口座に 400 元があり、私の口座に 100 元があり、あなたが私に 200 元を送金した場合、あなたの口座のお金は 200 元になり、私の口座のお金は 300 元になります。私の口座のお金は追加されるが、あなたの口座のお金は減額されないという中間状態は発生しません。
  • 分離とは、複数のトランザクションが同時に実行されたときに相互に干渉しないことを意味します。つまり、トランザクション内のデータは他のトランザクションから分離されます。
  • 耐久性とは、トランザクションが完了した後、データが永久に保存され、他の操作や障害がトランザクションの結果に影響を与えないことを意味します。

厳密に言えば、トランザクションには実際には 4 つの特性があります。原子性、一貫性、独立性、永続性です。これは誰もが話題にする ACID です。

実際のところ、私たちはこの取引に非常に精通しているはずです。トランザクションの目的は、データベース レベルの更新操作をすべて成功させるか、すべて失敗させることであることは誰もが知っています。

Redis を学習したかどうかはわかりません。もしそうなら、Redis トランザクションはすべての操作が実行される、あるいは実行されないことを保証できないため、疑問が生じるかもしれませんが、それはトランザクションとも呼ばれます。実際、Redis は公式 Web サイトでこれを明確にしています。公式ウェブサイトでは、トランザクション内のコマンドが失敗しても、後続のコマンドは引き続き処理され、Redis はコマンドの実行を停止しないため、ロールバックされないことが説明されています。

Redisがロールバックをサポートしない理由を説明

彼らが出した答えは、コマンドが間違っていた場合、それは構文エラー、つまり個人的なプログラミングエラーであり、この状況は実稼働環境に現れるのではなく検出されるはずだというものでした。したがって、Redis は高速化のためにロールバック操作をサポートしていません。

理にかなっているようだが、何かが間違っている

さて、これでトランザクションが何であるかがわかったので、分散トランザクションについて見てみましょう。

分散トランザクション

先ほど述べたトランザクションはすべて単一のプログラム内にあります。これは単一のマシンでは問題ではなく、通常のトランザクション操作を通じて解決できます。システムが徐々に大きくなり、強力になるにつれて、同時実行性とシステムもそれに応じて増加します。複数のシステム間で連携してトランザクションを完了するとなると、1 つのシステムのデータベースを介して直接完了することはできないため、実行がより困難になります。

注文システム、控除システム、ポイントシステムがあるとします。これらは 3 つのシステムに属しており、つまり、異なるデータベースにありますが、3 つのシステム内のサービスがすべて成功しているか、すべて失敗しているかを確認する必要があります。実際、複数のデータベースと複数のシステム間で設計されたこのようなトランザクション操作は、分散トランザクションとも呼ばれます。

分散トランザクションは実際には単純です。実際、それらは複数のローカル トランザクションで構成されています。分散トランザクションの場合、ACID を満たすことはほぼ不可能です。実際、スタンドアロンのトランザクションでは、ほとんどの場合、ACID を完全に満たすことはできません。そうでなければ、4 つの分離レベルはどこから来るのでしょうか?異なるデータベースや異なるシステム間で分散された分散トランザクションは言うまでもありません

分散トランザクションは大きく分けて6つの種類に分けられますが、実はこの6つの種類は3つの考え方で分類することができます。一緒に見てみましょう。

2PC と 3PC は強力な一貫性トランザクションですが、データの不整合やブロッキングなどのリスクが依然として存在し、データベース レベルでのみ適用できます。 TCC は補償取引のアイデアであり、より広範囲の領域に適用できるはずですが、この補償メカニズムは一般にビジネスへの侵襲性が高く、各操作で対応する 3 つの方法を実装する必要があります。もう一つのアイデアは、最終的な一貫性のあるトランザクションを実現することです。これには、ローカルメッセージ、トランザクションメッセージ、ベストエフォート通知の3つの方法があり、いずれも最終的な一貫性のあるトランザクションを実現するためのものであるため、時間に敏感でないビジネスに適しています。

これら 3 つのカテゴリについて大まかに理解できたので、それぞれを詳しく調べてみましょう。

  • 2PC 2段階提出:準備段階、提出段階

2PC は 2 フェーズ コミットとも呼ばれ、準備フェーズと送信フェーズを指します。

2 フェーズ コミットは強力な一貫性設計です。 2PC は、各参加者のコミットおよびロールバック メカニズムを調整および管理するトランザクション コーディネーターを導入します。具体的なプロセスを見てみましょう。

準備フェーズでは、コーディネーターが各参加者に準備コマンドを送信します。この準備は、実際には環境を整えるためのものであり、提出前の準備作業とも言えます。

すべてのリソースからの応答を同期的に待機した後、送信以外のすべての準備が整います。

コミット フェーズは必ずしもトランザクションのコミットを意味するわけではなく、ロールバック トランザクションの場合もあります。最初のフェーズのすべての準備が成功した場合、2 番目のフェーズのコミットはコミット トランザクションになります。同様に、最初のフェーズのすべての準備が成功しなかった場合、2 番目のフェーズのコミットはロールバック トランザクションになります。最初のフェーズが成功したと仮定すると、コーディネーターはすべての参加者にコミット コマンドを送信し、すべての参加者が成功するまで待機してから、成功したトランザクションを返します。

一部の参加者が最初のフェーズで戻らなかった場合、コーディネーターは、上の図と同様に、すべての参加者にロールバック トランザクションを送信して、すべての参加者にトランザクションをロールバックする要求を送信します。

この時点で、疑問を持ち始めた方もいるかもしれません。最初の段階での障害の処理方法はわかっていますが、2 番目の段階で障害が発生した場合はどうなりますか?

実際、ここでは 2 つの状況があります。 2 番目のステージはコミット ステージであり、2 番目のステージはロールバック操作です。これら 2 つの状況の処理方法は実際には同じであり、どちらも再試行が成功するまで再試行を続けます。コミットの場合、ビジネスシナリオに応じて、一定回数の再試行を実行した後にロールバックを試みることができます。ただし、ロールバック操作の場合、必ずしも正常な操作を実行できるとは限りません。

したがって、第 2 段階でロールバック操作が失敗し、失敗の数が一定数に達した場合は、手動で介入するのが最善の方法です。

提出プロセスが大まかに分析されました。詳細を見てみましょう。 2PC は同期ブロッキング プロトコルと見なすことができます。同期ブロッキングは、第 2 段階の操作を実行する前に、第 1 段階ですべての参加者が応答するのを待機します。 Java の基礎に精通している方であれば、Java 並行処理パッケージのツール クラス CountDownLatch や、同様の機能を持つ CyclicBarrier をすぐに思い出せるでしょうか。忘れてしまったらすぐに思い出してください。

実際、2PC には同期ブロッキングのためのタイムアウト メカニズムがあります。コーディネーターが参加者の応答を待っている間にタイムアウトになった場合、デフォルトで失敗になります。その後、コーディネーターはトランザクションが失敗するまで、すべての参加者にロールバック コマンドを直接発行します。

上記はすべて参加者の視点から検討したものです。コーディネーターに問題があったらどうなりますか?

コーディネーターが単一のポイントである場合、障害発生後に何らかのシステム問題が発生する可能性があります。プロセスの観点から分析してみましょう。

準備フェーズのコマンドが発行されず、コーディネータが失敗し、トランザクションが開始されていませんが、これは大きな問題ではありません。

準備フェーズ コマンドが発行され、コーディネータが失敗し、トランザクションが開始されます。参加者が成功するか失敗するかに関係なく、参加者は次の指示を待つことができないため、つまりトランザクションがスタックするため、最終的な状況は非常に悪くなります。トランザクションの実行が失敗するだけでなく、一部の共通リソースがロックされ、他のシステムがブロックされることもあります。準備フェーズのコマンドが発行され、すべてが成功し、第 2 フェーズの実行コミット フェーズのコマンドが発行されます。パーティションやネットワークの輻輳により、一部の参加者がコミット コマンドを受信できない可能性があるため、この状況も許容できません。理想的には、すべての参加者が一度にコミット コマンドを受信して​​も、参加者がコミットに失敗する可能性があるため、再試行が必要になります。この時点でコーディネーターは死亡しており、これも受け入れられません。

準備フェーズのコマンドが発行され、その一部が失敗すると、第 2 フェーズのロールバック コマンドが発行されます。実際、上記のような状況と同様に、さまざまな問題も発生します。

コーディネーターが 1 人だけでは不十分なので、複数のコーディネーターを用意し、選挙メカニズムを通じて新しいコーディネーターを選出しましょう。

すべてが最初の段階にある場合、実際には問題なく、トランザクションはまだ送信されておらず、そのままロールオーバーできます。第 2 段階の場合、すべての参加者が生きていると仮定すると、新しいコーディネーターはすべての参加者と自分の状況をさらに確認して、次に何をすべきかを推測できます。参加者個人が失敗すると恥ずかしいことになります。たとえば、コーディネーターがロールバック コマンドを送信し、最初の参加者がそれを受信して​​実行します。すると、コーディネーターと最初の参加者の両方が失敗します。この時点では、他の参加者はリクエストを受信しません。その後、新しいコーディネーターが来て他の参加者に質問し、全員が「OK」と答えます。しかし、最初の参加者が失敗したことはわかりません。すべて正常であるとしてコミットコマンドを直接送信すると、問題が発生します。これは私たちが望んでいる結果ではありません。

実際、2PC プロトコルでは言及されていませんが、実装中にコーディネーターが送信したリクエストをログ レコードと同様にあらゆる場所で記録できるように柔軟にする必要があります。これにより、新しいコーディネーターが来たときに、この時点でリクエストを送信するかどうかがわからなくなります。

コーディネータがコミット要求またはロールバック要求のどちらを送信すべきかわかっていても、参加者も電話を切ると役に立ちません。コーディネータは、参加者が電話を切る前にトランザクションをコミットしたかどうかを知ることができないためです。実際、ここで最も信頼できる方法は、各ステップを適宜ログに記録することです。重要なステップをログ レコードに強力にバインドするのが最適です。そうしないと、操作が成功しても、ログ レコードが失敗すると非常に悪い結果になります。つまり、さまざまな極端な状況を考慮し、あらゆる詳細を考慮に入れるように最善を尽くす必要があります。

2PC は、同期的にブロックされるため、強力な一貫性を確保しようとする分散トランザクションです。同期ブロックとは、場合によってはリソースがロックされ、1 つのポイントで障害が発生すると、リソースがロックされることを意味します。

以下のコードは「分散システム: 原則とパラダイム」から抜粋したものです。

  1. コーディネーター:
  2.  
  3.  
  4.  
  5. START_2PC書き込む ローカルログ; //トランザクションを開始
  6. VOTE_REQUESTマルチキャストする 参加者全員//参加者に投票を通知するブロードキャスト
  7. 一方、  すべての投票が集まりました {
  8. 待つ 入ってくる投票。
  9. if timeout { //コーディネータのタイムアウト
  10. GLOBAL_ABORT書き込む ローカルログ; //ログを書き込む
  11. GLOBAL_ABORTマルチキャストする 参加者全員//トランザクションの中断を通知する
  12. 出口;
  13. }
  14. 記録投票;
  15. } //参加者全員がOKの場合
  16. 参加者全員がVOTE_COMMITを送信しコーディネーターがCOMMITに投票した場合{
  17. GLOBAL_COMMIT書き込む ローカルログ;
  18. GLOBAL_COMMITマルチキャストする 参加者全員
  19. }それ以外{
  20. GLOBAL_ABORT書き込む ローカルログ;
  21. GLOBAL_ABORTマルチキャストする 参加者全員
  22. }
  23.  
  24. 参加者:
  25.  
  26. INIT書き込む ローカルログ; //ログを書き込む
  27. コーディネーターからのVOTE_REQUEST待ちます。
  28. if timeout { //タイムアウトを待つ
  29. VOTE_ABORT書き込む ローカルログ;
  30. 出口;
  31. }
  32. 参加者がCOMMITに投票した場合{
  33. VOTE_COMMIT書き込む ローカルログ; //自分の決定を記録する
  34. VOTE_COMMIT をコーディネーター送信します。コーディネーターからの決定待ちます
  35. タイムアウトの場合{
  36. DECISION_REQUEST を他の参加者マルチキャストします。 //タイムアウト通知
  37. DECISION を受信するまで待機します。 /* ブロックされたまま*/
  38. 決定書く ローカルログ;
  39. }
  40. DECISION == GLOBAL_COMMITの場合
  41. GLOBAL_COMMIT書き込む ローカルログ;
  42. そうでない場合、DECISION == GLOBAL_ABORT
  43. GLOBAL_ABORT書き込む ローカルログ;
  44. }それ以外{
  45. VOTE_ABORT書き込む ローカルログ;
  46. VOTE_ABORTをコーディネーター送信します。
  47. }
  48. 各参加者は、他の参加者からの DECISION_REQUEST リクエストを処理するためのスレッドを維持します。
  49.  
  50. 真の場合{
  51. 着信DECISION_REQUESTを受信するまで待機します
  52. ローカルログから最後に記録された STATEを読み取ります
  53. STATE == GLOBAL_COMMITの場合
  54. 要求元の参加者GLOBAL_COMMIT を送信します
  55. そうでない場合、STATE == INITまたはSTATE == GLOBAL_ABORT の場合。
  56. 要求元の参加者GLOBAL_ABORT を送信します
  57. それ以外 
  58. スキップ; /* 参加者はブロックされたままです */
  59. }
  • 3PC 3段階提出:準備段階、提出前段階、提出段階

3PC は実際には 2PC のアップグレード版です。 2PC と比較して、参加者はタイムアウト メカニズムも導入し、新しいステージを追加して、参加者がこのステージを使用してそれぞれの状態を統一できるようにしました。

3PC は、準備段階、提出前段階、提出段階の 3 つの段階に分かれています。 2PC の提出フェーズは、事前提出フェーズと提出フェーズの 2 つのフェーズに分かれているようです。しかし、ここでの準備段階は、実際には参加者に自分自身の状況、つまり現在の状況がどうなっているのか、負荷が過負荷になっていないか、新しいタスクを受け入れることができるかどうかを尋ねることです。

事前コミットフェーズは、実際には 2PC の準備フェーズに似ており、トランザクションの送信以外の必要な作業がすべて完了していることを意味します。これは、前の準備作業ですが、3PC では事前コミットフェーズと呼ばれます。


3PC では、準備段階でトランザクションが直接実行されるのではなく、参加者にトランザクションを実行できるかどうかが尋ねられます。したがって、リソースは直接ロックされません。事前コミットフェーズの導入は、状態を統一する役割を果たします。前処理段階では、すべての参加者が回答しました。

実際、これによって余分な段階も導入されるため、パフォーマンスは低下します。また、ほとんどの場合、リソースは問題なく、つまり使用可能であるため、使用可能であることがわかっているたびに 1 回問い合わせる必要があります。

もちろん、どのステージの参加者も失敗を返した場合、トランザクションは失敗したと宣言されます。 2PCについても同様です。もちろん、最終提出段階は2PCと同じです。送信要求が行われる限り、継続的に再試行することしかできません。

上で、2PC は同期的にブロックされると述べました。コミット要求が送信される前にコーディネータがハングアップすると、非常に困ったことになります。すべての参加者がリソースをロックしており、待機状態になっています。そのため、参加者が直接待機する必要がないように、タイムアウト メカニズムが導入されています。コミット コマンドの待機タイムアウトに達した場合、この段階ではトランザクションが送信される可能性が非常に高いため、参加者はトランザクションをコミットします。事前コミットの待機タイムアウトに達した場合、次のステップには影響はありません。

実はここには問題があります。タイムアウト メカニズムにより、データの不整合が発生します。つまり、コミット コマンドがタイムアウトするのを待機している場合、参加者は自動的にトランザクションをコミットしますが、ロールバック メカニズムも実行される可能性があるため、データの不整合が発生します。

3PC の導入は、2PC コーディネータと一部の参加者が送信フェーズで失敗した場合に、新しく選出されたコーディネータが送信するかロールバックするかがわからないという問題を解決するために行われます。新しいコーディネーターが来ると、参加者が事前提出段階または提出段階にあることがわかります。これは、すべての参加者が確認されていることを意味するため、この時点で提出コマンドが実行されます。

3PC は、参加者の状態を真に統一するために、つまり全員が同期するためのフェーズを残すために、事前コミット フェーズを導入します。ただし、これによってコーディネータに何をすべきかがわかるだけであり、これが正しいという保証はありません。これは実際には上記の 2PC の分析と一致しており、失敗した参加者がトランザクションを実行したかどうかを判断することは不可能です。したがって、3PC は事前コミット フェーズを通じて障害の複雑さを軽減できますが、データが本当に一貫していることや、障害が発生した参加者が回復したことを保証することはできません。

簡単に言うと、3PC は 2PC と比較して参加者のタイムアウト メカニズムにいくつかの改善を加え、事前コミット フェーズを追加しました。これにより、障害回復後のコーディネータの決定の複雑さを軽減できますが、全体的な対話プロセスが長くなり、パフォーマンスが低下し、データの不整合が発生します。

  • TCC: 試行-確認-キャンセル

TCC はビジネス レベルでの分散トランザクションです。分散トランザクションには、データベース レベルでの操作だけでなく、ビジネス レベルでの操作も含まれます。ここで TCC が役に立ちます。

TCC は、Try、Confirm、Cancel の 3 つのステップを指します。 Try は予約を意味し、これはリソースの予約とロックを意味します。確認は確認操作を指します。これは実際には実際の実行であり、対応するビジネス送信操作を実行するためにリソースを消費します。キャンセルはキャンセル操作を指し、予約段階のアクションを破棄する、つまりロールバック操作として理解できます。

概念的には、2PC や 3PC に似ています。これらはすべて最初に暫定的に実行され、リソースは暫定的にロックされます。すべての参加者が正常であれば、実際の操作を実行、コミット、またはロールバックできます。

たとえば、トランザクションで 3 つの操作 A、B、C を実行する必要がある場合、最初に 3 つの操作に対して予約アクションが実行されます。すべての予約が成功した場合、確認と送信の操作が実行されます。少なくとも 1 つの予約が失敗した場合は、キャンセル アクションが実行されます。

TCC モデルにはトランザクション マネージャの役割もあり、これは TCC 関連のグローバル トランザクション操作のステータスを記録し、トランザクションのコミットまたはロールバックを準備するために使用されます。実際、これは比較的理解しやすいのですが、難しいのはビジネスの定義にあります。

なんと言えばいいでしょうか? TCC は侵入性が高く、ビジネスと密接に結合されているため、対応する特定のビジネス シナリオとビジネス ロジックに従って応答操作を設定する必要があります。実際、注意する必要があるのは、キャンセル操作と確認操作の実行には再試行が必要であり、つまり操作のべき等性を保証する必要があるということです。

相対的に言えば、TCCは適用範囲が広いはずですが、ビジネスと結びついて多くの開発が必要になるという欠点があります。これは、すべてがビジネスに実装されるためです。これは、各シナリオに3つの実装方法が必要であることと同等です。つまり、ビジネスに組み込まれているため、TCCはビジネスシステムとデータベース間でトランザクションを実装できます。

  • ローカルメッセージテーブル

ローカル メッセージ テーブルは、各システムのローカル トランザクションを使用して分散トランザクションを実装します。これは実は非常に単純な原則です。実際には、ローカル メッセージを格納するためのテーブルがあり、通常はデータベース内に配置されます。そして、業務を実行する際には、業務の実際の実行操作と、この操作に対応するメッセージをメッセージ テーブルに配置する必要があります。この操作は同じトランザクションに保存されます。操作が成功する限り、メッセージがローカル メッセージ テーブルにも正常に配置されることが保証される必要があります。

次の操作を呼び出すときに、次の操作が正常に呼び出された場合は、メッセージのステータスを直接成功に変更できます。通話が失敗しても問題ありません。ローカル メッセージ テーブルを読み取り、失敗したメッセージをフィルター処理して対応するサービスを呼び出すスケジュールされたタスクを作成できます。サービスが正常に更新されると、メッセージのステータスを変更できます。

実際、ここでも再試行メカニズムが必要です。再試行するには、対応するサービス メソッドがべき等であることを確認する必要があります。一般的に、再試行回数には上限があります。最大数を超えた場合は、手動で介入することができます。

ローカル メッセージ テーブルはビジネスの最終的な一貫性を実装し、一時的なデータの不整合を許容できる必要があります。

  • メッセージトランザクション

実際、最も一般的なメッセージ トランザクションは RocketMQ に実装されており、多くのアプリケーション シナリオがあります。

RocketMQ のメカニズムは、まずセミメッセージであるトランザクション メッセージをブローカーに送信することです。セミメッセージとは、メッセージが消費者には見えないことを意味します。メッセージが正常に送信された後、ローカル トランザクションは引き続き実行されます。

2 番目のステップは、ローカル トランザクションの実行結果に応じて、コミット コマンドとロールバック コマンドをブローカーに送信することです。送信されない場合、RocketMQ 送信者は、対応するトランザクションの結果が成功したかロールバックされたかを確認するためにトランザクション ステータスをチェックするインターフェイスを提供します。

実際、これはタイムアウトのメカニズムです。一定期間内に操作要求が受信されない場合、ブローカーは対応する結果を使用して、トランザクションが正常に実行されたかどうか、つまりコミットかロールバックかを判断します。

コミットの場合、ブローカーはこのメッセージをサブスクライバーに送信し、対応する操作を実行します。完了後、サブスクライバーはこのメッセージを消費できます。ロールバックの場合、サブスクライバーはこのメッセージを受信しません。これは、トランザクションが実行されていないことと同じです。

  • ベストエフォート通知

実際、私は個人的にはベストエフォート通知は一種の考えだと思っています。たとえば、上記のローカル メッセージ テーブルとトランザクション メッセージもベスト エフォート通知タイプに属します。

ローカル メッセージ テーブルには、未完了のタスクのメッセージを定期的にチェックし、対応するサービスを呼び出して複数回再試行するバックグラウンド タスクがあります。複数の障害が発生した場合は、手動での介入が必要になりますが、これも最善の努力となります。

トランザクションメッセージは同様です。セミメッセージがコミットされると、コンシューマーに送信されます。コンシューマーが消費しない、または消費できない場合は、再試行を続けます。再試行回数が一定数に達すると、メッセージはプライベート メッセージ キューに戻されます。これもベスト エフォート通知です。

これは、トランザクションの最終的な一貫性を実現するために最善を尽くすという一種の考え方であるべきであり、時間に敏感ではないビジネス シナリオに適しています。

<<:  [Sticky JVM] JVM チューニングとは何ですか?

>>:  クラウド コンピューティング 3.0 のパターンとブレークスルー

推薦する

ECIS 2020 | 2020 エッジ コンピューティング インダストリー サミットが明日開幕します。重要なゲストや業界フォーラムをちょっと覗いてみましょう!

明日2020年エッジコンピューティング業界サミットが間もなく始まります8件以上の主要リリース、60件...

格安旅行でお金を稼ぐ方法: オンライン旅行コミュニティの現状と将来について語る

文/李翔昊予想外のビジネスベンチャー趣味を仕事にし、大きな発展性のある会社を創ることができるのは大き...

JVM メモリ管理 - GC アルゴリズムの詳細な説明

導入究極のアルゴリズムとは何ですか?実際、これは現在の JVM で使用されているアルゴリズムであり、...

愛がグレードダウンする時代に、ブランドは「520」マーケティングをどのように活用できるのでしょうか?

はじめに:「520の甘いマーケティングは限界効果を引き起こします。高度に均質化された「520」マーケ...

友情リンク交換: 燃えるような目

多くの新しいウェブマスターは、ウェブサイトを引き継ぐ際に、数日間友好的なリンクを交換するように手配さ...

Baidu Statisticsを正しく使う方法

Baidu Statistics は、Baidu がリリースした無料のプロフェッショナルなウェブサイ...

新しいブランドや製品のコンテンツマーケティングをどのように実施すればよいでしょうか?

ショートビデオ、セルフメディア、インフルエンサーのためのワンストップサービスコンテンツ マーケティン...

Django 1.6 のマークダウン ツール: django-markdown-deux

背景Markdown は文章を書くための素晴らしいツールだと言わざるを得ません。Django を使用...

Cloudcone: 3 周年、強力で安価な VPS 2 つ、3.99 USD/4G メモリ/2 コア/100g SSD/3T トラフィック

Cloudcone は、親会社が長い歴史を持つものの、新しいブランドとしては設立されてまだ 3 年し...

魔法のSEOの背後には、戻れない深淵が潜んでいるかもしれない

記事にあらゆる種類のキーワードを追加することに慣れたのはいつからかわかりません。まるで強迫性障害があ...

あなたの B2C 電子商取引ウェブサイトが競合他社の群衆から目立つことができないのはなぜでしょうか?

2018年最もホットなプロジェクト:テレマーケティングロボットがあなたの参加を待っていますB2C 電...

小説や文学のウェブサイトを運営することにはまだ未来があるのでしょうか?

検索エンジン最適化により、より多くの業界がインターネットに参入できるようになりました。検索エンジンの...

dabuu-1.83ドル/月/cpanelホスティング/SSD/無料専用IP/無料ドメイン名1つ

dabuu.com は 2009 年に設立されたホスティング プロバイダーです。多くの事業を展開して...

APPチャンネルプロモーション:3つの主要チャンネルと体験共有!

業界におけるマシュー効果が深まるにつれ、APPトラフィックを獲得することがますます困難になっています...