最も効率的な分散トランザクション処理ソリューションはどれですか?それはきっと…

最も効率的な分散トランザクション処理ソリューションはどれですか?それはきっと…

[[421456]]

前回の記事では、Song Ge が Seata の 4 つの分散トランザクション処理ソリューションを紹介しました。これまでの記事を読んだ後、皆さんは Seata の分散トランザクションについて十分に理解できたと思います。前の記事を読んでいない方は、まずこちらをお読みください:

  • 5分で分散トランザクションを体験してみましょう!とても簡単です!
  • たくさんのブログを読んだのですが、まだ TCC が理解できません。このケースを見てみませんか?
  • XA ビジネスは非常に複雑です、若者よ、残念ながらあなたには対応できないでしょう!
  • Saga トランザクションは「分離」を保証しますか?

しかし、読んでみると、Seata の分散トランザクションの処理は、コードはシンプルであるものの、ネットワーク上で費やされる内部時間が長すぎると感じた友人が多くいます。同時実行性の高いシナリオでは、これは適切な解決策ではないようです。

どの分散トランザクション処理ソリューションが効率的かといえば、メッセージ ミドルウェアは不可欠です。メッセージ ミドルウェアに基づく 2 フェーズ コミット ソリューションは、通常、同時実行性の高いシナリオで使用されます。この方法では、パフォーマンスが大幅に向上する代わりに、強力なデータ一貫性が犠牲になります。ただし、この方法を実装するにはコストと複雑さが比較的高く、その使用は実際のビジネス状況に依存します。

本日は、Song Ge が簡単なケースを通じて、メッセージ ミドルウェアを介して分散トランザクションを処理する方法についてお話しします。

1. 思考分析

まず全体的なアイデアについてお話ししましょう。

メッセージ駆動型マイクロサービスという用語がありますが、皆さんも聞いたことがあると思います。それをどう理解すればいいのでしょうか?

マイクロサービス システムでは、サービスは OpenFeign などの HTTP または Dubbo などの RPC を使用して相互に呼び出すことができます。これらのソリューションに加えて、典型的なレスポンシブ システム設計ソリューションであるメッセージ駆動型も使用できます。

メッセージ駆動型マイクロサービスでは、サービスが互いに直接呼び出すことはなくなりました。サービスが通信する必要がある場合、通信コンテンツはメッセージ ミドルウェアに送信され、他のサービスはメッセージ ミドルウェア内のメッセージ キューをリッスンして対応するビジネス ロジック呼び出しを完了します。これは難しいプロセスではありません。どうやってそれをやるのかを引き続き見ていきましょう。

2. ビジネス分析

長い間苦労した後、Song Ge さんはインターネット上で他の誰かが書いた例を見つけました。この問題を実証するには特に適していると思うので、自分でケースを書いたのではなく、他の人のコードをそのまま使用しました。前回の分散トランザクション Seata の説明と同じように、1 つずつ分析してみましょう。

まず、ユーザーがチケットを購入する場合の次のフローチャートを見てみましょう。

ユーザーがチケットを購入したい場合:

  1. 新しい注文キューにデータを書き込みます。
  2. 注文サービスは、このキュー内のメッセージを消費し、注文の作成を完了し、新しい注文の支払いキューにメッセージを書き込む役割を担います。
  3. ユーザー サービスは、新規注文の支払いキュー内のメッセージを消費し、ユーザー サービスでユーザーのアカウント残高の差し引きを完了し、新規注文の転送チケット キューにメッセージを書き込む役割を担います。
  4. チケット サービスは、新しい注文転送チケット キューを消費し、チケット サービスでチケット転送を完了し、注文完了キューにメッセージを送信する役割を担います。
  5. 最後に、注文サービスは注文完了キューを監視し、完了した注文を処理する役割を担います。

これは典型的なメッセージ駆動型マイクロサービスであり、典型的なレスポンシブ システムでもあります。このシステムには、次の 3 つのサービスがあります。

  • 注文サービス
  • ユーザーサービス
  • チケットサービス

