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

تحليل Redis Distributed Lock

في الآونة الأخيرة، واجهت احتياجات السيناريو التجاري التالية، حيث يجب إرسال مجموعة من البيانات إلى نظام آخر بشكل يومي، ولكن نظرًا لأن النظام مثبت ك集群، فإنه قد يؤدي إلى تضارب المهام في الحالة العامة، لذا يجب إضافة قفل توزيعي لضمان أن يتم تنفيذ Job المحدد في فترة زمنية معينة. تم النظر في خطة بدء العمل من قبل، مثل استخدام ZooKeeper توزيعي للمهام، وتنظيم المهام التوزيعية Quartz، ولكن نظرًا لوجود مكون إضافي ضروري لـ ZooKeeper، وتحتاج Quartz إلى إضافة جدول، ووجود مكون Redis في المشروع حاليًا، لذا تم النظر في استخدام قفل توزيعي Redis لإكمال وظيفة احتلال المهام التوزيعية هذه

سجلت بعض الطرق التي تسلكتها.

النسخة الأولى:

@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{
		//استخدام الخريطة التخزين
		BoundHashOperations valueOperations = redisTemplate.boundHashOps(key);
		valueOperations.put(key, value);
		//ثوان
		valueOperations.expire(cacheSeconds, TimeUnit.SECONDS);
		}
		return null;
	}
	@Override
	public void del(String key) {
		redisTemplate.delete(key);
	}

يتم استنساخ القفل باستخدام set و del لإعداد الاستيلاء والإطلاق للقفل، وبعد إجراء اختبارات، تبين أن set ليس آمنًا في الحالة المتنافسة، مما يؤدي غالبًا إلى عدم التزامن في البيانات

النسخة الثانية:

/**
   * قفل توزيعي
   * @param range طول القفل، يسمح بعدد من الطلبات للاستيلاء على الموارد
   * @param key
   * @return
   */
  public boolean getLock(int range, String key) {
    ValueOperations<String, Integer> valueOper1 = template.opsForValue();
    return valueOper1.increment(key, 1) <= range;
  }
  /**
   * إعداد القفل، إعداد يساوي 0
   * @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);
  }
  /**
   * إطلاق القفل
   * @param key
   */
  public void releaseLock(String key) {
    ValueOperations<String, Integer> operations = template.opsForValue();
    template.setKeySerializer(new GenericJackson2JsonRedisSerializer());
    template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
    template.delete(key);
  }

يتم استنساخ القفل باستخدام عملية الإضافية في redis. ولكن عند إطلاق القفل، يمكن لكل نواة حذف قيمة المفتاح في redis. وينسخ initLock عملية السابقة، لذا تم إسقاط هذه الطريقة أيضًا

النسخة_النهائية:

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;
  /**
   * @try_الحصول_على_القفل_الموزع
   * @param lockKey lock
   * @param requestId request identifier
   * @param expireTime زمن_الانقضاء
   * @return هل تم الحصول عليها بنجاح
   */
  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);
    إذا (مجرد_مقارنة_القيم_المناسبة(result)) {
      return true;
    }
    return false;
  }
  /**
   * release distributed lock
   * @param lockKey lock
   * @param requestId request identifier
   * @return whether the release is successful
   */
  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;
  }
}
أنت قد تحب