当前位置:网站首页>On the continuing Life of Distributed Locks - - Distributed Locks Based on redis

On the continuing Life of Distributed Locks - - Distributed Locks Based on redis

2022-06-11 13:04:00 Escargots mûrs

Table des matières

Un.、Contexte

2.、Mise en œuvre personnalisée

Trois、RedissonMise en œuvre du cadre


Un.、Contexte

Il n'y a pas grand - chose à dire sur les serrures distribuées,Il y a une scène,Si dans la serrure distribuée,Code d'affaires non exécuté,Puis la clé de la serrure a expiré,Alors les autresJVMIl est possible d'obtenir une serrure et d'avoir un problème de puissance.Comment résoudre cette situation?

1、Si le thread1J'ai la serrure.,Alors, avant que le Code d'affaires ne soit terminé,redisLa valeur de la clé est expirée,Alors il y a un problème d'idémpotence.La solution est:,Lorsque le thread1Après avoir obtenu la serrure,Ouvrez un thread pour écouter le thread1Si l'exécution est terminée,Si l'exécution n'est pas terminée,Pour prolonger l'expiration de la clé.

2、Si, pour une raison quelconque(Comme le Code.bug,Réseau instable),La serrure n'a jamais pu se libérer, Est - ce que vous allez continuer à prolonger l'expiration ?La réponse est non., Nous pouvons définir le nombre de fois où le temps est prolongé , Si le nombre de fois fixé est dépassé ou échoué , Relâchez automatiquement la serrure , Et faire reculer les affaires .

3、 Questions à prendre en considération lors de la libération de la serrure ,Qui a mis la serrure?, Que quelqu'un libère la serrure .

2.、Mise en œuvre personnalisée

Verrouiller la classe d'entité d'information

package com.xiaojie.lock;


import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author xiaojie
 * @version 1.0
 * @description:  Enregistrer les informations de verrouillage 
 * @date 2022/6/9 17:44
 */
public class RedisLockInfo {
    /**
     * État de la serrure statePourtrue Cela signifie que la serrure a été obtenue avec succès
     */
    private boolean state;
    /**
     * Verrouilléid
     */
    private String lockId;
    /**
     * Fil de serrure
     */
    private Thread lockThread;

    /**
     * Date d'expiration de la serrure
     */
    private Long expire;

    /**
     *  Nombre de renouvellements 
     */
    private AtomicInteger lifeCount;

    /**
     *  Obtenir le nombre de serrures 
     */
    private AtomicInteger lockCount;

    //  Nombre de fois où la serrure peut être rentrée 
    public RedisLockInfo(String lockId, Thread lockThread, Long expire) {
        state = true;
        this.lockId = lockId;
        this.lockThread = lockThread;
        this.expire = expire;
        lifeCount = new AtomicInteger(0);
        lockCount = new AtomicInteger(0);
    }

    public RedisLockInfo(Thread lockThread, Long expire) {
        this.lockThread = lockThread;
        this.expire = expire;
        lifeCount = new AtomicInteger(0);
        lockCount = new AtomicInteger(0);
        state = true;
    }

    public RedisLockInfo() {
        state = true;
    }

    public String getLockId() {
        return lockId;
    }

    public boolean isState() {
        return state;
    }

    public Thread getLockThread() {
        return lockThread;
    }

    public Long getExpire() {
        return expire;
    }

    //Nombre de retraits
    public Integer getLifeCount() {
        return lifeCount.incrementAndGet();
    }

    // Nombre de serrures obtenues 
    public Integer incrLockCount() {
        return lockCount.incrementAndGet();
    }

    // Nombre de serrures libérées 
    public Integer decreLockCount() {
        return lockCount.decrementAndGet();
    }
}

Méthode d'acquisition de la serrure et méthode de renouvellement de la vie

package com.xiaojie.lock.impl;

import com.xiaojie.lock.RedisLock;
import com.xiaojie.lock.RedisLockInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @author xiaojie
 * @version 1.0
 * @description:  Classes d'implémentation pour implémenter des serrures distribuées 
 * @date 2022/6/9 18:05
 */