これら 3 つのサービス間で直接通話することはできません。すべてのメッセージはメッセージ ミドルウェアに直接送信され、他のサービスはメッセージ ミドルウェアから必要なメッセージを取得して処理します。

具体的には、私たちの実践では、以下に示すように、チケットが十分にあるかどうかを確認するための追加のプロセスがあります。

注文を作成する際、チケット サービスはまずチケットが十分にあるかどうかを確認します。はいの場合は、注文の作成が続行されます。他のプロセスについては話しません。

また、発券システムでは、各チケットに座席などがあるなど、各チケットが異なるため、チケットはデータベース内のレコードとして設計されることが多いことにも注意してください。

3. 練習

プロセスをわかりやすく説明しましたので、具体的なコードの実践方法を見てみましょう。

3.1 データベースの準備

まず、次の 3 つのデータベースを準備します。

  • javaboy_order: 注文ライブラリ。ユーザーによる注文の作成などの操作はこのデータベースで完了します。
  • javaboy_ticket: チケット ライブラリ。このライブラリにはすべてのチケット情報が保存され、各チケットはレコードとなり、このライブラリに保存されます。
  • javaboy_user: ユーザー アカウントの残高や支払い記録などの情報を保存するユーザー データベース。

各ライブラリにはそれぞれ対応するテーブルがあります。操作の便宜上、これらのテーブルを自分で作成する必要はありません。将来プロジェクトを開始するときに、JPA を使用してそれらを自動的に作成できます。

3.2 プロジェクトの概要

プロジェクト全体を見てみましょう。完全なコードをダウンロードするには、WeChat パブリック アカウントで mq_tran に返信してください。

合計で 5 つのサービスがあります。

  • eureka: 登録センター
  • 注文: 注文サービス
  • サービス: パブリックモジュール
  • チケット:チケットサービス
  • ユーザー: ユーザーサービス

以下で一つずつ説明していきましょう。

3.3 登録センター

すべてがメッセージ主導なので、なぜ登録センターが必要なのかと言う人もいます。

メッセージ駆動型が使用されるのは正しいです。マイクロサービスがメッセージ駆動型になると、各サービスはメッセージ ミドルウェアにメッセージをスローするだけでよく、各サービスはメッセージ ミドルウェアでメッセージを消費するだけでよくなります。現時点では、サービス登録センターに対するニーズはそれほど強くないようです。ただし、この場合、メッセージ ドライバーは主にトランザクションの問題を処理するために使用されます。他の一般的なニーズを処理するために OpenFeign を引き続き使用しているため、ここでも登録センターが必要です。

ここでは、手間を省くために、一般的な Eureka を登録センターとして選択します。この記事は分散トランザクションがメインなので、スペースをあまり取らずにマイクロサービスに関することも簡単に紹介します。 Spring Cloud の使い方に詳しくない方は、公式アカウントのバックグラウンドで vhr に返信して一連のビデオ紹介を受けることができます。

サービス レジストリを作成するときは、独自のサービス レジストリを保護するために Spring Security を追加することを忘れないでください。

ここで少しお話ししたい小さな詳細があります。

Eureka が Spring Security によって保護された後、他のサービス登録は Http Basic を介して認証されるため、次のようにコードで Http Basic 認証を有効にする必要があります (次のコードは古いバージョンでは必要ありませんが、新しいバージョンでは必要です)。

  1. @構成
  2. パブリッククラス SecurityConfig は WebSecurityConfigurerAdapter を拡張します {
  3. @オーバーライド
  4. 保護されたvoid configure(HttpSecurity http)は例外をスローします{
  5. リクエストを承認する()
  6. .anyRequest().authenticated()
  7. そして()
  8. .httpBasic()
  9. および().formLogin()。および().csrf().disable();
  10. }
  11. }

3.4 チケット購入サービス

次にチケット購入サービスについて見てみましょう。

チケットの購入は注文から始まりますので、注文サービスからプロセス全体の分析を始めます。

