Redis を使用して分散ロックを実装する
redis 機能の紹介
1. String、List、Map などの豊富なデータ型をサポートします。 、セット、ZSet など。
2. RDB メソッドと AOF メソッドの両方でデータ永続性をサポート
3. クラスター動作モード、強力なパーティションフォールトトレランスをサポート
4. シングルスレッド、コマンドの順次処理
5. トランザクションのサポート
6. パブリケーションとサブスクリプションのサポート
Redis は、SETNX コマンドを使用して分散ロックを実装します:
SETNX キー値
キーが存在しない場合にのみ、キーの値を value に設定します。
指定されたキーがすでに存在する場合、SETNX は何もアクションを実行しません。
SETNX は「SET if Not eXists」(存在しない場合は SET) の略です。
利用可能なバージョン: >= 1.0.0 時間計算量: O(1) 戻り値:
設定が成功すると、1 を返します。
設定できませんでした。0 を返します。
redis> EXISTS job # job 不存在 (integer) 0 redis> SETNX job "programmer" # job 设置成功 (integer) 1 redis> SETNX job "code-farmer" # 尝试覆盖 job ,失败 (integer) 0 redis> GET job # 没有被覆盖 "programmer"
まず、パブリック Redis アクセス ツール クラスをカプセル化する必要があります。このクラスは、RedisTemplate インスタンスと ValueOperations インスタンスを注入する必要があります。Redis によって実装される分散ロックは最も単純な String 型を使用するため、ValueOperations インスタンスが使用されます。さらに、setIfObsent (文字列キー、文字列値)、expire (文字列キー、長いタイムアウト、TimeUnit 単位)、および delete (文字列キー) という 3 つのメソッドをカプセル化する必要があります。これらは、SETNX、expire、および del コマンドに対応します。それぞれRedisの。 Redis アクセス ツール クラスの具体的な実装は次のとおりです。
@Component public class RedisDao { @Autowired private RedisTemplate redisTemplate; @Resource(name="redisTemplate") private ValueOperations<Object, Object> valOpsObj; /** * 如果key不存在,就存储一个key-value,相当于SETNX命令 * @param key 键 * @param value 值,可以为空 * @return */ public boolean setIfObsent (String key, String value) { return valOpsObj.setIfAbsent(key, value); } /** * 为key设置失效时间 * @param key 键 * @param timeout 时间大小 * @param unit 时间单位 */ public boolean expire (String key, long timeout, TimeUnit unit) { return redisTemplate.expire(key, timeout, unit); } /** * 删除key * @param key 键 */ public void delete (String key) { redisTemplate.delete(key); } }
Redis アクセス ツール クラスの実装が完了したら、次に考慮する必要があるのは、分散ロックの競合をシミュレートする方法です。 Redis 自体は分散クラスターをサポートしているため、マルチスレッド処理のビジネス シナリオをシミュレートするだけで済みます。スレッド プールはここでシミュレーションに使用されます。テスト クラスの具体的な実装は次のとおりです:
@RestController @RequestMapping("test") public class TestController { private static final Logger LOG = LoggerFactory.getLogger(TestController.class); //日志对象 @Autowired private RedisDao redisDao; //定义的分布式锁key private static final String LOCK_KEY = "MyTestLock"; @RequestMapping(value={"testRedisLock"}, method=RequestMethod.GET) public void testRedisLock () { ExecutorService executorService = Executors.newFixedThreadPool(5); for (int i = 0; i < 5; i++) { executorService.submit(new Runnable() { @Override public void run() { //获取分布式锁 boolean flag = redisDao.setIfObsent(LOCK_KEY, "lock"); if (flag) { LOG.info(Thread.currentThread().getName() + ":获取Redis分布式锁成功"); //获取锁成功后设置失效时间 redisDao.expire(LOCK_KEY, 2, TimeUnit.SECONDS); try { LOG.info(Thread.currentThread().getName() + ":处理业务开始"); Thread.sleep(1000); //睡眠1000ms模拟处理业务 LOG.info(Thread.currentThread().getName() + ":处理业务结束"); //处理业务完成后删除锁 redisDao.delete(LOCK_KEY); } catch (InterruptedException e) { LOG.error("处理业务异常:", e); } } else { LOG.info(Thread.currentThread().getName() + ":获取Redis分布式锁失败"); } } }); } } }
上記のコードを通じて、次の疑問が生じる可能性があります:
スレッドが分散ロック、ロックを再取得してみませんか?
スレッドは分散ロックを正常に取得した後、ロックの有効期限を設定します。有効期限はどのように決定されますか?
スレッドのビジネス処理が完了した後、なぜロックを削除する必要があるのでしょうか?
これらの質問について話し合うことができます。
まず、キーがすでに存在する場合、Redis の SETNX コマンドは操作を実行しないため、SETNX によって実装される分散ロックはリエントラント ロックではありません。もちろん、コードを通じて、または分散ロックが取得されるまで n 回再試行することもできます。ただし、これは公正な競争を保証するものではなく、スレッドはロックを待機しているためにブロックされます。したがって、Redis によって実装された分散ロックは、共有リソースが 1 回書き込まれ、複数回読み取られるシナリオにより適しています。
2 番目に、分散ロックには有効期限を設定する必要があり、その有効期限は (データの整合性を確保するため) ビジネス処理に必要な時間より長くする必要があります。そのため、テスト段階では、通常の業務処理にかかる時間をできるだけ正確に予測し、業務処理プロセスにおける何らかの原因によるデッドロックを防ぐために有効期限を設定する必要があります。
第三に、業務処理が完了したら、ロックを削除する必要があります。
上記の分散ロックの設定とロックの有効期限の設定は 2 つの手順で完了しますが、より合理的な方法は、分散ロックの設定とロックの有効期限の設定を 1 回の操作で行うことです。両方とも成功するか、両方とも失敗するかのどちらかです。実装コードは次のとおりです:
/** * Redis访问工具类 */ @Component public class RedisDao { private static Logger logger = LoggerFactory.getLogger(RedisDao.class); @Autowired private StringRedisTemplate stringRedisTemplate; /** * 设置分布式锁 * @param key 键 * @param value 值 * @param timeout 失效时间 * @return */ public boolean setDistributeLock (String key, String value, long timeout) { RedisConnection connection = null; boolean flag = false; try { //获取一个连接 connection = stringRedisTemplate.getConnectionFactory().getConnection(); //设置分布式锁的同时为锁设置失效时间 connection.set(key.getBytes(), value.getBytes(), Expiration.seconds(timeout), RedisStringCommands.SetOption.SET_IF_ABSENT); flag = true; } catch (Exception e) { logger.error("set automic lock error:", e); } finally { //使用后关闭连接 connection.close(); } return flag; } /** * 查询key的失效时间 * @param key 键 * @param timeUnit 时间单位 * @return */ public long ttl (String key, TimeUnit timeUnit) { return stringRedisTemplate.getExpire(key, timeUnit); } } /** * 单元测试类 */ @RunWith(SpringRunner.class) @SpringBootTest public class Demo1ApplicationTests { private static final Logger LOG = LoggerFactory.getLogger(Demo1ApplicationTests.class); @Autowired private RedisDao redisDao; @Test public void testDistributeLock () { String key = "MyDistributeLock"; //设置分布式锁,失效时间20s boolean result = redisDao.setDistributeLock(key, "1", 20); if (result) { LOG.info("设置分布式锁成功"); long ttl = redisDao.ttl(key, TimeUnit.SECONDS); LOG.info("{}距离失效还有{}s", key, ttl); } } }
単体テスト クラスを実行すると、結果は次のようになります:
2019-05-15 13:07:10.827 - 设置分布式锁成功 2019-05-15 13:07:10.838 - MyDistributeLock距离失效还有19s
Redis 関連の知識の詳細については、Redis の使用方法のチュートリアルを参照してください## # カラム!
以上がRedis を使用して分散ロックを実装する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。