申し訳ありませんが、インターネット上で見つかったすべての Redis 分散ロックには脆弱性があります。

申し訳ありませんが、インターネット上で見つかったすべての Redis 分散ロックには脆弱性があります。

Redis ベースの分散ロックは誰もが知っているものですが、分散ロックが失敗したことはありますか?障害が発生したとき、使用している分散ロックが本当に信頼できるかどうか疑問に思ったことはありませんか?以下は私自身の経験に基づいてまとめたものです。

[[334734]]

画像はPexelsより

分散ロックは本当に必要ですか?

分散ロックの使用は、複数のプロセスが同じリソースにアクセスするという問題があることを示しています。

一般的に、同じリソースへの繰り返しアクセスは、次の 2 つのシナリオで防止されます。

  • 効率を向上します。たとえば、複数のノードが同じタスクのバッチを計算している場合、あるタスクがすでにノードによって計算中であれば、コンピューティング リソースの無駄を避けるために、他のノードはそのタスクを繰り返し計算する必要がありません。ただし、繰り返しカウントしても、それ以上の大きな損失は発生しないため、問題ありません。つまり、時々失敗しても構わないということです。
  • 正確性を確保します。この状況では、ロックに非常に高い要求が課せられます。計算を繰り返すと正確性に影響します。これによって失敗は許されません。

分散ロックの導入には、必然的に MySQL、Redis、Zookeeper などのサードパーティ インフラストラクチャの導入が必要になります。

分散ロックを実装するためのインフラに問題があれば業務にも影響が出るため、分散ロックを使用する前に、ロックなしで実装できるかどうかを検討してみてはいかがでしょうか。

ただし、これはこの記事の範囲外です。この記事では、ロックの必要性が合理的であると想定し、上記の 2 番目のケースを優先する傾向があります。なぜ? 100% 信頼できる分散ロックは存在しないからです。以下の内容を読めば理解できるでしょう。

まずは簡単な分散ロックの実装から始めましょう

分散ロックの Redis 実装は非常に一般的です。自分で実装したり、サードパーティのライブラリを使用したりするのは、少なくともそう思えるほど、非常に簡単です。ここでは、最もシンプルで信頼性の高い Redis 実装を紹介します。

最もシンプルな実装

実装は非常に古典的ですが、重要なポイントは 2 つあります。

  • 施錠と解錠に使用するロックは同じである必要があります。一般的な解決策は、各ロックにキー (一意の ID) を割り当て、ロック時にキーを生成し、ロック解除時にキーを決定することです。
  • リソースを永続的にロックすることはできません。一般的な解決策は、ロックに有効期限を設定することです。もちろん、他にも選択肢はありますが、それについては後で説明します。

コピー&ペースト可能な実装は次のとおりです。

ロック:

  1. 公共 静的ブール型tryLock(文字列キー、文字列ユニークID、整数秒) {
  2. 戻る  "OK" .equals ( jedis.set ( key , uniqueId, "NX" , "EX" , seconds));
  3. }

ここで、SET キー値 PX milliseoncds NX が呼び出されます。このコマンドがわからない場合は、SET キー値 [EX 秒|PX ミリ秒] [NX|XX] [KEEPTTL] を参照してください。

  1. https://redis.io/commands/設定 

ロック解除:

  1. 公共 静的ブール型 releaseLock(文字列キー、文字列 uniqueId) {
  2. 文字列luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
  3. "redis.call('del', KEYS[1]) を返す、そうでなければ 0 を返す、終了" ;
  4. jedis.eval()を返す
  5. luaスクリプト、
  6. Collections.singletonList(キー),
  7. コレクション.シングルトンリスト(一意のID)
  8. ).equals(1L);
  9. }

この実装の本質は、まず一意の ID が等しいかどうかを判断し、次に操作を実行する単純な Lua スクリプトにあります。

それは信頼できるでしょうか?