3.4.1 新規注文処理(注文)

ユーザーがチケット購入リクエストを開始すると、そのリクエストは注文サービスに送信されます。注文サービスは、まず order:new キューにメッセージを送信して、注文の処理を開始します。コードは次のとおりです。

  1. @トランザクション
  2. @PostMapping( "" ) は
  3. パブリックvoidを作成します(@RequestBody OrderDTO dto) {
  4. dto.setUuid(UUID.randomUUID().toString());
  5. rabbitTemplate.convertAndSend( "order:new" , dto);
  6. }

上記で設定した UUID は、処理プロセス中の注文全体の一意の識別子であり、メインラインとみなすこともできます。

order:new キュー内のメッセージは、チケット サービスによって消費されます。チケット サービスは、メッセージを order:new で消費し、チケット ロック操作を実行します (チケットをロックする目的は、2 人の消費者が同時に同じチケットを購入することを防ぐことです)。チケットが正常にロックされると、チケット サービスは order:locked キューにメッセージを送信し、チケットが正常にロックされたことを示します。それ以外の場合は、チケットのロックが失敗したことを示すメッセージが order:fail キューに送信されます。

ここでの OrderDTO オブジェクトは、チケット購入プロセス全体を実行します。

3.4.2 チケットのロック

チケットサービス内でチケットのロック操作が完了します。コードは次のとおりです。

  1. @トランザクション
  2. @RabbitListener(キュー = "order:new" )
  3. パブリックvoid handleTicketLock(OrderDTO メッセージ) {
  4. LOG.info( "チケットロックの新しい注文を取得:{}" , msg);
  5. チケットリポジトリの lockTicket のロックカウントを取得します
  6. ロックカウント == 0 の場合
  7. msg.setStatus( "TICKET_LOCK_FAIL" );
  8. rabbitTemplate.convertAndSend( "order:fail" , msg);
  9. }それ以外{
  10. msg.setStatus( "TICKET_LOCKED" );
  11. rabbitTemplate.convertAndSend( "order:locked" , msg);
  12. }
  13. }

まず lockTicket メソッドを呼び出して、データベース内のチケットをロックします。チケットのロックとは、購入するチケットの lock_user フィールドを customer_id (購入者の ID) に設定することを意味します。

チケットが正常にロックされた場合 (つまり、データベースが正常に変更された場合)、msg のステータスは TICKET_LOCKED に設定され、チケットが正常にロックされたことを示すメッセージが order:locked キューに送信されます。

チケットのロックが失敗した場合 (つまり、データベースの変更が失敗した場合)、msg のステータスは TICKET_LOCK_FAIL に設定され、チケットのロックが失敗したことを示すメッセージが order:fail キューに送信されます。

3.4.2 チケットロック成功(順序)

次に、注文サービスは order:locked キュー内のメッセージを消費します。これは、チケットが正常にロックされた後の次の操作です。

  1. @トランザクション
  2. @RabbitListener(キュー = "order:locked" )
  3. パブリックvoid ハンドル (OrderDTO メッセージ) {
  4. LOG.info( "作成する新しい注文を取得します:{}" , msg);
  5. (orderRepository.findOneByUuid(msg.getUuid()) がnullの場合) {
  6. LOG.info( "メッセージはすでに処理されています:{}" , msg);
  7. }それ以外{
  8. 注文 注文= newOrder(メッセージ);
  9. orderRepository.save(注文);
  10. msg.setId(オーダー.getId());
  11. }
  12. msg.setStatus( "NEW" );
  13. rabbitTemplate.convertAndSend( "order:pay" , msg);
  14. }

チケットが正常にロックされたら、まず注文 UUID に基づいて注文データベースを照会し、注文レコードがあるかどうかを確認します。そうであれば、メッセージが処理されたことを意味し、注文の重複処理を防ぐことができます (これは主にべき等性の問題を解決するためです)。

注文が処理されていない場合は、新しい注文オブジェクトを作成し、データベースに保存します。新しい注文オブジェクトを作成するときは、注文のステータスを NEW に設定します。