@Component
@Slf4j
public class RedisLockImpl implements RedisLock {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    private String redisLockKey = "redisLockKey";
    /**
     * CacheredisVerrouillage
     */
    private static Map<Thread, RedisLockInfo> lockCacheMap = new ConcurrentHashMap<>();
    /**
     * Retry time
     */
    private Long timeout = 3000L;
    private ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();


    @Override
    public boolean tryLock() {
        Thread currentThread = Thread.currentThread();
        RedisLockInfo redisLockInfo = lockCacheMap.get(currentThread);
        // Déterminer si le thread actuel a obtenu la serrure 
        if (null != redisLockInfo && redisLockInfo.isState()) {
            // Prouver que la serrure a été obtenue ,Verrouillage et rentrée
            log.info(">>>>>>>>>>>>>>>>>La serrure a été obtenue");
            redisLockInfo.incrLockCount(); // Le nombre de fois où la serrure a été obtenue plus 1
            return true;
        }
        Long startTime = System.currentTimeMillis();
        Long lockExpire = 30l; // Date d'expiration de la valeur clé 
        //Retry get Lock
        for (; ; ) {
            Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(redisLockKey, "1", lockExpire, TimeUnit.SECONDS);
            if (lock) {
                //Obtenir la serrure avec succès
                log.info(">>>>>>>>>>>>>>Obtenir la serrure avec succès");
                lockCacheMap.put(currentThread, new RedisLockInfo(currentThread, lockExpire));
                return true;
            }
            //  Contrôle d'un délai 
            Long endTime = System.currentTimeMillis();
            if (endTime - startTime > timeout) {
                log.info("In3 Le temps de retry s'est écoulé en quelques secondes >>>>>>>>>>>");
                return false;
            }
            // Continuez le cycle
            try {
                Thread.sleep(10); // Hibernation Évitez CPUEn haut.
            } catch (Exception e) {

            }
        }
    }

    @Override
    public boolean releaseLock() {
        Thread currentThread = Thread.currentThread();
        RedisLockInfo redisLockInfo = lockCacheMap.get(currentThread);
        if (null != redisLockInfo && redisLockInfo.isState()) {
            if (redisLockInfo.decreLockCount() <= 0) {
                lockCacheMap.remove(currentThread);
                //Supprimer la valeur de la clé
                stringRedisTemplate.delete(redisLockKey);
                return true;
            }
        }
        return false;
    }

    public RedisLockImpl() {
        // Début de la tâche programmée pour atteindre le renouvellement ,Chaque3 Une seconde pour la vie 
        this.scheduledExecutorService.scheduleAtFixedRate(new LifeExtensionThread(), 0, 3, TimeUnit.SECONDS);
    }

    // Définir le fil d'écoute 
    class LifeExtensionThread implements Runnable {
        @Override
        public void run() {
            log.info(" Démarrer le fil de vie >>>>>>>>>>>>>>>>>>>>>>>>>");
            if (lockCacheMap.size() > 0) {
                lockCacheMap.forEach((k, lockInfo) -> {
                    try {
                        Thread lockServiceThread = lockInfo.getLockThread();
                        if (lockServiceThread.isInterrupted()) {
                            // Déterminer si le thread est terminé 
                            log.info(" Le thread actuel a été arrêté >>>>>>>>>>>>>>>");
                            lockCacheMap.remove(k); // Supprimer le thread du cache 
                            return;
                        }
                        //  Durée de vie de la clé expirée 
                        Integer lifeCount = lockInfo.getLifeCount();
                        if (lifeCount > 5) {
                            log.info(">>>>>>>>>>>>>>>>> Vous avez survécu plusieurs fois et le thread actuel n'a pas encore libéré la serrure , Maintenant, prenez l'initiative de libérer la serrure   Évitez les problèmes d'impasse ");
                            // 1. ROLLBACK Business ( Par exemple, un ROLLBACK de transaction )
                            // 2.Relâchez la serrure.
                            releaseLock();
                            // 3. Arrêtez activement le thread 
                            lockServiceThread.interrupt();
                            // 4.Supprimer l'écoute
                            lockCacheMap.remove(k);
                            return;
                        }
                        // Réaliser le renouvellement à l'avance   Prolongation de l'expiration keyLe temps
                        stringRedisTemplate.expire(redisLockKey, lockInfo.getExpire(), TimeUnit.SECONDS);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                });
            }
        }
    }
}

Effets analogiques utilisant Jemeter Simuler les affaires de shooting

package com.xiaojie.controller;

import com.xiaojie.entity.Goods;
import com.xiaojie.lock.RedisLock;
import com.xiaojie.mapper.GoodsMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author xiaojie
 * @version 1.0
 * @description:  L'interface de second kill 
 * @date 2022/6/9 22:55
 */
@RestController
public class SeckillController {
    @Autowired
    private GoodsMapper goodsMapper;
    @Autowired
    private RedisLock redisLock;

