Maison  >  Article  >  base de données  >  Comment utiliser Redis pour implémenter des verrous distribués

Comment utiliser Redis pour implémenter des verrous distribués

尚
original
2019-07-05 16:45:209308parcourir

Comment utiliser Redis pour implémenter des verrous distribués

Utilisation de Redis pour implémenter des verrous distribués

Introduction aux fonctionnalités de Redis

1. Prend en charge les types de données riches, tels que String, List, Carte, Set, ZSet, etc.

2. Prise en charge de la persistance des données, des méthodes RDB et AOF

3. Prise en charge du mode de fonctionnement du cluster, forte tolérance aux pannes de partition

4. >

5. Prise en charge des transactions

6. Prise en charge de la publication et de l'abonnement

Redis implémente des verrous distribués à l'aide de la commande SETNX :

Valeur de clé SETNX

Définissez la valeur de key sur value si et seulement si key n’existe pas.

Si la clé donnée existe déjà, SETNX n'effectue aucune action.

SETNX est l'abréviation de "SET if Not eXists" (s'il n'existe pas, alors SET).

Versions disponibles : >= 1.0.0 Complexité temporelle : O(1) Valeur de retour :

Défini avec succès, renvoie 1 .

Échec de la définition, renvoyant 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"

Tout d'abord, nous devons encapsuler une classe d'outils d'accès Redis publique. Cette classe doit injecter une instance RedisTemplate et une instance ValueOperations. L'instance ValueOperations est utilisée car le verrou distribué implémenté par Redis utilise le type String le plus simple. De plus, nous devons encapsuler trois méthodes, à savoir setIfObsent (String key, String value), expire (String key, long timeout, TimeUnit unit) et delete (String key), qui correspondent aux commandes SETNX, expire et del. de Redis respectivement. Voici l'implémentation spécifique de la classe d'outils d'accès 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);
	}
}

a terminé l'implémentation de la classe d'outils d'accès Redis. Il faut maintenant considérer comment simuler la concurrence pour les verrous distribués. Étant donné que Redis lui-même prend en charge les clusters distribués, il lui suffit de simuler des scénarios métier de traitement multithread. Le pool de threads est utilisé ici pour la simulation. Voici l'implémentation spécifique de la classe de test :

@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分布式锁失败");
					}
				}
			});
		}
	}
}

Grâce au code ci-dessus, les questions suivantes peuvent se poser :

Si le thread ne parvient pas à obtenir le verrou distribué, Pourquoi ne pas essayer de réacquérir le verrou ?

Une fois que le thread a acquis avec succès le verrou distribué, il définit le délai d'expiration du verrou. Comment déterminer le délai d'expiration ?

Une fois le traitement du fil terminé, pourquoi devez-vous supprimer le verrou ?

Nous pouvons discuter de ces questions.

Premièrement, la commande SETNX de Redis n'effectuera aucune opération si la clé existe déjà, donc le verrou distribué implémenté par SETNX n'est pas un verrou réentrant. Bien entendu, vous pouvez également réessayer n fois via le code ou jusqu'à ce que le verrouillage distribué soit obtenu. Cependant, cela ne garantit pas une concurrence loyale et un fil de discussion sera bloqué car il attend le verrouillage. Par conséquent, le verrouillage distribué implémenté par Redis est plus adapté aux scénarios dans lesquels les ressources partagées sont écrites une fois et lues plusieurs fois.

Deuxièmement, le verrou distribué doit définir un délai d'expiration, et le délai d'expiration doit être supérieur au temps requis pour le traitement métier (afin de garantir la cohérence des données). Par conséquent, pendant la phase de test, le temps requis pour le traitement commercial normal doit être prévu aussi précisément que possible, et le délai d'expiration est défini pour éviter les blocages dus à certaines raisons dans le processus de traitement commercial.

Troisièmement, une fois le traitement commercial terminé, le verrou doit être supprimé.

Le réglage du verrou distribué et le réglage du délai d'expiration du verrou ci-dessus s'effectuent en deux étapes. Une manière plus raisonnable devrait consister à définir le verrou distribué et le délai d'expiration du verrou en une seule opération. Soit ils réussissent tous les deux, soit ils échouent tous les deux. Le code d'implémentation est le suivant :

/**
* 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);
		}
	}
}

Exécutez la classe de test unitaire et les résultats sont les suivants :

2019-05-15 13:07:10.827 - 设置分布式锁成功
2019-05-15 13:07:10.838 - MyDistributeLock距离失效还有19s

Pour plus de connaissances sur Redis, veuillez visiter le

Tutoriel d'utilisation de Redis colonne !

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn