English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Analysis of Redis distributed lock

In letzter Zeit bin ich auf eine Geschäftsszenario gestoßen, das täglich eine Reihe von Daten an ein anderes System senden muss. Da das System jedoch in Cluster-Deployment implementiert ist, kann es zu Konflikten bei der Aufgabenverteilung in der gleichen Situation kommen. Daher ist es erforderlich, eine Distributed Lock hinzuzufügen, um sicherzustellen, dass innerhalb eines bestimmten Zeitraums ein Job die Zeitgesteuerte Aufgabe durchführt. Frühere Überlegungen umfassten die Verwendung von ZooKeeper Distributed Tasks und Quartz Distributed Task Scheduling, aber da ZooKeeper zusätzliche Komponenten erfordert und Quartz eine Tabelle hinzufügen muss, und bereits im Projekt existiert Redis als Komponente, wurde erwogen, Redis Distributed Lock zu verwenden, um die Funktion der Distributed Task Grabbing durchzuführen

Notieren Sie sich den Weg, den ich gegangen bin.

Erste Version:

@Override
	public <T> Long set(String key,T value, Long cacheSeconds) {
		if (value instanceof HashMap) {
			BoundHashOperations valueOperations = redisTemplate.boundHashOps(key);
			valueOperations.putAll((Map) value);
			valueOperations.expire(cacheSeconds, TimeUnit.SECONDS);
		}
		else{
		//Verwenden Sie map zum Speichern
		BoundHashOperations valueOperations = redisTemplate.boundHashOps(key);
		valueOperations.put(key, value);
		//Sekunden
		valueOperations.expire(cacheSeconds, TimeUnit.SECONDS);
		}
		return null;
	}
	@Override
	public void del(String key) {
		redisTemplate.delete(key);
	}

Die Besitznahme und Freigabe des Locks durch 'set' und 'del' wird verwendet, aber nach Tests wurde festgestellt, dass 'set' nicht thread-sicher ist und in parallelen Situationen oft zu inkonsistenten Daten führt.

Zweite Version:

/**
   * Distributed Lock
   * @param range Die Länge des Locks, wie viele Anfragen den Ressourcenbesitz plündern dürfen
   * @param key
   * @return
   */
  public boolean getLock(int range, String key) {
    ValueOperations<String, Integer> valueOper1 = template.opsForValue();
    return valueOper1.increment(key, 1) <= range;
  }
  /**
   * Das Lock initialisieren, den Wert auf 0 setzen
   * @param key
   * @param expireSeconds
   * @return
   */
  public void initLock(String key, Long expireSeconds) {
    ValueOperations<String, Integer> operations = template.opsForValue();
    template.setKeySerializer(new GenericJackson2JsonRedisSerializer());
    template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
    operations.set(key, 0, expireSeconds * 1000);
  }
  /**
   * Lock freigeben
   * @param key
   */
  public void releaseLock(String key) {
    ValueOperations<String, Integer> operations = template.opsForValue();
    template.setKeySerializer(new GenericJackson2JsonRedisSerializer());
    template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
    template.delete(key);
  }

Der Besitz des Locks wird durch die Operation 'increament' von Redis realisiert. Beim Freigeben des Locks kann jeder Thread den Wert des Keys in Redis löschen. Und die Methode 'initLock' überschreibt den letzten Vorgang, daher wird diese Methode ebenfalls abgelehnt.

Endgültige Version:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnection;
import org.springframework.stereotype.Service;
import org.springframework.util.ReflectionUtils;
import redis.clients.jedis.Jedis;
import java.lang.reflect.Field;
import java.util.Collections;
@Service
public class RedisLock {
  private static final String LOCK_SUCCESS = "OK";
  private static final String SET_IF_NOT_EXIST = "NX";
  private static final String SET_WITH_EXPIRE_TIME = "PX";
  private static final Long RELEASE_SUCCESS = 1L;
  @Autowired
  private RedisConnectionFactory connectionFactory;
  /**
   * Versuchen, einen verteilten Lock zu erlangen
   * @param lockKey 锁
   * @param requestId 请求标识
   * @param expireTime Zeit des Ablaufs
   * @return Erfolgreich erlangt?
   */
  public boolean lock(String lockKey, String requestId, int expireTime) {
    Field jedisField = ReflectionUtils.findField(JedisConnection.class, "jedis");
    ReflectionUtils.makeAccessible(jedisField);
    Jedis jedis = (Jedis) ReflectionUtils.getField(jedisField, connectionFactory.getConnection());
    String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
    if (LOCK_SUCCESS.equals(result)) {
      return true;
    }
    return false;
  }
  /**
   * 释放分布式锁
   * @param lockKey 锁
   * @param requestId 请求标识
   * @return 是否释放成功
   */
  public boolean releaseLock(String lockKey, String requestId) {
    String script = "if redis.call('get', KEYS[1]) == ARGV[1]) then return redis.call('del', KEYS[1]) else return 0 end";
    Object result = getJedis().eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
    if (RELEASE_SUCCESS.equals(result)) {
      return true;
    }
    return false;
  }
  public Jedis getJedis() {
    Field jedisField = ReflectionUtils.findField(JedisConnection.class, "jedis");
    ReflectionUtils.makeAccessible(jedisField);
    Jedis jedis = (Jedis) ReflectionUtils.getField(jedisField, connectionFactory.getConnection());
    return jedis;
  }
}
You may also like