ホームページ  >  記事  >  データベース  >  Redis を使用して高い同時実行性を解決する方法

Redis を使用して高い同時実行性を解決する方法

WBOY
WBOY転載
2023-06-03 15:43:332553ブラウズ

NoSQL

Not Only SQLの略。 NoSQL は、従来の RDBMS が特定の問題に対処できないことを解決するために提案されました。

つまり、非リレーショナル データベースです。リレーショナル データの ACID 特性は保証されません。通常、データ間に相関関係はありません。拡張の点で実装が非常に簡単で、パフォーマンスが高いです。

Redis

Redis は nosql の代表的な存在であり、現在のインターネット企業にとって必須の技術でもあります。

Redis は主にハッシュ テーブルを使用して、キーと値のペアのストレージを実装します。ほとんどの場合、リクエストがディスクに直接アクセスしないようにキャッシュの形で直接使用されるため、効率が非常に良く、中小企業のニーズを十分に満たすことができます。

#一般的なデータ型

  • 文字列

  • ハッシュ ハッシュ

  • #リスト list
  • ##sets セット
  • #Ordered set sort set

  • # 文字列とそれぞれの操作コマンドは追加、削除、変更、確認だけですが、具体的なコマンドについては後ほど整理します。

  • 問題点

Web アプリケーションで多くのリクエストが同時に発生すると、データの読み取りと保存でエラーが発生する可能性があります。つまり、ダーティ リードやダーティ データ生成が発生します。

分散プロジェクトでは、さらに多くの問題が発生します。

感想

同時実行性に関して言えば、本質的には、複数のリクエストが同時に受信され、正しく処理できないということです。

すべてのリクエストをキューに入れて、ビジネス ロジックを実行するためにリクエストを 1 つずつ受け取ることができます。現時点では、メッセージ キューを使用することが実現可能な解決策です。同時実行性の高いメッセージ キューの対処方法については、次回まとめます。

もう 1 つの方法は、並列処理を直列化に直接変換することです。Java は同期を提供します。それは同期ですが、これは、厳しい効率要件がある場所や分散プロジェクトにはまだ適切なソリューションではないため、同時実行の問題を解決するために Redis を使用して分散ロックを実装することになります。

分散ロック

分散プロジェクトでは、ロックとロック解除を表すために、一意で汎用的かつ効率的な識別子が使用されます。

Redis は実装が非常に簡単です。つまり、キーが存在するかどうかによって、キーがロックされているかロック解除されているかがわかります。

文字列型を例として挙げます:

Integer stock = goodsMapper.getStock();
if (stock > 0) {
    stock =- 1;
    goodsMapper.updateStock(stock);
}

上記は、インスタント キルのための最も単純な疑似コードです。分散ロックを実装するために Redis を使用してみます。

// 这里是错误代码,只是一个思考过程,请耐心看完哦
String key = "REDIS_DISTRIBUTION_LOCKER"; // 分布式锁名称
String value = jedisUtils.get(key);
if (value != null) { // 未上锁
    // wingzingliu
    jedisUtils.set(key, 1); // 上锁
    Integer stock = goodsMapper.getStock();
    if (stock > 0) {
        stock =- 1;
        goodsMapper.updateStock(stock);
        jedisUtils.del(key); // 释放锁
    }
}

上記のコードには問題がある可能性があります。つまり、複数のリクエストが同時に受信され、特定の時点で複数のリクエストがすべて空の値を取得した場合、スレッド A は if および// wingzingliu に移動します。ロックすると、他のリクエストも入ってくるため、ダーティなデータが表示されます。

ここでのコードの問題は、原子性の問題が考慮されていないことです。

そこで、redis の setNx コマンドを使用する必要があります。本質は値を設定することですが、これはアトミックな操作です。実行後、設定が成功したかどうかが返されます。

redis> SETNX job "programmer"    # job 设置成功
(integer) 1
 
redis> SETNX job "code-farmer"   # 尝试覆盖 job ,失败
(integer) 0
 
redis> GET job                   # 没有被覆盖
"programmer"

値がある場合に注目すると、失敗して 0 が返されます。したがって、コードは次のように変換されます。

// 这里是错误代码,只是一个思考过程,请耐心看完哦
String key = "REDIS_DISTRIBUTION_LOCKER"; // 分布式锁名称
Long result = jedisUtils.setNx(key, 1);
if (result > 0) { // 上锁成功,进入逻辑
    // wingzingliu1
    Integer stock = goodsMapper.getStock();
    if (stock > 0) {
        stock =- 1;
        goodsMapper.updateStock(stock);
 
        System.out.println("购买成功!");
    } else {
        System.out.println("没有库存了!");
    }
    // wingzingliu2
    jedisUtils.del(key); // 释放锁
}

上記により、アトミック性を確保し、順番に正しく処理することができます。

しかし、別の隠れた問題があります。つまり、スレッドがロックを正常に実行した後、プログラムは wingzingliu1 と wingzingliu2 の間に例外をスローします。その後、プログラムは終了し、ロックは解放できません。他のスレッドは解放できません。入ることすらできない。

解決策は、try catchfinallyブロックを追加し、finallyのロックを解放することです。

しかし、ダウンしている場合はどうなるでしょうか?ロックがロックされた後、マシンはクラッシュします。finally の内容は依然として実行されません。ロックは解放されません。手動で処理しないと、今後すべてのスレッドがアクセスできなくなります。

そこで、redis の有効期限が導入され、一定の時間になると自動的にロックが解除されるようになります。

// 这里是不够完善的代码,请耐心看完哦
try {
    String key = "REDIS_DISTRIBUTION_LOCKER"; // 分布式锁名称
    Long result = jedisUtils.setNx(key, 1, 30); // 假设处理逻辑需要20s左右,设置了30秒自动过期
    if (result > 0) { // 上锁成功,进入逻辑
        Integer stock = goodsMapper.getStock();
        if (stock > 0) {
            stock =- 1;
            goodsMapper.updateStock(stock);
 
            System.out.println("购买成功!");
        } else {
            System.out.println("没有库存了!");
        }
    }
} catch (Exception e) {
    
} finally {
    jedisUtils.del(key); // 释放锁
}

上記は比較的完全な分散ロックですが、まだ小さな欠陥があります。特定のリクエスト A の処理が非常に遅いと仮定します。20 秒かかると予想されますが、35 秒かかります。 30 秒に達すると、ロックの有効期限が切れます。他のリクエストも自然に発生しました。

これにより、同時実行が発生するだけでなく、リクエスト A が処理された後もロック解放操作が実行され続けるため、実際にロックが次のスレッドに渡されます。類推すると、同時実行制御全体が台無しになります。

理論的には、キーの有効期限をより長く設定できますが、それは最善の解決策ではありません。ここで、ライフをロックするという概念が登場します。

ロック寿命延長

名前が示すように、ロック寿命を延長します。この実装では、ロックの有効期限が近づくとロック時間を延長します。 30 秒のロックが使用され、ロックがまだ存在するかどうかを 10 秒ごとにチェックすると仮定します。ロックがまだ存在する場合は、30 秒間ロックを維持します。これにより、上記の考えられる問題が回避されます。

ここではスケジュールされたタスクが使用されており、定期的に呼び出すことができます。

Extension

キーに設定した値は 1 です。実際、リクエスト ID を使用して保存できるため、どのリクエストからのロックであるかを知ることができ、他のスレッドのロックはロック解除されます。フロントエンドによって渡されるか、特定のルールに基づいてサーバーによって生成されます。

以上がRedis を使用して高い同時実行性を解決する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。