最後に、msg のステータスを NEW に設定し、order:pay キューにメッセージを送信して支払いプロセスを開始します。支払いはユーザーサービスによって提供されます。ユーザーサービスは、ユーザーのアカウント残高が十分かどうかを確認します。そうでない場合は、チケットの予約が失敗したことを示すメッセージが order:ticket_error キューに送信されます。残高が十分であれば、通常の支払い操作が実行され、支払いが成功すると、チケットの転送を開始するためのメッセージが order:ticket_move キューに送信されます。

3.4.3 支払い(ユーザー)

チケットが正常にロックされた後、次のステップは支払いであり、支払いサービスはユーザーによって提供されます。

  1. @トランザクション
  2. @RabbitListener(キュー = "order:pay" )
  3. パブリックvoid ハンドル (OrderDTO メッセージ) {
  4. LOG.info( "支払うための新しい注文を取得:{}" , msg);
  5. // まず payInfo をチェックして重複したメッセージがあるかどうかを確認します。
  6. PayInfo 支払い = payInfoRepository.findOneByOrderId(msg.getId());
  7. 支払いがnull場合
  8. LOG.warn( "注文はすでに支払われています。メッセージが重複しています。" );
  9. 戻る;
  10. }
  11. 顧客 customer = customerRepository.getById(msg.getCustomerId());
  12. (customer.getDeposit() < msg.getAmount()) の場合 {
  13. LOG.info( "デポジットが足りません。必要な金額:{}" , msg.getAmount());
  14. msg.setStatus( "NOT_ENOUGH_DEPOSIT" );
  15. rabbitTemplate.convertAndSend( "order:ticket_error" , msg);
  16. 戻る;
  17. }
  18. 支払い = 新しい PayInfo();
  19. 支払います。注文IDを設定します(msg.getId());
  20. 支払います。金額を設定します(msg.getAmount());
  21. 支払い.setStatus( "支払い済み" );
  22. payInfoRepository.save(支払い);
  23. 顧客リポジトリー.charge(msg.getCustomerId(), msg.getAmount());
  24. msg.setStatus( "支払済み" );
  25. rabbitTemplate.convertAndSend( "order:ticket_move" , msg);
  26. }

ここでの実行手順は次のとおりです。

  1. まず、注文IDに基づいて支払い情報を検索し、現在の注文が支払われているかどうかを確認します。サービスが完了したら、そのままご返却ください。このステップは、べき等性の問題にも対処します。
  2. 顧客の ID に基づいて、顧客の口座残高を含む顧客の完全な情報を検索します。
  3. 顧客のアカウント残高がチケット代金を支払うのに十分かどうかを確認します。そうでない場合は、msg のステータスを NOT_ENOUGH_DEPOSIT に設定し、予約が失敗したことを示すメッセージを order:ticket_error キューに送信します。
  4. 顧客のアカウント残高が運賃を支払うのに十分である場合、PayInfo オブジェクトが作成され、関連する支払い情報が設定され、pay_info テーブルに保存されます。
  5. 顧客の口座残高の引き落としを完了するには、charge メソッドを呼び出します。
  6. チケット配信操作を開始するには、order:ticket_move キューにメッセージを送信します。

3.4.4 チケット

  1. @トランザクション
  2. @RabbitListener(キュー = "order:ticket_move" )
  3. パブリックvoid handleTicketMove(OrderDTO メッセージ) {
  4. LOG.info( "チケット移動の新しい順序を取得します:{}" , msg);
  5. チケットリポジトリーチケットを移動します。
  6. 移動回数が0の場合
  7. LOG.info( "チケットはすでに転送されています。" );
  8. }
  9. msg.setStatus( "TICKET_MOVED" );
  10. rabbitTemplate.convertAndSend( "order:finish" , msg);
  11. }

チケット配信操作を完了するには、moveTicket メソッドを呼び出します。つまり、チケット テーブル内のチケットの所有者を customerId に設定します。

