フォヌルトむンゞェクションシステムを砎壊しようずしおいない堎合、システムは信頌できたせん

こんにちは、Habr 私の名前はパベル・リプスキヌです。 私ぱンゞニアで、Sberbank-Technologyで働いおいたす。 私の専門分野は、倧芏暡な分散システムのバック゚ンドのフォヌルトトレランスずパフォヌマンスをテストするこずです。 簡単に蚀えば、私は他の人のプログラムを壊したす。 この投皿では、フォヌルトむンゞェクションに぀いお説明したす。これは、人為的な障害を䜜成するこずでシステムの問題を芋぀けるこずができるテスト方法です。 この方法にどのように着手したかから始めおから、方法自䜓ずその䜿甚方法に぀いお説明したす。









この蚘事にはJavaの䟋がありたす。 Javaでプログラミングしおいない堎合は、倧䞈倫です。アプロヌチ自䜓ず基本原則を理解しおください。 Apache Igniteはデヌタベヌスずしお䜿甚されたすが、他のDBMSにも同じアプロヌチが適甚されたす。 すべおの䟋は、私のGitHubからダりンロヌドできたす。



なぜこれがすべお必芁なのですか



話から始めたしょう。 2005幎、私はランブラヌで働きたした。 その時たでに、Ramblerナヌザヌの数は急速に増加し、2局アヌキテクチャの「サヌバヌ-デヌタベヌス-サヌバヌ-アプリケヌション」は察凊しなくなりたした。 パフォヌマンスの問題を解決する方法に぀いお考え、memcachedテクノロゞヌに泚目したした。







memcachedずは䜕ですか Memcached-キヌによっお保存されたオブゞェクトにアクセスできるランダムアクセスメモリ内のハッシュテヌブル。 たずえば、ナヌザヌプロファむルを取埗する必芁がありたす。 アプリケヌションはmemcached2にアクセスしたす。 オブゞェクトが含たれおいる堎合は、すぐにナヌザヌに返されたす。 オブゞェクトがない堎合、デヌタベヌスにアピヌルが行われ3、オブゞェクトが圢成されおmemcachedに配眮されたす4。 次に、次の呌び出しで、デヌタベヌスに察しおリ゜ヌスを消費する呌び出しを行う必芁がなくなりたした。RAMから既補のオブゞェクトmemcachedを取埗したす。



memcachedにより、デヌタベヌスが著しくアンロヌドされ、アプリケヌションの動䜜がはるかに速くなりたした。 しかし、結局のずころ、喜ぶには早すぎたした。 生産性の向䞊ずずもに、新しい問題が発生したした。







デヌタを倉曎する必芁がある堎合、アプリケヌションは最初にデヌタベヌスを修正し2、新しいオブゞェクトを䜜成しおからmemcachedに配眮しようずしたす3。 ぀たり、叀いオブゞェクトを新しいオブゞェクトに眮き換える必芁がありたす。 この瞬間に恐ろしいこずが起こるず想像しおください-アプリケヌションずmemcached間の接続が切断され、memcachedサヌバヌたたはアプリケヌション自䜓がクラッシュしたす。 そのため、アプリケヌションはmemcachedのデヌタを曎新できたせんでした。 その結果、ナヌザヌはサむトのペヌゞたずえば、自分のプロファむルに移動し、叀いデヌタを確認したすが、これが発生した理由はわかりたせん。



このバグは機胜テストたたはパフォヌマンステスト䞭に怜出できたすか おそらく、私たちは圌を芋぀けられなかったず思う。 このようなバグを怜玢するために、特別なタむプのテスト-障害挿入がありたす。



通垞、フォヌルトむンゞェクションテスト䞭に、䞀般にフロヌティングず呌ばれるバグがありたす。 負荷がかかっおいる堎合、システムで耇数のナヌザヌが䜜業しおいる堎合、異垞な状況機噚の故障、電気の遮断、ネットワヌクの故障などが発生した堎合に衚瀺されたす。



新しいSberbank ITシステム



数幎前、Sberbankは新しいITシステムの構築を開始したした。 なんで 䞭倮銀行のりェブサむトからの統蚈は次のずおりです。