この実装の問題は何でしょうか?

  • 単一点の問題。上記の実装は、マスターノード 1 つだけで実行できます。ここでの単一のポイントは、単一のマスターを指します。クラスターであっても、ロックが正常にロックされた後、マスターからスレーブにロックがコピーされるときに失敗すると、同じリソースが複数のクライアントによってロックされることになります。
  • 実行時間がロックの有効期限を超えました。常にロックがかかった状態になる状況を回避するために、バックアップ有効期限が追加されると上記に書かれています。時間が経過すると、ロックは自動的に解除されます。しかし、この期間内にタスクが完了しなかった場合はどうなるでしょうか? GC やネットワークの遅延によりタスク時間が長くなるため、ロックの有効期限内にタスクが完了することを保証するのは困難です。

これら2つの問題をどのように解決すればよいでしょうか?もっと複雑な実装を試してみましょう。

レッドロックアルゴリズム

最初の単一点の問題について、Redis のアイデアに続いて、次に思い浮かぶのは間違いなく Redlock です。

単一マシン問題を解決するために、Redlock には複数 (2 つ以上) の Redis マスター ノードが必要です。複数のマスターノードは互いに独立しており、データの同期はありません。

Redlock の実装は次のとおりです。

①現在の時刻を取得します。

② N個のノードのロックを順に取得する。各ノードをロックする実装方法は上記と同様です。ここで細かい点として、ロックを取得するたびに有効期限が異なり、前回のロック取得操作で消費された時間を差し引く必要があるという点があります。

たとえば、渡されたロックの有効期限が 500 ミリ秒で、最初のノードのロックを取得するのに 1 ミリ秒かかる場合、最初のノードのロック有効期限は 499 ミリ秒になります。 2 番目のノードのロックを取得するのに 2 ミリ秒かかる場合、2 番目のノードのロックの有効期限は 497 ミリ秒になります。

ロックの有効期限が 0 以下の場合、ロックを取得する操作全体がタイムアウトし、操作全体が失敗したことを意味します。

③ロックが正常に取得できたかどうかを判定します。上記の手順でクライアントが (N/2+1) 個のノード ロックを取得し、各ロックの有効期限が 0 より大きい場合、ロックの取得は成功し、それ以外の場合は失敗します。失敗した場合はロックを解除します。

④ロックを解除します。ロック解除指示をすべてのノードに送信します。各ノードの実装ロジックは上記の単純な実装と同じです。

なぜすべてのノードで操作する必要があるのですか?分散シナリオでは、ノードからロックを取得できなかったとしても、そのノードでアクセラレーションが失敗したことを意味するわけではありません。ロックは実際には正常に取得された可能性がありますが、ネットワーク ジッタにより戻りタイムアウトが発生しました。

上記は一般的な Redlock 実装の説明です。一見すると、マルチマスター版の簡易版のように見えます。もしこれが本当なら、それはあまりにも単純すぎるでしょう。次に、さまざまなシナリオでこのアルゴリズムがどのように破られるかを分析してみましょう。

分散ロックの落とし穴

同時実行性の高いシナリオにおける問題

以下の問題は、同時実行性の低いシナリオでは発生しない可能性は低いですが、同時実行性が高いシナリオでは発生する可能性が高くなります。

パフォーマンスの問題は、次の 2 つの側面から発生します。

① ロックを取得する時間。 Redlock が高同時実行シナリオで使用される場合、N 個のマスター ノードが存在します。 1 つずつリクエストすると時間がかかり、パフォーマンスに影響します。

この問題は簡単に解決できます。以上の説明から、複数のノードからロックを取得する操作は同期操作ではなく、非同期操作であり、複数のノードが同時にロックを取得できることが容易にわかります。

並列処理される場合でも、ロック TTL > ロック取得時間 + タスク処理時間となるように、ロック取得時間を見積もる必要があります。

②ロックされたリソースが大きすぎます。ロック ソリューション自体は、正確性のために同時実行性を犠牲にし、その犠牲はリソースのサイズに比例します。この時点で、リソースを分割することを検討できます。

分割方法は 2 つあります。

① ビジネスの観点から、ロックされたリソースを複数のセグメントに分割し、各セグメントを個別にロックします。たとえば、マーチャントに対して複数の操作を実行し、操作の前にマーチャントをロックする必要がある場合、操作を複数の独立したステップに分割し、個別にロックして同時実行性を向上させることができます。