チケットが正常に配信されると、配信が完了したことを示すメッセージが order:finish キューに送信されます。

3.4.5 注文完了

  1. @トランザクション
  2. @RabbitListener(キュー = "order:finish" )
  3. パブリックvoid handleFinish(OrderDTO メッセージ) {
  4. LOG.info( "完了した注文を取得:{}" , msg);
  5. 注文 注文= orderRepository.getById(msg.getId());
  6. 注文.setStatus( "FINISH" );
  7. orderRepository.save(注文);
  8. }

ここでの処理は比較的簡単です。注文が完了したら、注文ステータスをFINISHに設定するだけです。

上記紹介が本文です。すべてがうまくいけば、この行に沿ってメッセージが送信され、注文が処理されます。

物事がスムーズに進まなければ、さまざまな予期せぬ問題が発生するでしょう。一つずつ見ていきましょう。

3.4.6 チケットロック失敗(注文)

チケットのロックはチケットサービスで行われます。チケットのロックが失敗した場合、メッセージは order:fail キューに直接送信され、このキュー内のメッセージは注文サービスによって消費されます。

3.4.7 デビット失敗(チケット)

ユーザ内で控除操作が完了します。控除が失敗した場合、メッセージは order:ticket_error キューに送信され、このキュー内のメッセージはチケット サービスによって消費されます。

  1. @トランザクション
  2. @RabbitListener(キュー = "order:ticket_error" )
  3. パブリックvoid handleError(OrderDTO メッセージ) {
  4. LOG.info( "チケットロック解除の順序エラーを取得:{}" , msg);
  5. 整数 チケットリポジトリの unMoveTicketを count で指定します
  6. カウント== 0)の場合{
  7. LOG.info( "チケットはすでにロック解除されています:" , msg);
  8. }
  9. count = ticketRepository.unLockTicket(msg.getCustomerId(), msg.getTicketNum());
  10. カウント== 0)の場合{
  11. LOG.info( "チケットは既に移動されていないか、移動されていません:" , msg);
  12. }
  13. rabbitTemplate.convertAndSend( "order:fail" , msg);
  14. }

控除が失敗した場合は、次の 3 つのことを行います。

  1. チケットの転送を元に戻します。つまり、チケットの所有者フィールドを null に戻します。
  2. チケットのロックを解除するということは、チケットの lock_user フィールドを null にリセットすることを意味します。
  3. 注文失敗メッセージを order:fail キューに送信します。

3.4.8 注文の失敗

注文サービスでは、メッセージが order:fail キューに送信される状況が 3 つあります。

  1. チケットのロックに失敗しました
  2. 引き落とし失敗(顧客口座の残高不足)
  3. 注文タイムアウト
  1. @トランザクション
  2. @RabbitListener(キュー = "order:fail" )
  3. パブリックvoid handleFailed(OrderDTO メッセージ) {
  4. LOG.info( "失敗した注文を取得:{}" , msg);
  5. 注文 注文;
  6. msg.getId() がnull場合
  7. 注文= newOrder(メッセージ);
  8. 注文.setReason( "TICKET_LOCK_FAIL" );
  9. }それ以外{
  10. 注文= orderRepository.getById(msg.getId());
  11. msg.getStatus() が"NOT_ENOUGH_DEPOSIT"等しい場合
  12. 注文.setReason( "NOT_ENOUGH_DEPOSIT" );
  13. }
  14. }
  15. 注文.setStatus( "FAIL" );
  16. orderRepository.save(注文);
  17. }

このメソッドの具体的な処理ロジックは次のとおりです。

  1. まず注文IDがあるかどうかを確認します。注文 ID がない場合は、チケットのロックが失敗したことを意味します。注文の理由属性の値を TICKET_LOCK_FAIL に設定します。
  2. 注文 ID がある場合は、その ID に基づいて注文情報を照会し、注文ステータスが NOT_ENOUGH_DEPOSIT (減額に失敗したことを意味する) であるかどうかを判断します。注文ステータスが NOT_ENOUGH_DEPOSIT の場合は、失敗理由もこれに設定します。
  3. 最後に、注文ステータスを FAIL に設定し、データベース内の注文情報を更新します。