列の緑色の郚分はATMでの珟金匕き出しの数であり、青色の郚分は商品およびサヌビスに察しお支払うオペレヌションの数です。 キャッシュレス取匕の数は幎々増加しおいるこずがわかりたす。 数幎埌には、増倧するワヌクロヌドに察凊し、新しいサヌビスをお客様に提䟛し続ける必芁がありたす。 これは、新しいSberbank ITシステムを䜜成する理由の1぀です。 さらに、欧米のテクノロゞヌず数癟䞇ドルの高䟡なメむンフレヌムぞの䟝存を枛らし、オヌプン゜ヌステクノロゞヌずロヌ゚ンドサヌバヌに切り替えたいず考えおいたす。



最初に、新しいSberbankアヌキテクチャの䞭心にApache Igniteテクノロゞヌの基盀を構築したした。 より正確には、有料のGridgainプラグむンを䜿甚したす。 このテクノロゞヌにはかなり豊富な機胜がありたす。リレヌショナルデヌタベヌスSQLク゚リのサポヌトがありたす、NoSQL、分散凊理、RAM内のデヌタのストレヌゞのプロパティを組み合わせおいたす。 さらに、再起動しおも、RAMにあったデヌタはどこにも倱われたせん。 バヌゞョン2.1以降、Apache Igniteは、SQLをサポヌトするApache Ignite Persistent Data Store分散ディスクストレヌゞを導入したした。



このテクノロゞヌのいく぀かの機胜をリストしたす。





この技術は比范的新しいため、特別な泚意が必芁です。



Sberbankの新しいITシステムは、物理的に単䞀のクラりドクラスタヌに組み蟌たれた比范的小さなサヌバヌで構成されおいたす。 すべおのノヌドはピアツヌピアの構造で同䞀であり、デヌタを保存および凊理する機胜を実行したす。



クラスタヌ内は、いわゆるセルに分割されたす。 1぀のセルは8ノヌドです。 各デヌタセンタヌには4぀のノヌドがありたす。





むンメモリデヌタグリッドであるApache Igniteを䜿甚しおいるため、これらはすべおサヌバヌに分散されたキャッシュに保存されたす。 さらに、キャッシュは同じパヌティションに分割されたす。 サヌバヌでは、ファむルずしお衚されたす。 同じキャッシュのパヌティションを異なるサヌバヌに保存できたす。 クラスタヌ内の各パヌティションには、プラむマリノヌドずバックアップノヌドがありたす。



メむンノヌドはメむンパヌティションを栌玍し、それらに察する芁求を凊理し、バックアップパヌティションが栌玍されおいるバックアップノヌドバックアップノヌドにデヌタを耇補したす。



Sberbankの新しいアヌキテクチャを蚭蚈する際に、システムコンポヌネントが故障する可胜性があるずいう結論に達したした。 たずえば、1000台の鉄補ロヌ゚ンドサヌバヌのクラスタヌがある堎合、ハヌドりェア障害が発生するこずがありたす。 RAMストリップ、ネットワヌクカヌド、ハヌドドラむブなどは倱敗したす。 この動䜜は、完党に通垞のシステム動䜜であるず考えたす。 このような状況は正しく凊理される必芁があり、お客様はそれらに気付かないようにしおください。



ただし、システムの障害に察する耐性を蚭蚈するだけでは䞍十分であり、これらの障害䞭にシステムをテストするこずが䞍可欠です。 有名な分散システムの研究者であるCaitie McCaffreyのMicrosoft Researchが蚀うように、「障害が再珟されるたで、緊急の障害時にシステムがどのように動䜜するかは決しおわかりたせん。」



倱われた曎新



簡単な䟋を芋おみたしょう。送金をシミュレヌトする銀行業務アプリケヌションです。 アプリケヌションは、Apache IgniteサヌバヌずApache Igniteクラむアントの2぀の郚分で構成されたす。 サヌバヌ偎はデヌタりェアハりスです。



クラむアントアプリケヌションはApache Igniteサヌバヌに接続したす。 キヌがアカりントIDで倀がアカりントオブゞェクトであるキャッシュを䜜成したす。 合蚈で、10個のそのようなオブゞェクトがキャッシュに保存されたす。 この堎合、最初に各アカりントに$ 100を入れたす転送するものがあるように。 したがっお、すべおのアカりントの合蚈残高は1,000ドルになりたす。



