ホームページ  >  記事  >  データベース  >  Redis 分散ロックの原理とその実装方法は何ですか?

Redis 分散ロックの原理とその実装方法は何ですか?

王林
王林転載
2023-05-27 16:16:283314ブラウズ

1 1 人 1 注文の同時実行のセキュリティ問題

以前の 1 人 1 注文のビジネスで使用されていた悲観的ロックは、分散システムでは有効になりません。

最も理想的なケースでは、1 つのスレッドがミューテックス ロックを正常に取得してクエリを実行し、順序を作成できれば、他のスレッドは干渉できなくなります。原則として、誰がロックを取得したかを監視するロック モニターが存在します。

Redis 分散ロックの原理とその実装方法は何ですか?

しかし問題が発生します:

分散システムでは、複数の異なる JVM が存在します。異なる JVM 環境では、ロック リスナーはスレッドによっては、他のスレッドがすでにロックを取得している場合でも、一部のスレッドは引き続きロックを取得できます。

Redis 分散ロックの原理とその実装方法は何ですか?

現時点では、通常の JVM のロックは機能しなくなっているため、分散ロックを使用する必要があります。

2 分散ロックの原理と実装

2.1 分散ロックとは

複数のプロセスの可視性と相互排除の要件を満たすことができるロックです。分散システムまたはクラスター モード。

その実装原理は、異なる JVM 環境がロック モニターを共有することです。これにより、複数のスレッドが複数のロックを使用することがなくなります。

Redis 分散ロックの原理とその実装方法は何ですか?

特徴:

Redis 分散ロックの原理とその実装方法は何ですか?

2.2 分散ロックの実装

主な実装方法は 3 つあります。私たちは皆、比較することができます。

以下に示すように:

Redis 分散ロックの原理とその実装方法は何ですか?

ここでは主に、Redis に基づく分散ロックの実装について説明します。

Reid 分散ロックの実装方法は、主に次の 2 つのステップで構成されます:

1. ロックを取得します

Redis の String 型の setnx メソッドを使用します (相互排他性を保証します)。 ) これは、ロックを取得するためのよく知られた方法です。 Redis サーバーのクラッシュを防ぐには、デッドロックを回避するためにロックのタイムアウトを設定する必要があります。 (ノンブロッキング)

したがって、次のコードを使用してロックを取得できます

SET lock thread1 nx ex 10

lock はロックのキー、thread1 は値、nx は setnx メソッド、ex はタイムアウトの設定です

2. ロックを解放します

ロックを解放するのは簡単で、削除するだけです。 。

del lock

コードの実装:

要件: インターフェイスを定義し、Redis を使用して分散ロック機能を実装します。

Redis 分散ロックの原理とその実装方法は何ですか?

コードは次のとおりです:

インターフェイス コード:

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

インターフェイス実装クラス:

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

ビジネス層がロックを取得し、ロックを解除する(クーポンフラッシュセール業務変更)

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

以上がRedis 分散ロックの原理とその実装方法は何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。