当前位置:网站首页>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
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";
}
}
边栏推荐
- TeaTalk·Online 演讲实录 | 圆满完结!安全上云,选对数据迁移策略很重要
- pip2pi和pypiserver及Apache在pip本地源配置中的应用实践
- Adobe Premiere foundation - batch material import sequence - variable speed and rewind (recall) - continuous action shot switching - subtitle requirements (13)
- How can non-standard automation equipment manufacturing enterprises achieve rapid and accurate quotation with the help of ERP system?
- [arcgis] City relevance analysis
- Development of smart contract DAPP system for TRX wave field chain
- From quic to TCP
- 【bug解决】上传图片后,取消这次上传 再次执行上传,上次的图片还存在
- 關於分布式鎖的續命問題——基於Redis實現的分布式鎖
- tf.data(二) —— 并行化 tf.data.Dataset 生成器
猜你喜欢

31w赛题奖金!当 AI for Science 撞上“先导杯”,会擦出什么样的火花?

openharmony标准系统之app手动签名

Venue floor efficiency is so low? The key lies in these two aspects

關於分布式鎖的續命問題——基於Redis實現的分布式鎖

Log management system, summary in multiple ways

Oracle database import data steps

火山引擎云数据库 veDB 在字节内部的业务实践

How can non-standard automation equipment manufacturing enterprises achieve rapid and accurate quotation with the help of ERP system?

Netstat command details

两件小事,感受到了和大神的差距
随机推荐
4K投影仪哪款性价比最高,当贝X3 Pro高亮128G存储618值得看
美容院管理系统如何解决门店运营的三大难题?
Syntax of SQL
Dbutil auxiliary class, manual commit transaction, metadata
常用字体介绍
From quic to TCP
What are the advantages of comprehensive venues?
pip2pi和pypiserver及Apache在pip本地源配置中的应用实践
启封easy QF PDA帮助企业提升ERP的管理水平
UI inspiration analysis Notes 6: feature
马斯克称自己不喜欢做CEO,更想做技术和设计;吴恩达的《机器学习》课程即将关闭注册|极客头条...
一个时代的终结!十年了吴恩达经典《机器学习》课程本月关闭注册,上线新课!...
[background interaction] select to bind the data transferred in the background
Node creates a template file with the art template template template engine
I am a graduating doctor majoring in mathematics. How should I choose an offer?
C language - data storage
为什么现在的会员制仓储店都集体爆发了?
[clearos] install the clearos system
【bug解决】上传图片后,取消这次上传 再次执行上传,上次的图片还存在
经营体育馆有哪些要素?