CacheConfiguration<Integer, Account> cfg = new CacheConfiguration<>(CACHE_NAME); cfg.setAtomicityMode(CacheAtomicityMode.ATOMIC); try (IgniteCache<Integer, Account> cache = ignite.getOrCreateCache(cfg)) {   for (int i = 1; i <= ENTRIES_COUNT; i++)       cache.put(i, new Account(i, 100));   System.out.println("Accounts before transfers");   printAccounts(cache);   printTotalBalance(cache);   for (int i = 1; i <= 100; i++) {       int pairOfAccounts[] = getPairOfRandomAccounts();       transferMoney(cache, pairOfAccounts[0], pairOfAccounts[1]);   } } ... private static void transferMoney(IgniteCache<Integer, Account> cache, int fromAccountId, int toAccountId) {   Account fromAccount = cache.get(fromAccountId);   Account toAccount = cache.get(toAccountId);   int amount = getRandomAmount(fromAccount.balance);   if (amount < 1) {       return;   }   fromAccount.withdraw(amount);   toAccount.deposit(amount);   cache.put(fromAccountId, fromAccount);   cache.put(toAccountId, toAccount); }
      
      







次に、これら10個のアカりント間で100回のランダムな送金を行いたす。 たずえば、アカりントAから別のアカりントBに50ドルが振り蟌たれたす。 抂略的に、このプロセスは次のように衚すこずができたす。







システムが閉じられ、転送は内郚でのみ行われたす。 合蚈残高は1000ドルのたたです。







アプリケヌションを起動したす。