3.4.9 注文タイムアウト(注文)

注文サービスには、次のように、処理に失敗した注文をデータベースから定期的に取得するスケジュールされたタスクもあります。

  1. @スケジュール済み(固定遅延 = 10000L)
  2. パブリックボイドチェック無効注文() {
  3. ZonedDateTime チェック時間 = ZonedDateTime.now().minusMinutes(1L);
  4. リスト< Order > orders = orderRepository.findAllByStatusAndCreatedDateBefore( "NEW" , checkTime);
  5. 注文.stream().forEach(注文-> {
  6. LOG.error( "注文タイムアウト:{}" , order );
  7. OrderDTO dto = 新しい OrderDTO();
  8. dto.setId(オーダー.getId());
  9. dto.setTicketNum(注文.getTicketNum());
  10. dto.setUuid(オーダー.getUuid());
  11. dto.setAmount(注文.getAmount());
  12. dto.setTitle(注文.getTitle());
  13. dto.setCustomerId(注文.getCustomerId());
  14. dto.setStatus( "タイムアウト" );
  15. rabbitTemplate.convertAndSend( "order:ticket_error" , dto);
  16. });
  17. }

ご覧のとおり、ここではデータベースにアクセスして、ステータスが NEW で 1 分前に行われた注文を取得します。前回の分析によると、チケットが正常にロックされると、注文のステータスは NEW に設定され、データベースに保存されます。つまり、チケットがロックされてから 1 分以内にチケットが販売されない場合、注文タイムアウトが設定され、order:ticket_error キューにメッセージが送信されます。このメッセージはチケット サービスによって消費され、チケットの配信とロック操作が最終的に完了します。

これが一般的なコード処理フローです。

前の図をもう一度見てみましょう。

この図をコードと組み合わせると理解しやすいでしょうか?

3.5 テスト

次に簡単なテストをしてみましょう。

次のように、チケット予約の失敗のテストから始めましょう。

ユーザーが持っているお金は 1,000 元だけで、チケットの価格は 10,000 元なので、チケットの購入は必ず失敗します。リクエストが正常に実行された後、注文テーブルをチェックして次のレコードを見つけます。

ご覧のとおり、注文が失敗した理由は口座残高不足です。この時点で、チケット テーブルとユーザー テーブルをチェックし、それらがそのままであることを確認します (必要に応じて、逆補正されています)。

次に、チケット テーブルの lock_user フィールドに次のように値を手動で設定します。

これはチケットがロックされていることを意味します。

次に、チケット購入リクエストを開始します (今回は金額を適切な範囲に設定できますが、今回は支払い段階に達していないため、設定しなくても問題ありません)。

リクエストが正常に送信された後、注文テーブルをチェックして次のレコードを見つけます。

この注文が失敗した理由は、チケットをロックできなかったためであることがわかります。この時点で、チケット テーブルとユーザー テーブルをチェックし、それらがそのままであることを確認します (必要に応じて、逆補正されています)。

最後に、もう一度成功するテストをしてみましょう。まず、チケット テーブルの lock_user フィールドをクリアし、次のリクエストを送信します。

今回はチケット購入に成功しました。チケット表を確認すると、請求書が発行されていることがわかります。

注文フォームを表示:

チケット購入成功記録がもう1件残ります。

ユーザー テーブルを表示します。

ユーザーのアカウントから引き落とされました。

支払い記録テーブルを表示します。

すでに支払い記録があることがわかります。

4. 結論

全体的に、上記のケースは技術的に難しいものではありません。複雑さは設計にあります。まず、良いニュースを処理するプロセスと、メッセージ処理が失敗した後に補償する方法を設計する必要があります。これは全員のスキルを試すテストです。

