Cet article vous présentera comment utiliser Redis pour implémenter un verrou distribué sûr et fiable, et expliquera les principaux éléments et les malentendus courants concernant la mise en œuvre du verrou distribué. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer. J'espère qu'il sera utile à tout le monde.
Dans un scénario simultané, lorsque plusieurs processus ou threads partagent des ressources de lecture et d'écriture, l'accès aux ressources doit être garanti pour s'exclure mutuellement. Dans un système autonome, nous pouvons utiliser l'API du package de concurrence Java, le mot-clé synchronisé, etc. pour résoudre le problème, mais dans un système distribué, ces méthodes ne sont plus applicables et nous devons implémenter nous-mêmes les verrous distribués ; .
Les solutions courantes de mise en œuvre de verrous distribués incluent : basées sur une base de données, basées sur Redis, basées sur Zookeeper, etc. Dans le cadre du sujet Redis, cet article parlera de la mise en œuvre de verrous distribués basés sur Redis. [Recommandations associées : Tutoriel vidéo Redis]
Les verrous distribués et les verrous intégrés JVM ont le même objectif : permettre aux applications d'accéder ou de faire fonctionner des ressources partagées dans l'ordre prévu, et d'empêcher plusieurs threads de fonctionner sur la même ressource en même temps, provoquant le système fonctionne de manière désordonnée et incontrôlable. Souvent utilisé dans des scénarios tels que les déductions sur les stocks de produits et les déductions sur les coupons.
Théoriquement, afin de garantir la sécurité et l'efficacité de la serrure, les serrures distribuées doivent remplir au moins les conditions suivantes :
En termes de mise en œuvre, les verrous distribués sont grossièrement divisés en trois étapes :
SET key value NX PX milliseconds
Les scripts Lua et les commandes DEL fournissent un support fiable pour un déverrouillage en toute sécurité. <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>${your-spring-boot-version}</version> </dependency>
spring.redis.database=0 spring.redis.host=localhost spring.redis.port=6379
@Configuration public class RedisConfig { // 自己定义了一个 RedisTemplate @Bean @SuppressWarnings("all") public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) throws UnknownHostException { // 我们为了自己开发方便,一般直接使用 <String, Object> RedisTemplate<String, Object> template = new RedisTemplate<String, Object>(); template.setConnectionFactory(factory); // Json序列化配置 Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); // String 的序列化 StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); // key采用String的序列化方式 template.setKeySerializer(stringRedisSerializer); // hash的key也采用String的序列化方式 template.setHashKeySerializer(stringRedisSerializer); // value序列化方式采用jackson template.setValueSerializer(jackson2JsonRedisSerializer); // hash的value序列化方式采用jackson template.setHashValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; } }
@Service public class RedisLock { @Resource private RedisTemplate<String, Object> redisTemplate; /** * 加锁,最多等待maxWait毫秒 * * @param lockKey 锁定key * @param lockValue 锁定value * @param timeout 锁定时长(毫秒) * @param maxWait 加锁等待时间(毫秒) * @return true-成功,false-失败 */ public boolean tryAcquire(String lockKey, String lockValue, int timeout, long maxWait) { long start = System.currentTimeMillis(); while (true) { // 尝试加锁 Boolean ret = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, timeout, TimeUnit.MILLISECONDS); if (!ObjectUtils.isEmpty(ret) && ret) { return true; } // 计算已经等待的时间 long now = System.currentTimeMillis(); if (now - start > maxWait) { return false; } try { Thread.sleep(200); } catch (Exception ex) { return false; } } } /** * 释放锁 * * @param lockKey 锁定key * @param lockValue 锁定value * @return true-成功,false-失败 */ public boolean releaseLock(String lockKey, String lockValue) { // lua脚本 String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end"; DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class); Long result = redisTemplate.opsForValue().getOperations().execute(redisScript, Collections.singletonList(lockKey), lockValue); return result != null && result > 0L; } }
@SpringBootTest class RedisDistLockDemoApplicationTests { @Resource private RedisLock redisLock; @Test public void testLock() { redisLock.tryAcquire("abcd", "abcd", 5 * 60 * 1000, 5 * 1000); redisLock.releaseLock("abcd", "abcd"); } }
set
pour verrouiller, garantissant l'exclusion mutuelle des verrous et évitant les blocages ; NX
PX
Utilisez le script Lua pour déverrouiller afin d'empêcher le déverrouillage d'autres threads appendfsync=always
Mais il peut y avoir des problèmes en mode sentinelle et en mode cluster, pourquoi ?
Le mode Sentinelle et le mode cluster sont basés sur l'architecture maître-esclave. La synchronisation des données est réalisée entre le maître et l'esclave via la propagation des commandes, et la propagation des commandes est asynchrone.
Il est donc possible que les données du nœud maître soient écrites avec succès, mais que le nœud maître tombe en panne avant que le nœud esclave ne soit notifié.
Lorsque le nœud esclave est promu au nouveau nœud maître via le basculement, les autres threads ont la possibilité de se reverrouiller avec succès, ce qui fait que la condition d'exclusion mutuelle du verrou distribué n'est pas remplie.
RedLock officiel
La synchronisation maître-esclave étant basée sur le principe de la réplication asynchrone, le mode sentinelle et le mode cluster sont intrinsèquement incapables de remplir cette condition. Pour cette raison, l'auteur de Redis a spécialement proposé une solution-RedLock (Redis Distribute Lock).
Parlons d'abord des exigences de l'environnement. N (N>=3) instances Redis déployées indépendamment sont requises. La réplication maître-esclave, le basculement et d'autres technologies ne sont pas requis entre eux.
Afin d'obtenir le cadenas, le client suivra le processus suivant :
RedLock的设计思路延续了Redis内部多种场景的投票方案,通过多个实例分别加锁解决竞态问题,虽然加锁消耗了时间,但是消除了主从机制下的安全问题。
官方推荐Java实现为Redisson,它具备可重入特性,按照RedLock进行实现,支持独立实例模式、集群模式、主从模式、哨兵模式等;API比较简单,上手容易。示例如下(直接通过测试用例):
@Test public void testRedLock() throws InterruptedException { Config config = new Config(); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); final RedissonClient client = Redisson.create(config); // 获取锁实例 final RLock lock = client.getLock("test-lock"); // 加锁 lock.lock(60 * 1000, TimeUnit.MILLISECONDS); try { // 假装做些什么事情 Thread.sleep(50 * 1000); } catch (Exception ex) { ex.printStackTrace(); } finally { //解锁 lock.unlock(); } }
Redisson封装的非常好,我们可以像使用Java内置的锁一样去使用,代码简洁的不能再少了。关于Redisson源码的分析,网上有很多文章大家可以找找看。
分布式锁是我们研发过程中常用的的一种解决并发问题的方式,Redis是只是一种实现方式。
关键的是要弄清楚加锁、解锁背后的原理,以及实现分布式锁需要解决的核心问题,同时考虑我们所采用的中间件有什么特性可以支撑。了解这些后,实现起来就不是什么问题了。
学习了RedLock的思想,我们是不是也可以在自己的应用程序内实现了分布式锁呢?欢迎沟通!
更多编程相关知识,请访问:编程入门!!
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!