② バケット化のアイデアを使用して、リソースを複数のバケットに分割します。 1 つのロックが失敗した場合は、すぐに次のロックを試してください。たとえば、バッチタスク処理のシナリオでは、200 万の加盟店のタスクを処理するために、処理速度を上げるために複数のスレッドが使用され、各スレッドで 100 の加盟店を処理し、これらの 100 の加盟店をロックする必要があります。

これを処理しないと、2 つのスレッドによって同時にロックされるマーチャントが重複していないことを確認することが難しくなります。この場合、1 つの次元を使用できます。

たとえば、あるタグに対して、マーチャントがバケットに分割され、1 つのタスクが 1 つのバケットを処理します。このバケットを処理した後、競合を減らすために次のバケットが処理されます。

再試行の問題: 単純な実装でも Redlock 実装でも、再試行ロジックが存在します。

上記のアルゴリズムを直接実装すると、ほぼ同時に同じロックを取得するクライアントが複数存在し、各クライアントは一部のノードをロックしますが、大多数のノードを取得するクライアントは存在しません。

解決策も非常に一般的で、再試行時間にランダムな時間を追加して、再試行中に複数のノードをずらすというものです。これによって問題が完全に解決されるわけではありませんが、問題を効果的に軽減することができます。試してみて効果があることが証明されました。

ノードのダウンタイム

永続性のない単一のマスターノードの場合、ダウンするとシステムがクラッシュします。この場合、実装では繰り返し操作をサポートし、冪等性を確保する必要があります。

Redlock などのマルチマスター シナリオでは、次のようなシナリオを見てみましょう。

  • Redis ノードが 5 つ (A、B、C、D、E) あり、永続化は実行されていないとします。
  • クライアント 1 は 3 つのノード A、B、C からロックを正常に取得し、クライアント 1 はロックを正常に取得します。
  • ノード C がダウンしています。
  • クライアント2はC、D、Eからのロックを正常に取得します。クライアント2もロックを正常に取得します。すると、Client1 と Client2 が同時にロックを取得し、Redlock が解除されます。

どうすれば解決できるでしょうか?最も簡単な解決策は、永続性をオンにすることです。永続性により、すべての Redis コマンドを永続化できますが、これはパフォーマンスに大きな影響を与えるため、通常は使用されません。この方法を使用しないと、ノードがハングしたときに少量のデータが確実に失われ、その中にロックが含まれる可能性があります。

もう一つの選択肢は開始を遅らせることです。つまり、ノードがクラッシュして修復された後、すぐに追加されるのではなく、参加する前にしばらく待機します。待機時間は、クラッシュの瞬間のすべてのロックの最大 TTL よりも大きくする必要があります。

しかし、この解決策でも問題は解決できません。上記のステップ 3 で B と C の両方が失敗した場合はどうなりますか?すると、残るノードは A、D、E の 3 つだけになります。D と E からロックを正常に取得できれば十分ですが、それでも問題は発生します。

この問題を緩和する唯一の方法は、マスターノードの総数を増やすことです。マスターノードを追加すると安定性は向上しますが、コストも増加するため、両者のバランスを取る必要があります。

タスク実行時間がロックTTLを超える

以前は、ネットワークの遅延、ロックの期限切れ、タスクが複数のスレッドによって実行されることなどにより、生産ラインでタスクの実行時間が予想を超える状況がありました。

この問題は、Zookeeper や DB に基づくものも含め、すべての分散ロックで発生します。これは、ロックの有効期限と、クライアントがロックの有効期限が切れたことを認識していないこととの間の矛盾です。

セッションをロックする場合、通常は、クライアントがクラッシュしてロックが解除されるのを防ぐために、ロックの TTL を設定します。

ただし、このような使用法はすべて同じ問題に直面します。つまり、クライアントの実行時間がロックの TTL よりも短くなるという保証がないということです。

ほとんどのプログラマーは楽観的で、このような状況はあり得ないと考えていますが、私もかつてはそう思っていましたが、現実に何度も打ちのめされるまではそう思っていました。