さらに、上記のケースでは、メッセージの送信と消費の両方で、RabbitMQ のトランザクション メカニズム (メッセージの消費が成功することを保証するため) と Spring のトランザクション メカニズム (メッセージの送信とデータの保存が同時に成功することを保証するため) が使用されます。これらについては詳しくは触れません。

つまり、メッセージ ミドルウェアを介して分散トランザクションを処理すると、パフォーマンスが大幅に向上する代わりに、強力なデータ一貫性が犠牲になります。ただし、この方法を実装するにはコストと複雑さが比較的高く、その使用は実際のビジネス状況に依存します。

この記事はWeChatの公式アカウント「江南一店語」から転載したものです。下のQRコードからフォローできます。この記事の転載については江南易店宇公式アカウントまでご連絡ください。

<<:  VMware が Forrester によってゼロ トラスト ネットワーク アクセスのリーダーに選出

>>:  クラウドネイティブテクノロジーが5Gモバイルネットワークに与える影響

推薦する

Baiduのハイパーリンクアルゴリズムのアップデートについて、SEO担当者はどのように始めるべきか

10月23日はウェブマスターコミュニティにとって大きな打撃でした。百度は「ハイパーリンクアルゴリズム...

オンラインブランドマーケティングがオフラインに浸透し、シーンマーケティングが画期的な武器になりました!

「今この瞬間、私はあなたと一緒にいます」は、過去には想像もできなかった消費シナリオでしたが、今では特...

Renrendaiのジレンマを解読する:監督の欠如とそれに伴うリスクが業界にとって大きな懸念となっている

この新聞の地図製作者、何江氏最近、一部のメディアは、オンラインP2P(別名Renrendai)プラッ...

locvps: 新年特別キャンペーン「年払い小額プラン」、252元/年、2Gメモリ/1コア/20g SSD/400Gトラフィック、香港(3データセンター)/大阪、日本

locvps は新年に向けて特別オファーをご用意しました。年間支払いの小規模プランで 20% 割引 ...

SEO最適化時の外部リンク公開で回避すべきリスク要因の分析

周知のように、ウェブサイト最適化のプロセスは継続的な蓄積と段階的な進歩のプロセスです。百度のアルゴリ...

SEO がすぐに成果を生み出せない理由をご存知ですか?

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

SEO業界に参入しようとしているウェブマスターの友人に宛てた手紙

SEO 業界に参入したばかり、または参入準備中のウェブマスターの友人に宛てたメッセージです。最近、何...

raksmart: 12.12 乾物、年 1 回、サンノゼ/香港、VPS、専用サーバー

raksmart は昨年 12.12 プロモーションを実施しました。米国データセンターの無制限トラフ...

5G 時代のクラウド コンピューティング市場の動向を通信事業者はどのように把握するのでしょうか?

2019年6月6日、工業情報化部は3大通信事業者に5G商用ライセンスを正式に発行し、中国が正式に5G...

企業ウェブサイト向けのオリジナル記事リソースをより適切に見つける方法

最近、ウェブサイトの構築と最適化に忙しく、ブログの更新が遅くなってしまいました。この間、多くの企業サ...

Baiduホームページにおける個人ウェブサイトの長期ランキングについての簡単な議論

自動車業界のウェブサイトのキーワード最適化ソリューションについてですが、自動車業界のキーワードランキ...

クラウド在庫管理でオンデマンドセルフサービスのリスクを抑制

シャドー IT はコンプライアンスとセキュリティの問題を引き起こし、クラウド コンピューティングのコ...

ダブル11にBステーションが潜んでいる?

「ビリビリのトップ100製品リスト?聞いたことないよ」多くのUpマスターは半ば冗談めかして「ビリビリ...

占いと風水:インターネットで変わるビジネス

占い、性格分析、ゆるキャラ、生贄など伝統的なビジネスは、インターネット上でどのように生き残ることがで...

アプリ操作丨Appleクリーニング無料リストトップ200! 40以上のゲームがリストから削除されました

単語の削除、ランキングのロック、棚からの削除、より遅く厳しいレビュー、承認なしでの繰り返しの提出.....