Maison  >  Article  >  base de données  >  Quel est le principe du verrouillage distribué Redis et comment le mettre en œuvre

Quel est le principe du verrouillage distribué Redis et comment le mettre en œuvre

王林
王林avant
2023-05-27 16:16:283368parcourir

1 Problème de sécurité de concurrence Une personne, une commande

Le verrou pessimiste utilisé dans l'activité précédente Une personne, une commande ne peut pas prendre effet dans un système distribué.

Dans le meilleur des cas, si un thread parvient à acquérir le mutex, à interroger et à créer la commande, les autres threads ne pourront pas interférer. Le principe est qu'il y aura un moniteur de verrouillage pour surveiller qui a obtenu le verrouillage.

Quel est le principe du verrouillage distribué Redis et comment le mettre en œuvre

Mais le problème se pose :

Dans un système distribué, il existe plusieurs JVM différentes. Dans différents environnements JVM, il existe plusieurs écouteurs de verrouillage, et certains threads apparaîtront dans d'autres. Lorsque le thread a déjà obtenu le verrou, il peut toujours obtenir le verrou.

Quel est le principe du verrouillage distribué Redis et comment le mettre en œuvre

À l'heure actuelle, les verrous de la JVM ordinaire ne fonctionnent plus, nous devons donc utiliser des verrous distribués.

2 Le principe et la mise en œuvre des verrous distribués

2.1 Qu'est-ce qu'un verrou distribué

C'est un verrou qui peut rendre plusieurs processus visibles et mutuellement exclusifs dans un système distribué ou en mode cluster.

Son principe de mise en œuvre est que différents environnements JVM partagent un moniteur de verrouillage. Cela ne conduira pas à plusieurs threads utilisant plusieurs verrous.

Quel est le principe du verrouillage distribué Redis et comment le mettre en œuvre

Caractéristiques :

Quel est le principe du verrouillage distribué Redis et comment le mettre en œuvre

2.2 Implémentation du verrouillage distribué

Il existe principalement trois méthodes d'implémentation, nous pouvons toutes les comparer.

Comme indiqué ci-dessous :

Quel est le principe du verrouillage distribué Redis et comment le mettre en œuvre

Ici, nous parlons principalement de la mise en œuvre de verrous distribués basés sur Redis.

La méthode d'implémentation du verrou distribué Reids implique principalement les deux étapes suivantes :

1 Obtention du verrou

Utiliser la méthode setnx de type String dans Redis (garantissant l'exclusivité mutuelle) est déjà un moyen bien connu pour obtenir le verrou. verrouillage. Afin d'éviter que le serveur Redis ne plante, nous devons définir un délai d'attente pour le verrouillage afin d'éviter un blocage. (Non bloquant)

Ainsi, le code suivant peut être utilisé pour obtenir le verrou

SET lock thread1 nx ex 10

lock est la clé du verrou, thread1 est la valeur, nx est la méthode setnx, et par exemple, il faut définir le délai d'attente

2. Libérez le verrou

Libérer le verrou est simple, supprimez-le simplement.

del lock

Implémentation du code :

Exigences : Définir une interface et utiliser Redis pour implémenter la fonction de verrouillage distribué.

Quel est le principe du verrouillage distribué Redis et comment le mettre en œuvre

Le code est le suivant :

Code d'interface :

package com.hmdp.utils;
public interface ILock {
    /**
     * 尝试获取锁
     * @param timeoutSec 锁的持有时间,过期自动释放
     * @return true代表获取锁成功,false代表获取锁失败。
     */
    boolean tryLock(long timeoutSec);
    /**
     * 释放锁
     */
    void unlock();
}

Classe d'implémentation d'interface :

package com.hmdp.utils;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.concurrent.TimeUnit;
/**
 * @Version 1.0
 */
public class SimpleRedisLock implements ILock {
    //Redis
    private StringRedisTemplate stringRedisTemplate;
    //业务名称,也就是锁的名称
    private String name;
    public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.name = name;
    }
    //key的前缀
    private static final String KEY_PREFIX = "lock:";
    @Override
    public boolean tryLock(long timeoutSec) {
        //获取线程id,当作set的value
        long threadId = Thread.currentThread().getId();
        Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(KEY_PREFIX + name, threadId+"", timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success);
    }
    //释放锁
    @Override
    public void unlock() {
        //删除key
        stringRedisTemplate.delete(KEY_PREFIX+name);
    }
}

La couche commerciale acquiert les verrous et libère les verrous (modification commerciale de la vente flash du coupon)

package com.hmdp.service.impl;
import com.hmdp.dto.Result;
import com.hmdp.entity.SeckillVoucher;
import com.hmdp.entity.VoucherOrder;
import com.hmdp.mapper.VoucherOrderMapper;
import com.hmdp.service.ISeckillVoucherService;
import com.hmdp.service.IVoucherOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisIdWorker;
import com.hmdp.utils.SimpleRedisLock;
import com.hmdp.utils.UserHolder;
import org.springframework.aop.framework.AopContext;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.time.LocalDateTime;
/**
 * <p>
 *  服务实现类
 * </p>
 *
 */
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
 
    @Resource
    private ISeckillVoucherService iSeckillVoucherService;
    @Resource
    private RedisIdWorker redisIdWorker;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public Result seckillVoucher(Long voucherId) {
        //1.获取优惠券信息
        SeckillVoucher voucher = iSeckillVoucherService.getById(voucherId);
        //2.判断是否已经开始
        if (voucher.getBeginTime().isAfter(LocalDateTime.now())){
            Result.fail("秒杀尚未开始!");
        }
        //3.判断是否已经结束
        if (voucher.getEndTime().isBefore(LocalDateTime.now())){
            Result.fail("秒杀已经结束了!");
        }
        //4.判断库存是否充足
        if (voucher.getStock() < 1) {
            Result.fail("库存不充足!");
        }
        //5.扣减库存
        boolean success = iSeckillVoucherService.update()
                .setSql("stock = stock-1").eq("voucher_id",voucherId).gt("stock",0)
                .update();
        if (!success){
            Result.fail("库存不充足!");
        }
        Long userId = UserHolder.getUser().getId();
        //1.创建锁对象
        SimpleRedisLock lock = new SimpleRedisLock(stringRedisTemplate, "order:" + userId);
        //2.尝试获取锁
        boolean isLock = lock.tryLock(1200);
        if (!isLock){
            //获取锁失败
            return Result.fail("一个用户只能下一单!");
        }
        try {
            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            return proxy.createVoucherOrder(voucherId);
        } finally {
            //释放锁
            lock.unlock();
        }
    }
    @Transactional
    public Result createVoucherOrder(Long voucherId) {
        Long userId = UserHolder.getUser().getId();
        //6.根据优惠券id和用户id判断订单是否已经存在
        //如果存在,则返回错误信息
        int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
        if (count > 0) {
            return Result.fail("用户已经购买!");
        }
        //7. 创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        //7.1添加订单id
        Long orderId = redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);
        //7.2添加用户id
        voucherOrder.setUserId(userId);
        //7.3添加优惠券id
        voucherOrder.setVoucherId(voucherId);
        save(voucherOrder);
        //8.返回订单id
        return Result.ok(orderId);
    }
}

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:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer