>  기사  >  데이터 베이스  >  Redis 분산 잠금의 원리와 구현 방법

Redis 분산 잠금의 원리와 구현 방법

王林
王林앞으로
2023-05-27 16:16:283314검색

1 1인 1주문 동시성 보안 문제

기존 1인 1주문 비즈니스에서 사용된 비관적 잠금은 분산 시스템에서는 적용되지 않습니다.

최선의 시나리오에서는 하나의 스레드가 성공적으로 뮤텍스를 획득하고 쿼리하고 순서를 생성할 수 있으면 다른 스레드가 방해할 수 없습니다. 원칙은 잠금을 획득한 사람을 모니터링하는 잠금 모니터가 있다는 것입니다.

Redis 분산 잠금의 원리와 구현 방법

그러나 문제가 발생합니다.

분산 시스템에는 여러 개의 서로 다른 JVM이 있으며, 서로 다른 JVM 환경에는 여러 개의 잠금 리스너가 있으며, 스레드가 이미 잠금을 획득한 경우 일부 스레드가 다른 곳에 나타납니다. 여전히 잠금을 얻을 수 있습니다.

Redis 분산 잠금의 원리와 구현 방법

이때 일반 JVM의 잠금은 더 이상 작동하지 않으므로 분산 잠금을 사용해야 합니다.

2 분산 잠금의 원리 및 구현

2.1 분산 잠금이란 무엇입니까

분산 시스템 또는 클러스터 모드에서 여러 프로세스를 표시하고 상호 배타적으로 만들 수 있는 잠금입니다.

구현 원칙은 다양한 JVM 환경이 잠금 모니터를 공유한다는 것입니다. 이로 인해 다중 잠금을 사용하는 다중 스레드가 발생하지 않습니다.

Redis 분산 잠금의 원리와 구현 방법

특징:

Redis 분산 잠금의 원리와 구현 방법

2.2 분산 잠금 구현

주로 세 가지 구현 방법이 있으며 모두 비교할 수 있습니다.

아래와 같이:

Redis 분산 잠금의 원리와 구현 방법

여기에서는 주로 Redis 기반의 분산 잠금 구현에 대해 설명합니다.

Reids 분산 잠금을 구현하는 방법은 주로 다음 두 단계로 구성됩니다.

1. 잠금 획득

Redis에서 String 유형의 setnx 메서드(상호 배타성 보장)를 사용하는 것은 이미 잘 알려진 방법입니다. 잠그다. Redis 서버 충돌을 방지하려면 교착 상태를 방지하기 위해 잠금 시간 초과를 설정해야 합니다. (비차단)

따라서 다음 코드를 사용하여 잠금을 얻을 수 있습니다

SET 잠금 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 yisu.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제