ホームページ  >  記事  >  データベース  >  Redis を使用して分散ロックを実装する方法を詳しく説明した記事

Redis を使用して分散ロックを実装する方法を詳しく説明した記事

WBOY
WBOY転載
2022-09-07 14:18:182102ブラウズ

推奨学習: Redis ビデオ チュートリアル

1. 分散ロックとは

マルチスレッド コードを作成すると、異なるスレッドがリソースをめぐって競合することがあります。リソースの競合によるエラーを避けるために、リソースをロックします。ロックを取得したスレッドのみが実行を続行できます。

プロセス内のロックは、本質的にはメモリ内の変数です。スレッドがロックを適用する操作を実行するときに、ロックを表す変数の値を 1 に正常に設定できた場合、それは次のことを意味します。ロックが取得されました その他 スレッドはロックを取得しようとするとブロックされます ロックを所有するスレッドが操作を完了すると、ロックの値が 0 に設定され、ロックが解放されます。

上で話しているのは、サーバーのプロセス内の異なるスレッド間のロックです。このロックはメモリに配置され、分散アプリケーション、たとえば異なるアプリケーションの場合は、 (プロセスまたはスレッド) は異なるサーバーにデプロイされるため、ロックはメモリ内の変数で表すことができません。

ロックはサーバー上の共有メモリ空間で表すことができるため、分散アプリケーションの場合、共有ストレージ システムを使用して共有ロックを保存できます。これが分散ロックです。インメモリ データベースとして, Redis は非常に高速に実行され、分散ロックを実装するための共有ストレージ システムとして非常に適しています。

2. Redis を使用して分散ロックを実装する

ロックの場合、実際にはロックとロックの解放の 2 つの操作しかありません。 Redis を通じてそれを実現する方法を見てみましょう?

2.1 setnx

ロック

#Redis コマンドは、キー値が存在するかどうかを判断します。存在する場合は、存在しない場合は値を作成して割り当て、1 を返すため、setnx を実行して代表的なロック キーの値を設定できます。設定が成功した場合はロックが取得されたことを意味し、失敗した場合はロックを取得できません。

# 使用key为lock来表示一个锁
setnx lock 1

2.2 ロックの解除

操作実行後、ロックを解除したい場合は、Redis のキー値を直接変更します。 lock 削除するだけで、他のプロセスが setnx コマンドを通じてリセットしてロックを取得できるようになります。

# 释放锁
del lock

上記の 2 つのコマンドを通じて、単純な分散ロックを実装しましたが、ここで問題があります。プロセスが setnx コマンドを通じてロックされている場合、特定の If the操作が失敗し、時間内にロックを解放する方法がない場合、他のプロセスがロックを取得できなくなり、システムは実行を続行できなくなります。この問題を解決する方法は、有効期間を設定することです。この有効期間が経過すると、ロックは自動的に解除されます。

2.3 ロックの有効期間を設定する

ロックの有効期間を設定するのは非常に簡単で、expire コマンドを使用するだけです。 Redis の例:

# 加锁
setnx lock 1 
# 给锁设置10s有效期
expire lock 10

ただし、別の問題が発生します。ロックを設定した後、expire コマンドを実行する前にプロセスがハングした場合、expire 実行が成功しなかった場合は、ロックが解放されていないため、上記の 2 つのコマンドが同時に実行されていることを確認する必要があります。

LUA 言語で記述したスクリプトを使用する方法と、Redisset コマンドを使用する方法の 2 つがあります。 ## set コマンドの後に nx パラメータを指定すると、実行効果は setnx と一致し、set コマンドの後に次のパラメータを指定できます。 ex パラメータ。有効期限を設定します。これにより、set コマンドを使用して setnxexpire をマージできるようになります。実行のアトミック性を保証できます。

# 判断是否键值是否存在,ex后面跟着的是键值的有效期,10s
set lock 1 nx ex 10

有効ロックの問題を解決したら、次は別の問題を見てみましょう。

上の図に示すように、

ABC## という 3 つの異なるサーバー上にプロセスが存在します。 # 操作を実行するには、ロックを取得し、実行後にロックを解放する必要があります。 <p>现在的情况是<code>进程A执行第2步时卡顿了(上面绿色区域所示),且时间超出了锁有效期,所以进程A设置的锁自动释放了,这时候进程B获得了锁,并开始执行操作,但由于进程A只是卡顿了而已,所以会继续执行的时候,在第3步的时候会手动释放锁,但是这个时候,锁由线程B所拥有,也就是说进程A删除的不是自己的锁,而进程B的锁,这时候进程B还没执行完,但锁被释放后,进程C可以加锁,也就是说由于进程A卡顿释放错了锁,导致进程B和进程C可以同时获得锁

怎么避免这种情况呢?如何区分其他进程的锁,避免删除其他进程的锁呢?答案就是每个进程在加锁的时候,给锁设置一个唯一值,并在释放锁的时候,判断是不是自己设置的锁。

2.4 给锁设置唯一值

给锁设置唯一值的时候,一样是使用set命令,唯一的不同是将键值1改为一个随机生成的唯一值,比如uuid。

 # rand_uid表示唯一id
set lock rand_id nx ex 10

当锁里的值由进程设置后,释放锁的时候,就需要判断锁是不是自己的,步骤如下:

  • 通过Redisget命令获得锁的值
  • 根据获得的值,判断锁是不是自己设置的
  • 如果是,通过del命令释放锁。

此时我们看到,释放锁需要执行三个操作,如果三个操作依次执行的话,是没有办法保证原子性的,比如进程A在执行到第2步后,准备开始执行del命令时,而锁由时有效期到了,被自动释放了,并被其他服务器上的进程B获得锁,但这时候线程A执行del还是把线程B的锁给删掉了。

解决这个问题的办法就是保证上述三个操作执行的原子性,即在执行释放锁的三个操作中,其他进程不可以获得锁,想要做到这一点,需要使用到LUA脚本。

2.5 通过LUA脚本实现释放锁的原子性

Redis支持LUA脚本,LUA脚里的代码执行的时候,其他客户端的请求不会被执行,这样可以保证原子性操作,所以我们可以使用下面脚本进行锁的释放:

if redis.call("get",KEYS[1]) == ARGV[1] then 
  return redis.call("del",KEYS[1])
else 
  return 0
end

将上述脚本保存为脚本后,可以调用Redis客户端命令redis-cli来执行,如下:

# lock为key,rand_id表示key里保存的值
redis-cli --eval unlock.lua lock , rand_id

推荐学习:Redis视频教程

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

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