Redis 分布式锁测试

本文介绍了在SpringBoot项目中如何添加必要的依赖,如SpringBoot测试、Redis启动器和Fastjson,以及如何使用Redis分布式锁进行订单抢单操作。通过Junit测试模拟入库和压测,并展示了Redis的可视化操作和分布式锁的实现原理。
摘要由CSDN通过智能技术生成

一、前提依赖(除去SpringBoot项目基本依赖外):

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
</dependency>

<!-- 配置使用redis启动器 -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- 引入阿里fastjson2依赖 -->
<dependency>
   <groupId>com.alibaba.fastjson2</groupId>
   <artifactId>fastjson2</artifactId>
   <version>2.0.42</version>
</dependency>

<!--junit 测试-->
<dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.13.2</version>
</dependency>

二、我这里用到的实体类(Orderinfo ):

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
public class Orderinfo implements Serializable {
    private String onum;
    private Integer eid;
    private BigDecimal price;
    private String fromname;
    private String fromaddress;
    private String fromtel;
    private String toname;
    private String toaddress;
    private String totel;
    private String fromcardnum;
    private Integer state;
    private Date createtime;
    private static final long serialVersionUID = 1L;
    public String getOnum() {
        return onum;
    }
    public void setOnum(String onum) {
        this.onum = onum == null ? null : onum.trim();
    }
    public Integer getEid() {
        return eid;
    }
    public void setEid(Integer eid) {
        this.eid = eid;
    }
    public BigDecimal getPrice() {
        return price;
    }
    public void setPrice(BigDecimal price) {
        this.price = price;
    }
    public String getFromname() {
        return fromname;
    }
    public void setFromname(String fromname) {
        this.fromname = fromname == null ? null : fromname.trim();
    }
    public String getFromaddress() {
        return fromaddress;
    }
    public void setFromaddress(String fromaddress) {
        this.fromaddress = fromaddress == null ? null : fromaddress.trim();
    }
    public String getFromtel() {
        return fromtel;
    }
    public void setFromtel(String fromtel) {
        this.fromtel = fromtel == null ? null : fromtel.trim();
    }
    public String getToname() {
        return toname;
    }
    public void setToname(String toname) {
        this.toname = toname == null ? null : toname.trim();
    }
    public String getToaddress() {
        return toaddress;
    }
    public void setToaddress(String toaddress) {
        this.toaddress = toaddress == null ? null : toaddress.trim();
    }
    public String getTotel() {
        return totel;
    }
    public void setTotel(String totel) {
        this.totel = totel == null ? null : totel.trim();
    }
    public String getFromcardnum() {
        return fromcardnum;
    }
    public void setFromcardnum(String fromcardnum) {
        this.fromcardnum = fromcardnum == null ? null : fromcardnum.trim();
    }
    public Integer getState() {
        return state;
    }
    public void setState(Integer state) {
        this.state = state;
    }
    public Date getCreatetime() {
        return createtime;
    }
    public void setCreatetime(Date createtime) {
        this.createtime = createtime;
    }
}

三、场景-抢单

        1. 添加RedisLockUtil:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
@Component
public class RedisLockUtil {
   @Autowired
   private RedisTemplate<String, String> redisTemplate;
   /**
    * 加锁
    * @param key   键
    * @param value 当前时间 + 超时时间
    * @return 是否拿到锁
    */

   public boolean lock(String key, String value) {
      if (redisTemplate.opsForValue().setIfAbsent(key, value)) {
         return true;
      }
      String currentValue = redisTemplate.opsForValue().get(key);
      //如果锁过期
      if (!StringUtils.isEmpty(currentValue)
            && Long.parseLong(currentValue) < System.currentTimeMillis()) {
//设置新值,返回旧值
         String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
         //是否已被别人抢占 比对currentValue 和oldValue 是否一致 确保未被其他人抢占
         return !StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue);
      }
      return false;
   }

   /**
    * 解锁
    *
    * @param key   键
    * @param value 当前时间 + 超时时间
    */
   public void unlock(String key, String value) {
      try {
         String currentValue = redisTemplate.opsForValue().get(key);
         if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
            redisTemplate.opsForValue().getOperations().delete(key);
         }
      } catch (Exception e) {
         System.out.println("redis解锁异常");
      }
   }
}

        2. 新建RedisLockTest类: 

import com.alibaba.fastjson2.JSON;
import com.logistics.order.entity.Orderinfo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.ArrayList;
import java.util.List;
@SpringBootTest
@RunWith(SpringJUnit4ClassRunner.class)
@Controller
public class RedisLockTest {
    @Autowired
    public StringRedisTemplate stringRedisTemplate;

    @Autowired
    public RedisLockUtil redisLock;

    //模拟入库
    @Test
    public void insertRedis(){

        Orderinfo orderInfo1 = new Orderinfo();
        orderInfo1.setOnum("1");
        orderInfo1.setEid(1);

        Orderinfo orderInfo2 = new Orderinfo();
        orderInfo2.setOnum("2");
        orderInfo2.setEid(2);

        Orderinfo orderInfo3 = new Orderinfo();
        orderInfo3.setOnum("3");
        orderInfo3.setEid(3);

        List<Orderinfo> orderinfoList = new ArrayList<Orderinfo>();
        orderinfoList.add(orderInfo1);
        orderinfoList.add(orderInfo2);
        orderinfoList.add(orderInfo3);

        orderinfoList.forEach(x -> {
            stringRedisTemplate.boundHashOps("order").put(x.getOnum(), JSON.toJSONString(x));
        });

        System.out.println("入库成功。");
    }