Martin Kleppmann 氏もこれに疑問を呈していたので、彼の写真をそのまま使用しましょう。

  • クライアント1がロックを取得します。
  • クライアント 1 がタスクを開始すると、STW GC が発生し、時間がロックの有効期限を超えます。
  • クライアント2はロックを取得し、タスクを開始します。
  • クライアント1のGCが終了し、タスクが続行されます。この時点で、Client1 と Client2 の両方がロックを取得したと判断してタスクを処理し、エラーが発生します。

Martin Kleppmann 氏は GC の例を挙げましたが、ネットワーク遅延の状況に​​遭遇しました。どのような状況であっても、この状況は避けられないことは否定できず、一度発生すると混乱しやすくなります。

この問題を解決するにはどうすればいいでしょうか? 1 つの解決策は、TTL を設定するのではなく、ロックを正常に取得した後にロックにウォッチドッグを追加することです。ウォッチドッグはタイマー タスクを開始し、ロックが解放されずに期限切れになりそうなときにロックを更新します。

これは少し抽象的なので、Redisson のソースコードと合わせて説明しましょう。

  1. パブリッククラスRedissonLockはRedissonExpirableを拡張し、RLockを実装します{
  2. ...
  3. @オーバーライド
  4. パブリックボイドロック(){
  5. 試す {
  6. 割り込み可能にロックします();
  7. } キャッチ (InterruptedException e) {
  8. スレッド.currentThread().interrupt();
  9. }
  10. }
  11.  
  12. @オーバーライド
  13. パブリックvoid lock(long リース時間、TimeUnit 単位) {
  14. 試す {
  15. lockInterruptibly(リース時間、単位);
  16. } キャッチ (InterruptedException e) {
  17. スレッド.currentThread().interrupt();
  18. }
  19. }
  20. ...
  21. }

Redisson でよく使用されるロック API は上記の 2 つです。 1つはTTLで渡さないことです。この場合、Redisson が自らメンテナンスを行い、自動的に更新します。

もう 1 つの方法は、TTL を自分で渡すことです。この場合、Redisson はリースを自動的に更新しません。または、leaseTime の値を -1 として渡すことができます。ただし、この方法はお勧めできません。すでに API が存在するのに、なぜこのような奇妙な書き方を使用するのでしょうか?