    @GetMapping("/secKill")
    public String secKill(Long id) {
        try {
            if (!redisLock.tryLock()) {
                //Impossible d'obtenir la serrure
                return " L'activité est trop intense ,Veuillez réessayer plus tard.";
            }
            Goods goods = goodsMapper.selectById(id);
            if (null == goods) {
                return " Il n'y a pas d'activité de second kill pour cet article ";
            }
            if (goods.getStock() <= 0) {
                return " L'inventaire des marchandises est vide .....";
            }
            //Moins d'inventaire
            Integer result = goodsMapper.deceGoods(id);
            return result > 0 ? "Le second a réussi." : "Échec de second Kill";
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //Relâchez la serrure.
            redisLock.releaseLock();
        }
        return "fail";
    }
}

Trois、RedissonMise en œuvre du cadre

Source de l'image   https://blog.csdn.net/menxu_work/article/details/123827526  

1、RedissonVersredis N'est plus utilisé lors de l'écriture des valeurs clés setNxOrdre de,Mais utiliserLua Écrire sous forme de script

2、 Après avoir écrit avec succès la valeur de la clé, un thread d'arrière - plan est défini ( Fils de chien de garde ) Pour prolonger la durée de vie de la clé , La valeur par défaut est par 10 Une seconde pour la vie , La durée de vie est 30Secondes

3、 Le type de valeur clé est hashType,Comme le montre la figure ci - dessous:,Parmi eux1 Est le nombre de serrures

 4、Quand vous relâchez la serrure,Aussi parLuaComment le script fonctionne, Lorsque le nombre de serrures est inférieur à 0Heure, Supprime la valeur de la clé .

Analyse des sources

Code de verrouillage

 private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
        long threadId = Thread.currentThread().getId();
        Long ttl = tryAcquire(-1, leaseTime, unit, threadId);
        // lock acquired
        if (ttl == null) {
    //S'il n'y a pas de délai d'expiration, Prouver que la valeur de la clé n'existe pas ,Retour direct
            return;
        }

        RFuture<RedissonLockEntry> future = subscribe(threadId);
        if (interruptibly) {
            commandExecutor.syncSubscriptionInterrupted(future);
        } else {
            commandExecutor.syncSubscription(future);
        }

//La valeur clé existe, Spin pour obtenir la serrure 
        try {
            while (true) {
                ttl = tryAcquire(-1, leaseTime, unit, threadId);
                // lock acquired
                if (ttl == null) {
                    break;
                }

                // waiting for message
                if (ttl >= 0) {
                    try {
                        future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                    } catch (InterruptedException e) {
                        if (interruptibly) {
                            throw e;
                        }
                        future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                    }
                } else {
                    if (interruptibly) {
                        future.getNow().getLatch().acquire();
                    } else {
                        future.getNow().getLatch().acquireUninterruptibly();
                    }
                }
            }
        } finally {
//Désabonnement
            unsubscribe(future, threadId);
        }
//        get(lockAsync(leaseTime, unit));
    }


  <T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        return evalWriteAsync(getRawName(), LongCodec.INSTANCE, command,
                "if (redis.call('exists', KEYS[1]) == 0) then " + //Déterminer si la valeur de la clé existe,==0N'existe pas
                        "redis.call('hincrby', KEYS[1], ARGV[2], 1); " + // Créer une clé de type hachage ,Et définissez la valeur à1
                        "redis.call('pexpire', KEYS[1], ARGV[1]); " +//Définir le délai d'expiration ,Par défaut30s
                        "return nil; " +
                        "end; " +
                        "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +//La valeur clé existe
                        "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +// Valeur de la clé valueValeur plus1
                        "redis.call('pexpire', KEYS[1], ARGV[1]); " + //Définir l'expiration à30s
                        "return nil; " +
                        "end; " +
                        "return redis.call('pttl', KEYS[1]);", //La valeur clé existe, Et ce n'est pas un retour à la serrure , Renvoie la durée de vie restante de la clé 
                Collections.singletonList(getRawName()), unit.toMillis(leaseTime), getLockName(threadId));
    }

