栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

还不会基于redis的分布式锁吗?超级详细,手把手教学,包教包会

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

还不会基于redis的分布式锁吗?超级详细,手把手教学,包教包会

分布式锁-redis实现方式

随着项目架构由最简单的单体结构,到后面的集群模式,再到后面的微服务架构,架构开始也越来越复杂。传统的jvm进程锁可能满足不了当前的软件架构,所以分布式锁越来越多被用到。

说起分布式锁,可能实现方式有很多,常见的可能有以下几种:

  1. 基于数据库
  2. 基于redis
  3. 基于zookeeper
基于数据库

基于数据库的设计比较简单,原理就是根据数据库唯一索引。实现方式自己可以上网搜索,本文就不着重说明。

基于redis

redis为什么能做分布式锁,是因为redis是属于单线程模型,底层是因为file event handler(文件事件处理器)的设计是单线程模型。

实战演练
  1. 新建项目
  2. 点击next
  3. 点击next
  4. 新建完成之后,修改配置文件
  5. 模拟业务代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/test")
@Slf4j
public class TestController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @GetMapping("/test")
    public String test() {
        String num = stringRedisTemplate.opsForValue().get("num");
        int testNum = Integer.parseInt(num);
        if (testNum > 0) {
            log.info("库存数量为{}", testNum);
            testNum--;
            stringRedisTemplate.opsForValue().set("num", testNum + "");
        } else {
            return "库存不足";
        }
        return "成功";
    }
}
  1. idea启动一个项目的两个实例(不会的可以上网查找资料)
  2. nginx做负载均衡(不会安装的可以上网查下资料),配置文件如下
#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;

    keepalive_timeout  65;

	upstream mysvr {   
      server 127.0.0.1:8080;
      server 127.0.0.1:8090;
    }

    server {
        listen       80;
        server_name  localhost;

        location ~*^.+$ {
            proxy_pass	http://mysvr;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        } 
    }
}
  1. 测试架构是否正常

    后台日志验证负载均衡是否正常
  • 8080日志
  • 8090日志
  1. 并发问题模拟
    • 首先需要在redis中将key为num的设置初始值,也就是库存量为10
    • 用压测工具jmeter新建测试用例
      1. 添加线程组

      2. 添加http请求

      3. 添加测试结果

    • 测试结果
      • 8080
      • 8090

        问题分析:

线程1和线程2同时读取库存,同时修改库存,返回库存,肯定会出现并发问题(超卖问题)。

  1. 加入redis分布式锁
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/api/test")
@Slf4j
public class TestController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @GetMapping("/test")
    public String test() throws InterruptedException {
        String lKey = "order";
        String uuid = UUID.randomUUID().toString();
        Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(lKey, uuid, 1, TimeUnit.SECONDS);
        if(!aBoolean){
            log.warn("获取redis锁失败");
            return "获取redis锁失败";
        }
        try {
            String num = stringRedisTemplate.opsForValue().get("num");
            int testNum = Integer.parseInt(num);
            if (testNum > 0) {
                log.info("库存数量为{}", testNum);
                testNum--;
                stringRedisTemplate.opsForValue().set("num", testNum + "");
            } else {
                log.info("库存不足");
                // 为了保证释放的是自己的锁
                if(uuid.equals(stringRedisTemplate.opsForValue().get(lKey))){
                    stringRedisTemplate.delete(lKey);
                }
                return "库存不足";
            }
        } finally {
            // 为了保证释放的是自己的锁
            if(uuid.equals(stringRedisTemplate.opsForValue().get(lKey))){
                stringRedisTemplate.delete(lKey);
            }
        }
        return "成功";
    }
}

测试结果:

  • 8080

  • 8090

    注意点:

  • 为什么要加key的过期时间?

    问题场景,如果线程刚刚拿到锁,在这个时候程序被强行停止,那么这个锁将永远不会被释放,后续所有线程都将无法拿到锁。

  • 为什么在释放锁的时候需要判断uuid?

    问题场景,如果线程执行业务逻辑的时间比设置的时间还久,那么等线程执行到释放锁的代码的时候释放的是不是就不是自己的那把锁了,有可能就是其他线程的锁,会导致锁失效。

  • 什么时候释放锁?

    首先是业务正常执行完逻辑之后需要释放锁,业务逻辑出现异常也需要释放锁,库存不足了也是需要释放锁。

这样一把redis分布式锁就实现了!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/273070.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号