次に、パラメータを渡さないメソッドのロック ロジックを分析します。

  1. パブリッククラスRedissonLockはRedissonExpirableを拡張し、RLockを実装します{
  2.  
  3. ...
  4.  
  5. 公共 静的最終long LOCK_EXPIRATION_INTERVAL_SECONDS = 30;
  6. 保護された長い内部ロックリース時間 = TimeUnit.SECONDS.toMillis(LOCK_EXPIRATION_INTERVAL_SECONDS);
  7.  
  8.  
  9. @オーバーライド
  10. パブリックボイドロック(){
  11. 試す {
  12. 割り込み可能にロックします();
  13. } キャッチ (InterruptedException e) {
  14. スレッド.currentThread().interrupt();
  15. }
  16. }
  17.  
  18. @オーバーライド
  19. パブリックvoid lockInterruptibly() は InterruptedException をスローします {
  20. ロック割り込み可能(-1, null );
  21. }
  22.  
  23. @オーバーライド
  24. パブリックvoid lockInterruptibly(longleasingTime, TimeUnit unit) throws InterruptedException {
  25. 長いスレッド ID = Thread.currentThread().getId();
  26. Long ttl = tryAcquire(leaseTime, Unit, threadId);
  27. // ロックを取得しました
  28. ttl == null の場合
  29. 戻る;
  30. }
  31.  
  32. RFuture<RedissonLockEntry> future = subscribe(threadId);
  33. コマンドExecutor.syncSubscription(将来);
  34.  
  35. 試す {
  36. )の間{
  37. ttl = tryAcquire(リース時間、ユニット、スレッドID);
  38. // ロックを取得しました
  39. ttl == null の場合
  40. 壊す;
  41. }
  42.  
  43. //メッセージ待機中
  44. ttl >= 0 の場合
  45. getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
  46. }それ以外{
  47. getEntry(threadId).getLatch().acquire();
  48. }
  49. }
  50. ついに
  51. 購読を解除します(将来、スレッドID);
  52. }
  53. // get(lockAsync(leaseTime,unit));
  54. }
  55.  
  56. プライベート Long tryAcquire(long リースタイム、TimeUnit ユニット、long スレッド ID) {
  57. get(tryAcquireAsync(leaseTime, unit, threadId)) を返します
  58. }
  59.  
  60. プライベート <T> RFuture<Long> tryAcquireAsync(long リースタイム、TimeUnit ユニット、final long スレッド ID) {
  61. リースタイムが -1 の場合
  62. tryLockInnerAsync(leaseTime、ユニット、スレッドId、RedisCommands.EVAL_LONG)を返します
  63. }
  64. RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(LOCK_EXPIRATION_INTERVAL_SECONDS、TimeUnit.SECONDS、threadId、RedisCommands.EVAL_LONG);
  65. ttlRemainingFuture.addListener(新しいFutureListener<Long>() {
  66. @オーバーライド
  67. パブリックvoid operationComplete(Future<Long> future) は例外をスローします {
  68. (!future.isSuccess())の場合{
  69. 戻る;
  70. }
  71.  
  72. Long ttlRemaining = future.getNow();
  73. // ロックを取得しました
  74. 残り時間 == null の場合
  75. スケジュール有効期限更新(スレッドID)
  76. }
  77. }
  78. });
  79. ttlRemainingFutureを返します
  80. }
  81.  
  82. プライベート void スケジュール更新 (最終ロング スレッド ID) {
  83. 有効期限更新マップにキーが含まれている場合(getEntryName()) {
  84. 戻る;
  85. }
  86.  
  87. タイムアウトタスク = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
  88. @オーバーライド
  89. パブリックvoid run(Timeout timeout) 例外をスローします {
  90.  
  91. RFuture<ブール値> future = commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
  92. 「(redis.call('hexists', KEYS[1], ARGV[2]) == 1) の場合」 +
  93. "redis.call('pexpire', KEYS[1], ARGV[1]); " +
  94. "1を返す; " +
  95. 「終了;」 +
  96. "0を返します。"
  97. コレクション。<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
  98.  
  99. future.addListener(新しいFutureListener<Boolean>() {
  100. @オーバーライド
  101. パブリックvoid operationComplete(Future<Boolean> future) は例外をスローします {
  102. 有効期限更新マップを削除します。
  103. (!future.isSuccess())の場合{
  104. log.error( "ロック " + getName() + " の有効期限を更新できません" , future.cause());
  105. 戻る;
  106. }
  107.  
  108. (future.getNow())の場合{
  109. // スケジュールを再設定する
  110. スケジュール有効期限更新(スレッドID)
  111. }
  112. }
  113. });
  114. }
  115. }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
  116.  
  117. if (expirationRenewalMap.putIfAbsent(getEntryName(), task) != null ) {
  118. タスクをキャンセルします。
  119. }
  120. }
  121.  
  122.  
  123. ...
  124. }

ご覧のとおり、最終的なロック ロジックは org.redisson.RedissonLock#tryAcquireAsync に入ります。ロックの取得に成功すると、scheduleExpirationRenewal に入ります。

ここでタイマーが初期化され、遅延時間は internalLockLeaseTime/3 になります。

Redisson では、internalLockLeaseTime は 30 秒です。つまり、10 秒ごとに 30 秒間更新されます。

分散ロックが Zookeeper に基づいて実装されている場合、Zookeeper を使用してノードが生きているかどうかを確認し、更新を実現できます。私は Zookeeper 分散ロックを使用したことがないので、詳細には触れません。

ただし、この方法では、同時に 1 つのクライアントだけがロックを取得することを保証することはできません。更新が失敗した場合、たとえば Martin Kleppmann が言及した STW GC が発生したり、クライアントが Redis クラスターとの接続を失ったりすると、更新が失敗する限り、複数のクライアントが同時にロックを取得します。

私のシナリオでは、ロックの粒度を下げましたが、Redisson の更新メカニズムで十分でした。より厳密にしたい場合は、更新が失敗した場合にタスクを終了するロジックを追加する必要があります。