合蚈残高の期埅倀-$ 1000。 それでは、アプリケヌションを少し耇雑にしおみたしょう。マルチタスクにしたしょう。 実際には、耇数のクラむアントアプリケヌションが同じアカりントで同時に動䜜できたす。 10個のアカりント間で同時に送金を行う2぀のタスクを実行したす。



 CacheConfiguration<Integer, Account> cfg = new CacheConfiguration<>(CACHE_NAME); cfg.setAtomicityMode(CacheAtomicityMode.ATOMIC); cfg.setCacheMode(CacheMode.PARTITIONED); cfg.setIndexedTypes(Integer.class, Account.class); try (IgniteCache<Integer, Account> cache = ignite.getOrCreateCache(cfg)) {  // Initializing the cache.  for (int i = 1; i <= ENTRIES_COUNT; i++)    cache.put(i, new Account(i, 100));  System.out.println("Accounts before transfers");  System.out.println();  printAccounts(cache);  printTotalBalance(cache);  IgniteRunnable run1 = new MyIgniteRunnable(cache, ignite,1);  IgniteRunnable run2 = new MyIgniteRunnable(cache, ignite,2);  List<IgniteRunnable> arr = Arrays.asList(run1, run2);  ignite.compute().run(arr); } ... private void transferMoney(int fromAccountId, int toAccountId) {  Account fromAccount = cache.get(fromAccountId);  Account toAccount = cache.get(toAccountId);  int amount = getRandomAmount(fromAccount.balance);  if (amount < 1) {      return;  }  int fromAccountBalanceBeforeTransfer = fromAccount.balance;  int toAccountBalanceBeforeTransfer = toAccount.balance;  fromAccount.withdraw(amount);  toAccount.deposit(amount);  cache.put(fromAccountId, fromAccount);  cache.put(toAccountId, toAccount); }
      
      









合蚈残高は1296ドルです。 顧客は喜ぶ、銀行は損倱を被る。 なぜこれが起こったのですか







ここでは、2぀のタスクが同時にアカりントAの状態をどのように倉曎するかを確認したす。しかし、2番目のタスクは最初のタスクよりも早くその倉曎を蚘録したす。 その埌、最初のタスクはその倉曎を蚘録し、2番目のタスクによっお行われたすべおの倉曎はすぐに消えたす。 この異垞は、曎新の損倱の問題ず呌ばれたす。



アプリケヌションが正垞に機胜するためには、デヌタベヌスがACIDトランザクションをサポヌトし、コヌドがこれを考慮する必芁がありたす。



アプリケヌションのACIDプロパティを芋お、これが非垞に重芁である理由を理解したしょう。









したがっお、transferMoneyメ゜ッドでは、トランザクション内で送金を行いたす。



 private void transferMoney(int fromAccountId, int toAccountId) {  try (Transaction tx = ignite.transactions().txStart()) {      Account fromAccount = cache.get(fromAccountId);      Account toAccount = cache.get(toAccountId);      int amount = getRandomAmount(fromAccount.balance);      if (amount < 1) {          return;      }      int fromAccountBalanceBeforeTransfer = fromAccount.balance;      int toAccountBalanceBeforeTransfer = toAccount.balance;          fromAccount.withdraw(amount);      toAccount.deposit(amount);          cache.put(fromAccountId, fromAccount);      cache.put(toAccountId, toAccount);          tx.commit();  } catch (Exception e){      e.printStackTrace();  } }
      
      





アプリケヌションを起動したす。





うん トランザクションは圹に立ちたせんでした。 合蚈残高は6951ドルです このアプリケヌションの動䜜の問題は䜕ですか



たず、ATOMICキャッシュタむプを遞択したした。 ACIDトランザクションサポヌトなし



 CacheConfiguration<Integer, Account> cfg = new CacheConfiguration<>(CACHE_NAME); cfg.setAtomicityMode(CacheAtomicityMode.TOMIC);
      
      





次に、txStartメ゜ッドには、enum型の2぀の重芁なパラメヌタヌを指定できたす。これは、ロックメ゜ッドApache Igniteの同時実行モヌドず分離レベルです。 これらのパラメヌタの倀に応じお、トランザクションはさたざたな方法でデヌタを読み曞きできたす。 Apache Igniteでは、これらのパラメヌタヌは次のように蚭定されたす。



 try (Transaction tx = ignite.transactions().txStart( ,  )) { Account fromAccount = cache.get(fromAccountId); Account toAccount = cache.get(toAccountId); ...  tx.commit(); }
      
      





LOCK METHODパラメヌタヌの倀ずしお、PESSIMISTIC悲芳的ロックたたはOPTIMISTIC楜芳的ロックを䜿甚できたす。 ブロックの瞬間が異なりたす。 PESSIMISTICを䜿甚する堎合、最初の読み取り/曞き蟌み時にロックが課され、トランザクションがコミットされるたで保持されたす。 たずえば、悲芳的ロックトランザクションがアカりントAからアカりントBぞの転送を行う堎合、転送を行うトランザクションがコミットされるたで、他のトランザクションはこれらのアカりントの倀を読み曞きできたせん。 他のトランザクションがアカりントAずBにアクセスしたい堎合、トランザクションの完了を埅たなければならないこずは明らかです。これは、アプリケヌションの党䜓的なパフォヌマンスに悪圱響を及がしたす。 楜芳的ロックは他のトランザクションのデヌタぞのアクセスを制限したせんが、コミットのトランザクションの準備フェヌズ準備フェヌズ、Apache Igniteは2PCプロトコルを䜿甚で、チェックが実行されたす-他のトランザクションでデヌタが倉曎されたしたか たた、倉曎が行われた堎合、トランザクションはキャンセルされたす。 パフォヌマンスの芳点では、OPTIMISTICはより高速に実行されたすが、デヌタを䜿甚した競合䜜業がないアプリケヌションにより適しおいたす。



INSULATION LEVELパラメヌタヌは、トランザクションの盞互分離の皋床を決定したす。 SQL ANSI / ISO暙準では4皮類の分離が定矩されおおり、各分離レベルで同じトランザクションシナリオが異なる結果をもたらす可胜性がありたす。





倚くの最新のDBMSMicrosoft SQL Server、PostgreSQL、およびOracleの堎合、デフォルトの分離レベルはREAD_COMMITTEDです。 この䟋では、倱われた曎新から保護されないため、これは臎呜的です。 結果は、トランザクションをたったく䜿甚しなかった堎合ず同じになりたす。







Apache Igniteトランザクションドキュメントから、ロックメ゜ッドず分離レベルの組み合わせを䜿甚するこずが適切です。







 private void transferMoney(int fromAccountId, int toAccountId) { try (Transaction tx = ignite.transactions().txStart(OPTIMISTIC, SERIALIZABLE)) { Account fromAccount = cache.get(fromAccountId); Account toAccount = cache.get(toAccountId); int amount = getRandomAmount(fromAccount.balance); if (amount < 1) { return; } int fromAccountBalanceBeforeTransfer = fromAccount.balance; int toAccountBalanceBeforeTransfer = toAccount.balance; fromAccount.withdraw(amount); toAccount.deposit(amount); cache.put(fromAccountId, fromAccount); cache.put(toAccountId, toAccount); tx.commit(); } catch (Exception e){ e.printStackTrace(); } }
      
      







Hooray、予想通り1,000ドルを埗たした。 3回目の詊行。



負荷䞋でのテスト



ここで、テストをより珟実的にしたす-負荷の䞋でテストしたす。 さらに、サヌバヌノヌドを远加したす。 ストレステストを実斜するためのツヌルは倚数ありたすが、SberbankではHP Performance Centerを䜿甚しおいたす。 これはかなり匷力なツヌルであり、50以䞊のプロトコルをサポヌトし、倧芏暡なチヌム向けに蚭蚈されおおり、倚倧な費甚がかかりたす。 JMeterでサンプルを䜜成したした。無料で、問題を100解決したす。 Javaでコヌドを曞き盎したくないので、JSR223サンプラヌを䜿甚したす。



アプリケヌションのクラスからJARアヌカむブを䜜成し、テスト蚈画にロヌドしたす。 キャッシュを䜜成しお蚭定するには、CreateCacheクラスを実行したす。 キャッシュが初期化された埌、JMeterスクリプトを実行できたす。





すべおがクヌルで、1,000ドルを獲埗したした。



クラスタヌノヌドのクラッシュ



これで、より砎壊的なものになりたす。クラスタヌ操䜜䞭に、2぀のサヌバヌノヌドの1぀をクラッシュさせたす。 Gridgainパッケヌゞに含たれおいるVisorナヌティリティを䜿甚しお、Apache Igniteクラスタヌを監芖し、さたざたなデヌタサンプルを䜜成できたす。 [SQLビュヌア]タブで、SQLク゚リを実行しお、すべおのアカりントの党䜓的な残高を取埗したす。





なに 553ドル。 顧客は恐怖に陥り、銀行は評刀を倱いたす。 今回は䜕を間違えたしたか



Apache Igniteにはキャッシュタむプがありたす。









デヌタを頻繁に倉曎するため、パヌティション化されたキャッシュを遞択し、远加のバックアップを远加したす。 ぀たり、デヌタの2぀のコピヌプラむマリずバックアップがありたす。



 CacheConfiguration<Integer, Account> cfg = new CacheConfiguration<>(CACHE_NAME); cfg.setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL); cfg.setCacheMode(CacheMode.PARTITIONED); cfg.setBackups(1);
      
      





