首頁 > 資料庫 > Redis > 主體

Redis分散式鎖的原理是什麼和怎麼實現

王林
發布: 2023-05-27 16:16:28
轉載
3349 人瀏覽過

1 一人一單並發安全問題

之前一人一單的業務使用的悲觀鎖,在分散式系統下,是無法生效的。

在最理想的情況下,如果一個執行緒可以成功地取得互斥鎖並對訂單進行查詢和創建,則其他執行緒將無法幹擾。它的原理是會有一個鎖監視器,來監聽是誰獲得了鎖。

Redis分散式鎖的原理是什麼和怎麼實現

但問題就出現在:

分散式系統下,有多個不同的JVM,不同的JVM的環境下,鎖定監聽器是有多個的,就會出現有的線程在別的線程已經拿到鎖的情況下,仍然可以取得的到鎖。

Redis分散式鎖的原理是什麼和怎麼實現

這時候,普通的JVM中的鎖定就已經不管用了,就需要我們利用分散式鎖定 。

2 分散式鎖的原理與實作

2.1 什麼是分散式鎖定

就是可以滿足分散式系統或叢集模式下多進程可見且互斥的鎖。

它的實作原理就是,不同的JVM環境,都來共用一個鎖定監視器。這樣就不會導致出現多個執行緒用多把鎖的情況了。

Redis分散式鎖的原理是什麼和怎麼實現

特點:

Redis分散式鎖的原理是什麼和怎麼實現

#2.2 分散式鎖定的實作

主要有三種實作方法,我們可以都來進行一個對比。

如下圖:

Redis分散式鎖的原理是什麼和怎麼實現

這裡主要講基於Redis的分散式鎖定的實作 。

實作Reids分散式鎖定的方法主要就下面兩個步驟:

1. 取得鎖定

使用Redis中String類型的setnx方法(保證互斥性)已經是大家熟知的獲取鎖的方式。為了防止redis伺服器崩潰,我們需要為鎖定設定逾時時間,以避免死鎖。 (非阻塞)

所以,取得鎖定的方式可以使用以下程式碼

##SET lock thread1 nx ex 10

lock是鎖定的key, thread1 是value,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
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!