このアプローチは以前 Python コードで実装されていましたが、Java ではこのような厳しい状況に遭遇したことはありませんでした。

ここで Martin Kleppmann の解決策についても触れておきたいと思います。個人的には、この解決策は信頼できないと思いますが、その理由については後で説明します。

彼の解決策は、ロックされたリソースに独自の保証セットを維持させ、ロックの失敗により複数のクライアントが同じリソースに同時にアクセスするのを防ぐことです。

クライアントがロックを取得すると、リソース トークンも取得します。このトークンは単調に増加します。リソースが書き込まれるたびに、現在のトークンが古いトークンであるかどうかがチェックされます。そうであれば、書き込みは許可されません。

上記のシナリオでは、クライアント 1 がロックを取得するとトークン 33 が割り当てられ、クライアント 2 がロックを取得するとトークン 34 が割り当てられます。

クライアント 1 の GC 中に、クライアント 2 はすでにリソースを書き込み済みです。この時点で、最大のトークンは 34 です。クライアント 1 が GC から戻ってトークン 33 を使用してリソースを書き込もうとすると、トークンの有効期限が切れているため拒否されます。

このアプローチでは、リソースがトークン ジェネレーターを提供する必要があります。このフェンスソリューションについていくつか質問があります。

①取引を保証するものではありません。図ではストレージへのアクセスは 34 回のみ示されていますが、実際のシナリオでは、タスク内でストレージに複数回アクセスされる可能性があり、これらのアクセスはアトミックである必要があります。

GC の前にクライアント 1 がトークン 33 を使用してストレージに 1 回アクセスすると、GC が発生します。

クライアント 2 はロックを取得し、トークン 34 を使用してストレージにアクセスします。この時点で、2 つのクライアントによって書き込まれたデータが正しいことが保証されますか?

そうでない場合、ストレージ自体にトランザクション メカニズムなど、それを保証する他のメカニズムがない限り、このソリューションには欠陥があります。可能であれば、ここでのトークンは冗長であり、フェンシング ソリューションは不要です。

②同時実行性の高いシナリオでは実用的ではありません。毎回書き込めるのは最大のトークンだけなので、ストレージへのアクセスは線形です。同時実行性の高いシナリオでは、このアプローチによりスループットが大幅に制限されます。分散ロックは主にこのようなシナリオで使用されますが、これは非常に矛盾した設計です。

③これはすべての分散ロックに共通する問題です。このソリューションは一般的なソリューションであり、Redlock または他のロックで使用できます。したがって、これは Redlock とはまったく関係のない単なる解決策であると理解しています。

システムクロックのドリフト

この問題は検討されただけで、実際のプロジェクトでは発生していません。理論的には可能なので、ここでもお話しします。

Redis の有効期限はシステム クロックに依存します。クロックのドリフトが大きすぎると、有効期限の計算に影響します。

システム クロックがドリフトするのはなぜですか?システム時間について簡単に説明しましょう。 Linux では、clock realtime と clock monotonic の 2 つのシステム時間を提供しています。

クロックリアルタイムは、xtime/ウォールタイムとも呼ばれます。この時間はユーザーと NTP によって変更できます。 gettimeofday はこの時間を使用し、Redis も有効期限の計算にこの時間を使用します。

クロック モノトニック (文字通り単調な時間として翻訳されます) はユーザーによって変更されませんが、NTP によって変更されます。

理想的なケースでは、すべてのシステム クロックが常に NTP サーバーと同期されますが、これは明らかに不可能です。

システム クロックのドリフトが発生する原因は 2 つあります。

  • システムの時計が NTP サーバーと同期されていません。現時点ではこれに対する特に良い解決策はないので、運用と保守の担当者に頼るしかありません。
  • 時計のリアルタイムは手動で変更されました。分散ロックを実装する場合は、クロック リアルタイムを使用しないでください。

残念ながら、Redis はこの時間を使用します。 Redis 5.0 のソースコードを調べたところ、依然としてクロックリアルタイムが使用されていることがわかりました。