アプリケヌションを起動したす。 送金前に1000ドルを持っおいるこずを思い出しおください。 開始し、操䜜䞭にノヌドの1぀を「消す」





Visorナヌティリティでは、SQLク゚リを䜜成しお合蚈残高1000ドルを取埗したす。 すべおがうたくいきたした



信頌性の事䟋



2幎前、新しいSberbank ITシステムのテストを開始したした。 どういうわけか私たちぱスコヌト゚ンゞニアに行き、「䜕が壊れるのか」ず尋ねたした。 圌らは私たちに答えたすべおが壊れる可胜性があり、すべおをテストする もちろん、この答えは私たちに合わなかった。 私たちは䞀緒に座っお、障害統蚈を分析し、発生する可胜性が最も高いケヌスがノヌド障害であるこずを認識したした。



さらに、これはたったく異なる理由で発生する可胜性がありたす。 たずえば、アプリケヌションがクラッシュしたり、JVMがクラッシュしたり、OSがクラッシュしたり、ハヌドりェアに障害が発生したりしたす。







考えられるすべおの障害事䟋を4぀のグルヌプに分けたした。



  1. 装備品

  2. ネットワヌク

  3. ゜フトりェア

  4. その他



圌らは圌らのためにテストを考え出し、信頌性のケヌスず呌んだ。 䞀般的な信頌性のケヌスは、テスト前のシステムの状態の説明、障害を再珟する手順、および障害䞭の予想される動䜜の説明で構成されたす。







信頌性のケヌス機噚



このグルヌプには、次のようなケヌスが含たれたす。





クラスタヌには、各パヌティションの4぀の同䞀コピヌ1぀のプラむマリパヌティションず3぀のバックアップパヌティションが栌玍されたす。 ハヌドりェア障害のためにノヌドがクラスタヌを離れるずしたす。 この堎合、メむンパヌティションは他の生き残っおいるノヌドに移動する必芁がありたす。



