ホームページ  >  記事  >  Java  >  Java分散ロックを実装する方法

Java分散ロックを実装する方法

王林
王林転載
2023-04-25 18:19:071727ブラウズ

1. 分散ロックの概要

単一マシンのマルチスレッド: Java では通常、ReetrantLock クラスや synchronized キーワードなどのローカル ロックを使用して、JVM プロセス内の複数のスレッドを制御し、ローカル共有にアクセスします。

Java分散ロックを実装する方法

# 分散システム: 通常、さまざまなサービス/クライアントは別個の JVM プロセスで実行されます。複数の JVM プロセスが同じリソースを共有する場合、ローカル ロックを使用してリソースへの相互排他的アクセスを実現する方法はありません。このようにして、分散ロックが誕生しました。

例: システムの注文サービスのコピーが合計 3 つ展開され、すべてが外部にサービスを提供します。ユーザーは注文前に在庫を確認する必要があるため、過剰販売を防ぐために、在庫確認操作への同期アクセスを実現するためにロックが必要です。注文サービスは別の JVM プロセスに配置されているため、この場合、ローカル ロックは正しく機能しません。分散ロックを使用する必要があります。これにより、複数のスレッドが同じ JVM プロセスに存在しない場合でも、同じロックを取得でき、それによって共有リソースへの相互排他的アクセスが実現されます。

Java分散ロックを実装する方法

最も基本的な分散ロックは次の条件を満たす必要があります:

  • 相互排他: いつでも、ロックは次のユーザーのみが保持できます。 1 つのスレッド ホールド;

  • 高可用性: ロック サービスは高可用性です。さらに、ロックを解放するためのクライアントのコード ロジックに問題がある場合でも、ロックは最終的に解放され、共有リソースへの他のスレッドのアクセスには影響しません。

  • リエントラント: ノードはロックを取得した後、再度ロックを取得できます。

2. Redis に基づいた分散ロックの実装

1. Redis に基づいた最も単純な分散ロックの実装方法

ローカル ロックかどうかまたは 分散ロックの核心は == 「相互排他」 == にあります。

Redis では、SETNX コマンドを使用して相互排他を実現できます。 SETNXつまり、SET if Not eXists (Java の setIfAbsent メソッドに相当) キーが存在しない場合は、キーの値が設定されます。キーがすでに存在する場合、SETNX は何も行いません。

> SETNX lockKey uniqueValue
(integer) 1
> SETNX lockKey uniqueValue
(integer) 0

ロックを解除するには、 DEL コマンドで対応するキーを直接削除します

> DEL lockKey
(整数) 1

他のロックを誤って削除しないように、ここをお勧めしますLuaスクリプトを使用して、キーに対応する値(ユニーク値)で判定します。

Lua スクリプトは、ロック解除操作の原子性を確保するために選択されています。 Redis は Lua スクリプトをアトミックな方法で実行できるため、ロック解放操作のアトミック性が保証されます。

// 释放锁时,先比较锁对应的 value 值是否相等,避免锁的误释放
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

Java分散ロックを実装する方法

これは最も単純な Redis 分散ロックの実装です。実装方法は比較的単純で、パフォーマンスは非常に効率的です。ただし、この方法で分散ロックを実装するにはいくつかの問題があります。たとえば、ロックを解放するロジックが突然ハングアップするなど、アプリケーションで何らかの問題が発生した場合、ロックが解放されず、他のスレッド/プロセスが共有リソースにアクセスできなくなる可能性があります。

2. ロックの有効期限を設定する理由

主にロックが解放されないようにするためです

127.0.0.1:6379> SET lockKey uniqueValue EX 3 NX
OK

  • lockKey: ロック名;

  • uniqueValue: ロックを一意に識別できるランダムな文字列。

  • NX: SET は、lockKey に対応するキー値が存在しない場合にのみ成功します;

  • EX: 有効期限の設定 (秒 ) EX 3 は、このロックの自動有効期限が 3 秒であることを示します。 EX に対応するのは PX (ミリ秒単位) で、どちらも有効期限の設定です。

指定されたキーの値と有効期限の設定がアトミックな操作であることを確認してください。 ! !そうしないと、ロックが解除できないという問題が依然として発生する可能性があります。

このソリューションには抜け穴もあります:

  • 共有リソースの操作時間が有効期限よりも長い場合、ロックの早期期限切れの問題が発生します。分散ロックが発生する 直接的な障害

  • ロック タイムアウトの設定が長すぎると、パフォーマンスに影響します

3。ロックの正常な更新を実現します

Redisson は、複数の分散ロックの実装だけでなく、すぐに使える多くの機能を提供するオープン ソースの Java 言語 Redis クライアントです。さらに、Redisson は、Redis スタンドアロン、Redis Sentinel、Redis Cluster などの複数のデプロイメント アーキテクチャもサポートしています。

Redisson の分散ロックには自動更新メカニズムが備わっています。使い方は非常に簡単で、原理も比較的単純です。ロックの監視と更新に特別に使用される Watch Dog が提供されます。共有リソースの実行が完了していない場合、Watch Dog はロックの有効期限を継続的に延長して、タイムアウトによってロックが解放されないようにします。

Java分散ロックを実装する方法

使用方式举例:

// 1.获取指定的分布式锁对象
RLock lock = redisson.getLock("lock");
// 2.拿锁且不设置锁超时时间,具备 Watch Dog 自动续期机制
lock.lock();
// 3.执行业务
...
// 4.释放锁
lock.unlock();

只有未指定锁超时时间,才会使用到 Watch Dog 自动续期机制。

// 手动给锁设置过期时间,不具备 Watch Dog 自动续期机制
lock.lock(10, TimeUnit.SECONDS);

总的来说就是使用Redisson,它带有自动的续期机制

4. 如何实现可重入锁

所谓可重入锁指的是在一个线程中可以多次获取同一把锁,比如一个线程在执行一个带锁的方法,该方法中又调用了另一个需要相同锁的方法,则该线程可以直接执行调用的方法即可重入 ,而无需重新获得锁。像 Java 中的 synchronized 和 ReentrantLock 都属于可重入锁。

可重入分布式锁的实现核心思路是线程在获取锁的时候判断是否为自己的锁,如果是的话,就不用再重新获取了。为此,我们可以为每个锁关联一个可重入计数器和一个占有它的线程。当可重入计数器大于 0 时,则锁被占有,需要判断占有该锁的线程和请求获取锁的线程是否为同一个。

以上がJava分散ロックを実装する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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