1. ホーム
  2. データベース
  3. レディス

RedisTemplatを使った簡単な分散ロックの実装の話

2022-01-20 19:53:31

redissonフレームワークを使用しないRedis分散ロックの実装

準備すること

依存関係のインポート

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>



RedisConfigクラスの書き方

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String , Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        //String type key serializer
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //String type value serializer
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        //Hash type key serializer
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        //Hash type value serializer
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        //Inject the connection factory
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        return redisTemplate;
    }
 }


1. SpringBootTestでテストモジュールを書く

1.1: プレースホルダロックの使用

Placeholder locking problem : 例外発生時にロックが解除できず、後続スレッドがデッドロックになる。

@SpringBootTest
class ApplicationTests {
    @Autowired
    private RedisTemplate redisTemplate;
	@Test
	public void lodsTest01(){
		ValueOperations valueOperations = redisTemplate.opsForValue();
	        // create a placeholder, if the key does not exist to set success
	        Boolean isLock = valueOperations.setIfAbsent("k1", "v1");
	        //If the occupancy is successful, do the normal operation
	        if (isLock){
	        	//set a name to store in redis
	            valueOperations.set("name","xxxx");
	            // Retrieve the name from redis
	            String name = (String) valueOperations.get("name");
	            System.out.println("name = " + name);
	            //Manually create exceptions
	            Integer.parseInt("xxxx");
	            //delete the lock at the end of the operation
	            redisTemplate.delete("k1");
	        }else{
	            System.out.println("A thread is in use, please try later");
	        }
	}
}


test

/
ロックを解放できない例外が発生した最初のスレッド。


その後、すべてのスレッドにアクセスできなくなります。

Solutions : ロックに有効時間を追加する。

1.2: デッドロックを解決するために有効な時間を設定するためにプレースホルダーを使用する。

Placeholder set valid time problem : スレッドに例外が発生しても、プレースホルダーの時間が経過すればロックは解除されます。しかし、多数のスレッドが同時にアクセスする場合、スレッド1が外的要因(ネットワーク変動、サーバートラブルなど)の影響を受け、スレッド1の業務が終了していないのにロックの有効時間が切れた場合、次のスレッドが入り、スレッドインセキュリティが発生し、スレッドが互いのロックを削除し合うことになります。



	@Test
    public void testLock02() {
        ValueOperations valueOperations = redisTemplate.opsForValue();
        // If the key does not exist before you can set the success, set a valid time to prevent thread exceptions deadlock
        Boolean isLock = valueOperations.setIfAbsent("k1", "v1",5, TimeUnit.SECONDS);
        //If the occupancy is successful, proceed to normal operation
        if (isLock){
        	//set a name to store in redis
            valueOperations.set("name","xxxx");
            // Retrieve the name from redis
            String str = (String) valueOperations.get("name");
            System.out.println("name = " + str);
            // Create exception
            Integer.parseInt("xxxx");
            //delete the lock at the end of the operation
            redisTemplate.delete("k1");
        }else{
            System.out.println("A thread is in use, please try later");
        }
    }


solution :

luaスクリプトで、各ロックのキーに対応する値に乱数を設定する。

1.3: lua スクリプトを使用してスレッドセキュリティを解決する。

Redisサーバー上でluaスクリプトを記述することができます。
Advantages : サーバー上で高速に動作する

Disadvantages コードを修正するのが面倒

lua スクリプトは java 経由で送信可能です。
Pros: コードの修正が容易

Disadvantages : リクエストが送信されるたびに、ネットワークリソースを消費する

1.3.1: luaスクリプトの書き方



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

1.3.2: ReidsConfig クラスの修正



	@Bean
    public DefaultRedisScript<Boolean> defaultRedisScript(){
        DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
        //lock.lua script location is the same level as application.yml
        redisScript.setLocation(new ClassPathResource("lock.lua"));
        // set the type to boolean
        redisScript.setResultType(Boolean.class);
        return redisScript;
    }

1.3.3: テストモジュールの作成



@Test
    public void testLock03(){
        ValueOperations valueOperations = redisTemplate.opsForValue();
        String value = UUID.randomUUID().toString();
        // If the key does not exist, set the value to a random number to prevent thread insecurity
        Boolean isLock = valueOperations.setIfAbsent("k1", value, 5, TimeUnit.SECONDS);
        //If the occupation is successful, proceed to normal operation 
        if (isLock){
        	//set a name to store in redis
            valueOperations.set("name","xxxx");
            // Retrieve the name from redis
            String name = (String) valueOperations.get("name");
            System.out.println("name = " + name);
            // send lua script for redis to delete the value corresponding to the lock
            Boolean aBoolean = (Boolean) redisTemplate.execute(redisScript, Collections.singletonList("k1"), value);
            System.out.println(aBoolean);
        }else{
            System.out.println("There are threads in use, please try later");
        }
    }


Test results:

/{br nameの値をredisに格納することに成功し、ロックを解除してtrueを返す

ロックは名前だけを残して正常に削除されます。

RedisTemplatを使った簡単な分散ロックの実装についての記事は以上です。RedisTemplatの分散ロックについては、BinaryDevelopの過去記事を検索するか、引き続き以下の関連記事を参照してください。