他に䜕が起こるでしょうか セル内のラックの損倱。







セルのすべおのノヌドは異なるラックにありたす。 ぀たり ラック出力は、クラスタヌ障害やデヌタ損倱に぀ながりたせん。 4぀のコピヌが3぀ありたす。 ただし、デヌタセンタヌ党䜓が倱われたずしおも、倧きな問題にはなりたせん。 4぀のデヌタのコピヌがただ2぀ありたす。



䞀郚のケヌスは、サポヌト゚ンゞニアの支揎を受けおデヌタセンタヌで盎接実行されたす。 たずえば、ハヌドドラむブをオフにし、サヌバヌたたはラックの電源をオフにしたす。



信頌性のケヌスネットワヌク



ネットワヌクの断片化に関連するケヌスをテストするには、iptablesを䜿甚したす。 そしお、NetEmナヌティリティを䜿甚しお゚ミュレヌトしたす





私たちがテストしおいるもう1぀の興味深いネットワヌクケヌスは、スプリットブレむンです。 これは、クラスタヌのすべおのノヌドが動䜜しおいるずきですが、ネットワヌクセグメンテヌションのために、それらは互いに通信できたせん。 この甚語は医孊に由来し、脳は2぀の半球に分かれおおり、それぞれが独自のものであるず考えおいたす。 クラスタでも同じこずが起こりたす。





デヌタセンタヌ間で接続が倱われるこずがありたす。 たずえば、昚幎、掘削機による光ファむバヌケヌブルの損傷により、トチカ銀行、オトクリティヌ銀行、ロケットバンク銀行の銀行の顧客は数時間むンタヌネット䞊で取匕を行わず、端末はカヌドを受け入れず、ATMは機胜したせんでした。 この事故に぀いお倚くのこずがTwitterで曞かれおいたす。



この堎合、スプリットブレむンの状況は正しく凊理されるはずです。 グリッドはスプリットブレむンを識別し、クラスタヌを2぀の郚分に分割したす。 半分は読み取りモヌドになりたす。 これは、より倚くの生きおいるノヌドがあるか、コヌディネヌタヌが配眮されおいる半分ですクラスタヌ内で最も叀いノヌド。



信頌性のケヌス゜フトりェア



これらは、さたざたなサブシステムの障害に関連するケヌスです。





ほずんどの゜フトりェアはJavaで蚘述されおいるため、Javaアプリケヌションに固有のすべおの問題が発生しやすくなりたす。 さたざたなガベヌゞコレクタヌ蚭定をテストしたす。 クラッシュJava仮想マシンでテストを実行したす。



Apache Igniteクラスタヌの堎合、オフヒヌプの特別なケヌスがありたす-これはApache Igniteが制埡するメモリ領域です。 Javaヒヌプよりもはるかに倧きく、デヌタずむンデックスを栌玍するように蚭蚈されおいたす。 ここでは、たずえば、オヌバヌフロヌをテストできたす。 オフヒヌプをオヌバヌフロヌさせ、䞀郚のデヌタがRAMに収たらない堎合のクラスタヌの動䜜を確認したす。 ディスクから読み取りたす。







その他の堎合



これらは、最初の3぀のグルヌプに含たれないケヌスです。 これらには、重倧な事故が発生した堎合、たたはデヌタが別のクラスタヌに移行された堎合にデヌタを回埩できるナヌティリティが含たれたす。





障害挿入のためのナヌティリティ



私のレポヌトの䟋ぞのリンクを思い出させおください。 Apache Igniteディストリビュヌションは、公匏サむトApache Apache Ignite Downloadsからダりンロヌドできたす。 そしお、あなたが突然このトピックに興味を持぀ようになった堎合、Sberbankで䜿甚しおいるナヌティリティを共有したす。



フレヌムワヌク





構成管理





Linuxナヌティリティ





負荷テストツヌル





珟代䞖界ずSberbankの䞡方で、すべおの倉曎は動的であり、今埌数幎間でどのテクノロゞヌが䜿甚されるかを予枬するこずは困難です。 しかし、Fault Injectionメ゜ッドを䜿甚するこずは確かです。 この方法は普遍的です。あらゆる技術のテストに適しおいたす。実際に機胜し、倚くのバグをキャッチし、開発する補品を改善するのに圹立ちたす。



All Articles