Redis combat - distributed lock

Table of contents

1 One person, one order concurrency security issue

2 Principle and Implementation of Distributed Lock

2.1 What is a distributed lock?

2.2 Implementation of distributed locks


1 One person, one order concurrency security issue

The pessimistic lock used in the previous one-person-one-order business cannot take effect in a distributed system.

The ideal situation is like this: one thread successfully acquires the mutex, and queries the order and creates the order, and other threads cannot intervene. Its principle is that there will be a lock monitor to monitor who has acquired the lock. 

But the problem arises in:

In a distributed system, there are multiple different JVMs. In different JVM environments, there are multiple lock listeners, and some threads can still acquire locks even when other threads have already acquired them. to the lock.

At this time, the locks in the ordinary JVM are no longer useful, so we need to use distributed locks.

2 Principle and Implementation of Distributed Lock

2.1 What is a distributed lock?

It is a lock that can be seen and mutually exclusive by multiple processes in a distributed system or cluster mode.

Its implementation principle is that different JVM environments share a lock monitor. This will not lead to the situation where multiple threads use multiple locks.

 Features:

2.2 Implementation of distributed locks

 There are three main implementation methods, we can all come to a comparison.

As shown below:

Here we mainly talk about the implementation of distributed locks based on Redis .

The method of implementing Reids distributed lock mainly consists of the following two steps:

1. Acquire the lock

The method of acquiring locks is an old friend, which is to use the setnx method (mutual exclusion) in the Redis String type method. However, in order to prevent the downtime of the redis server, we need to set a timeout period for the lock to avoid deadlock. (non-blocking)

Therefore, the way to acquire the lock can use the following code

SET lock thread1 nx ex 10

lock is the key of the lock, thread1 is the value, nx is the setnx method, and ex is the timeout setting 

2. Release the lock

Releasing the lock is as simple as deleting.

of the lock

Code:

Requirements: Define an interface and use Redis to realize the function of distributed locks.

 code show as below:

Interface code:

package com.hmdp.utils;

public interface ILock {

    /**
     * 尝试获取锁
     * @param timeoutSec 锁的持有时间,过期自动释放
     * @return true代表获取锁成功,false代表获取锁失败。
     */
    boolean tryLock(long timeoutSec);

    /**
     * 释放锁
     */
    void unlock();
}

 Interface implementation class:

package com.hmdp.utils;

import org.springframework.data.redis.core.StringRedisTemplate;

import java.util.concurrent.TimeUnit;

/**
 * @Author 华子
 * @Date 2022/12/3 20:53
 * @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);
    }
}

The business layer acquires and releases locks (coupon spike business modification) 

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>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@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);
    }
}

Guess you like

Origin blog.csdn.net/qq_59212867/article/details/128166108