Code pour libérer la serrure

protected RFuture<Boolean> unlockInnerAsync(long threadId) {
        return evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " + // Si la valeur de la clé n'existe pas, la serrure a été libérée 
                        "return nil;" +
                        "end; " +
                        "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " + // Il y en a presque , Alors la valeur de la clé diminue 1
                        "if (counter > 0) then " +
                        "redis.call('pexpire', KEYS[1], ARGV[2]); " + //Si moins1 La valeur de la clé arrière existe toujours ,Réinitialiser l'expiration
                        "return 0; " +
                        "else " +
                        "redis.call('del', KEYS[1]); " + //Supprimer la valeur de la clé
                        "redis.call('publish', KEYS[2], ARGV[1]); " +
                        "return 1; " +
                        "end; " +
                        "return nil;",
                Arrays.asList(getRawName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));
    }

Code de vie

  Mise en œuvre du Code de simulation de second kill

package com.xiaojie.redisson;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author xiaojie
 * @version 1.0
 * @description: reddissonConfiguration
 * @date 2022/6/10 2:05
 */
@Configuration
public class RedissonConfig {

    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private String port;
    @Value("${spring.redis.password}")
    private String password;

    @Bean
    public RedissonClient getRedisson() {
        Config config = new Config();
        // Réglez l'heure du chien de garde 
//        config.setLockWatchdogTimeout(30000L);
        // Définir la version autonome redis
        config.useSingleServer().setAddress("redis://" + host + ":" + port);
        //Définir le mot de passe
        config.useSingleServer().setPassword(password);
        // Comment configurer un Cluster 
//        config.useClusterServers().addNodeAddress("redis://" + host + ":" + port);
//        config.useClusterServers().addNodeAddress("redis://" + host2 + ":" + port2);
        // Ajouter une configuration maître - esclave 
//      config.useMasterSlaveServers().setMasterAddress("").setPassword("").addSlaveAddress(new String[]{"",""});
        return Redisson.create(config);
    }

}

L'interface de second kill

package com.xiaojie.controller;

import com.xiaojie.entity.Goods;
import com.xiaojie.lock.RedisLock;
import com.xiaojie.mapper.GoodsMapper;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author xiaojie
 * @version 1.0
 * @description:  L'interface de second kill 
 * @date 2022/6/9 22:55
 */
@RestController
public class RedissonSeckillController {
    @Autowired
    private GoodsMapper goodsMapper;
    @Autowired
    private RedissonClient redissonClient;

    @GetMapping("/secKillRedisson")
    public String secKillRedisson(Long id) {
        RLock rLock = null;
        try {
            rLock = redissonClient.getLock(id + "");
            rLock.lock(); //Verrouillage, Ajouter quelques serrures ,finally La serrure est libérée plusieurs fois 
            Goods goods = goodsMapper.selectById(id);
            if (null == goods) {
                return " Il n'y a pas d'activité de second kill pour cet article ";
            }
            if (goods.getStock() <= 0) {
                return " L'inventaire des marchandises est vide .....";
            }
            //Moins d'inventaire
            Integer result = goodsMapper.deceGoods(id);
            return result > 0 ? "Le second a réussi." : "Échec de second Kill";
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rLock.unlock();
        }
        return "fail";
    }
}

原网站

版权声明
本文为[Escargots mûrs]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/162/202206111250521946.html