Antirez はクロック モノトニックに変更するように言いましたが、ボスはまだ変更していません。つまり、Redis サーバーの時間を手動で変更すると、Redis に問題が発生する可能性があります。

要約する

この記事では、シンプルな Redis ベースの分散ロックから、より複雑な Redlock 実装までを説明し、分散ロックの使用過程で遭遇する落とし穴と解決策をいくつか紹介します。

著者: 陳漢里

紹介: 本業を欠かさないプログラマー。私は物流財務グループ、物流ターミナル事業グループ、圧力バランスグループで便利屋として働いてきました。私は Python から Java までのテクノロジー スタックを学習しましたが、ビジネス コードをうまく記述する方法をまだ学習していません。私は抽象モデルを使用してビジネスを災害から救うことを夢見ています。

編集者:タオ・ジアロン

出典: Ele.me 物流技術チーム

<<:  JVMクラスローダーを整理しました

>>:  QingCloudはCITIC NetworkおよびIntone Technologyと戦略的提携を結び、新たなインフラの波に乗り出す

推薦する

今日頭条氏が再びテンセントを批判:異なる扱いを受けているのに、なぜ短編動画プラットフォームはテンセントを平等に扱わないのか

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

良いソフトコピーを書く方法

1. リンクを追加する自社のウェブサイトへのリンクを含めることで、効果的な外部リンクが増え、検索エン...

2023年雲奇会議開幕 アリババの蔡崇馨氏:AI時代の最もオープンなクラウドを構築

10月31日午前、杭州雲棲鎮で2023年雲棲会議が開幕した。アリババグループのジョセフ・ツァイ会長は...

budgetvm: 米国の大規模帯域幅サーバー、10G 帯域幅、月額 99 ドル、2*e5-2630/16G メモリ/2T ハードディスク/20T トラフィック

budgetvm(アメリカの会社、2004年~)は、10Gbpsの帯域幅にアクセスできる、月額99ド...

HostHatch - すべての VPS が 20% オフ + 「言葉では言い表せない」メモリ時間 / 無料 cpanel ライセンス

hosthatch からの最新ニュース: [1] メモリが「言葉では言い表せないほど」倍増 + 20...

2012年の百度の最新アルゴリズムの分析

26日の夜、Baiduが大混乱に陥りました。皆さんもご存知のとおり、大小さまざまなウェブサイトがさま...

UCloud は、全国の教育機関にオンライン教育ソリューションを提供するクラウド教育アライアンスを立ち上げました。

疫病との闘いは終わっていないが、教育は始まった。 UCloudは、感染予防・抑制期間中に「授業は中止...

HPA PaaS コミュニティが設立されました!

1. HPA PaaSとは注: HP APaaS には、ゼロ コード、ロー コード プラットフォーム...

hostyun: 15% オフ、香港 VPS、10Gbps 帯域幅、3 ネットワーク最適化、23.8 元/月、1G メモリ/1 コア/10gSSD/500G トラフィック

Hostyunは、香港の最新のVPS、香港のT3レベルのコンピュータルーム、鶏のための直接10G帯域...

Ultravps シアトル データセンター 1G メモリ/KVM シンプル評価

ultravps.com は fiberhub のサイトであることは、おそらく多くの人が知っているで...

著作権侵害で逃亡していたパイレーツ・ベイの共同創設者が2年後に逮捕される

NetEase Technology News、6月2日、ロイター通信によると、スウェーデン警察は土...

MiTo ウェブサイト構築:中小企業に適したウェブサイト構築プラットフォームとはどのようなものでしょうか?

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

dignusdata: 台湾 VPS、5T トラフィック、月額 6 ユーロのみ、2G メモリ + 25g NVMe

Dignusdataは、ウェブサイト証明書(Dignus Data DOOEL[MK])から判断する...

Baidu、Toutiao、Tencentがインターネット教育を狙っている!

百度と今日頭条が再び主要メディアの見出しを飾った。彼らが最後に一緒にいたのは4月26日だった。両者は...

電子商取引のトラフィックは少ない:来年はブランドB2Cの再編の年になる

【はじめに】垂直電子商取引は100メートル短距離走というよりマラソンのようなもので、規模を追求するゲ...