    //压测
    @GetMapping("/getOrder")
    public void getOrder(String onum){

        //定义redis锁的key
        String lockkey = "orderkey";

        //定义锁的超时时间 1s
        Long ex = 1000L;
        String valueTimeout = System.currentTimeMillis()+ex+"";

        //判断锁是否加成功
        boolean lock = redisLock.lock(lockkey, valueTimeout);

        if(lock){
            String orderJson = (String)stringRedisTemplate.boundHashOps("order").get(onum);
            Orderinfo order = JSON.parseObject(orderJson, Orderinfo.class);
            System.out.println("订单:"+order.getOnum() +" 被抢到。");
            stringRedisTemplate.boundHashOps("order").delete(onum);

            //释放锁
            redisLock.unlock(lockkey,valueTimeout);
        }
    }
}

        3. 进入 Redis 的可视化客户端工具内查看添加信息:

        4. 模拟抢单:

@GetMapping("/getOrder")
    public void getOrder(String onum){
        //定义redis锁的key
        String lockkey = "orderkey";
        //定义锁的超时时间 1s
        Long ex = 1000L;
        String valueTimeout = System.currentTimeMillis()+ex+"";

        //判断锁是否加成功
        boolean lock = redisLock.lock(lockkey, valueTimeout);
        if(lock){
            String orderJson = (String)stringRedisTemplate.boundHashOps("order").get(onum);
            Orderinfo order = JSON.parseObject(orderJson, Orderinfo.class);
            System.out.println("订单:"+order.getOnum() +" 被抢到。");
            stringRedisTemplate.boundHashOps("order").delete(onum);
            //释放锁
            redisLock.unlock(lockkey,valueTimeout);
        }
    }

四、Jmeter压测:

        1. 创建线程组:

        2. 添加 HTTP 请求:

        3. 给一个 Linstener 监听的结果树:

        4. 模拟每秒 50 个请求:

        5. 设置请求及请求参数:

        6. 点击 5 图中的绿色小三角启动压测:

        Idea控制台:

Redis分布式锁:

分布式锁,是一种思想,它的实现方式有很多。比如,我们将沙滩当做分布式锁的组件,那么它看起来应该是这样的:

加锁

在沙滩上踩一脚,留下自己的脚印,就对应了加锁操作。其他进程或者线程,看到沙滩上已经有脚印,证明锁已被别人持有,则等待。

解锁

把脚印从沙滩上抹去,就是解锁的过程。

锁超时

为了避免死锁,我们可以设置一阵风,在单位时间后刮起,将脚印自动抹去。

分布式锁的实现有很多,比如基于数据库、memcached、Redis、系统文件、zookeeper等。它们的核心的理念跟上面的过程大致相同。

在这里我们通过单节点Redis实现一个简单的分布式锁。

1、加锁

加锁实际上就是在redis中,给Key键设置一个值,如果设置值成功,则表示客户端获得了锁。

2、解锁

解锁的过程就是将Key键删除

Redis分布式锁实现原理:

利用redis在同一时刻操作一个键的值只能有一个进程的特性,如果能设值成功就获取到锁。解锁,就是删除指定的键。为防止死锁可以设置锁超时时间,如果锁超时就释放锁。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
为了测试 Redis 分布式锁,你可以按照以下步骤进行操作: 1. 确保你已经安装了 Redis,并且可以通过命令行或者其他方式连接到 Redis 服务器。 2. 创建一个测试程序或者脚本,用于模拟多个并发请求获取和释放 Redis 分布式锁的情况。 3. 在测试程序中,使用 Redis 客户端库连接到 Redis 服务器。 4. 在测试程序中,创建多个并发线程或者进程,每个线程或进程都会尝试获取 Redis 锁。 5. 在每个线程或进程中,使用 Redis 的 SETNX 命令尝试获取锁。如果 SETNX 返回 1,表示获得了锁;如果返回 0,表示锁已经被其他线程或进程持有。 6. 如果获取到锁,执行需要加锁的业务逻辑;如果未获取到锁,等待一段时间后再次尝试获取锁。 7. 在业务逻辑执行完毕后,使用 Redis 的 DEL 命令释放锁。 8. 在测试程序中,记录每个线程或进程获取和释放锁的情况,以及锁的持有时间和并发冲突情况。 9. 执行测试程序,观察输出结果,检查是否存在并发冲突和锁的正确获取和释放。 10. 根据测试结果进行分析和调优,修改测试程序或者业务逻辑,以确保 Redis 分布式锁的正确性和高可用性。 请注意,在测试分布式锁时,你可能需要考虑以下情况: - 并发请求的数量和频率 - 锁的超时时间和自动释放机制 - 锁的重入性和可重入性 - 异常情况下的锁的释放和恢复机制 - 锁的可靠性和高可用性 以上是一般的测试步骤,你可以根据具体的使用场景和需求进行适当调整和扩展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰阔落好喝Wow

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值