使用Redis实现分布式锁

分布式锁一般有三种实现方式:1. 数据库乐观锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁

本篇博客介绍的是第二种,基于Redis的分布式锁,实际上是使用Redisson这一款强大的框架来实现分布式锁的.

代码示例:   https://gitee.com/zhang-xiao-xiang/zxx-redis-lock

代码结构预览

1:创建一个springboot的基本项目,写好一些基本接口

所需的基本pom依赖

        <!--spring boot操作Redis的依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>
        <!--java web接口-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--简化java代码插件-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--Redis实现分布式锁的基本框架-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.5.0</version>
        </dependency>

 application.properties文件配置(注意端口可以配置8081和8082,多实例启动,模拟多肽tomcat服务器,idea的多实例启动方式如下图)

server.port=8082
spring.redis.url=redis://localhost:6379
写一个RedissonConfig配置类 来配置你的redisson
package com.zhang.zxx.redis.lock.config;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * RedissonAutoConfiguration:写一个RedissonConfig配置类 来配置你的redisson
 *
 * @author zhangxiaoxiang
 * @date 2020/09/10
 */
@Configuration
public class RedissonAutoConfiguration {
    @Value("${spring.redis.url}")
    private String addressUrl;

    @Bean
    public RedissonClient getRedisson() {
        Config config = new Config();
        config.useSingleServer()
                .setAddress(addressUrl)
                .setReconnectionTimeout(10000)
                .setRetryInterval(5000)
                .setTimeout(10000)
                .setConnectTimeout(10000);
        return Redisson.create(config);
    }
}

编写基本接口(注释的是优化后,使用分布式锁的,为了演示效果,先注释,然后再对比)

package com.zhang.zxx.redis.lock.controller;

import com.zhang.zxx.redis.lock.config.RedissonAutoConfiguration;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Objects;

/**
 * RedisLockController:测试分布式锁
 *
 * @author zhangxiaoxiang
 * @date 2020/09/10
 */
@RestController
@RequestMapping("/order")
public class RedisLockController {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RedissonAutoConfiguration redissonAutoConfiguration;

    @Value("${server.port:}")
    private String port;

    /**
     * 使用接口为redis添加个库存(手动添加也行)
     * @return
     */
    @RequestMapping("/setprodnum")
    public String addProdNum() {
        redisTemplate.opsForValue().set("stock","50");
        System.out.println("缓存数量是: "+redisTemplate.opsForValue().get("stock"));
        return redisTemplate.opsForValue().get("stock")+"";
    }

    /**
     * 未使用分布式锁:秒杀商品,减库存接口
     *
     * @return
     */
    @RequestMapping("/getcommodity")
    public String getCommodity() {
        //库存(这里翻译成了锁也是为了方便理解)的key
        String lockKey = "prod_lock";
       // 检查指定的对象引用是否不为空 Objects.requireNonNull,查出库存
        int stock = Integer.parseInt(Objects.requireNonNull(redisTemplate.opsForValue().get("stock")));
        if (stock>0){
            int realStock=stock-1;
            redisTemplate.opsForValue().set("stock",realStock+"");
            System.out.println("进入减库存的接口,服务器端口:" + port+"扣减成功,库存剩余===>"+realStock);
            return "扣减成功,库存剩余:"+realStock;
        }else {
            System.err.println("扣减失败,库存不足了呢");
            return "扣减失败,库存不足了呢";
        }



    }


    // /**
    //  * 秒杀商品,减库存接口,分布式锁解决方案
    //  *
    //  * @return
    //  */
    // @RequestMapping("/getcommodity")
    // public String getCommodity() {
    //     String lockKey = "prod_lock";
    //     RLock rLock = redissonAutoConfiguration.getRedisson().getLock(lockKey);
    //     try {
    //         rLock.lock();
    //         int stock = Integer.parseInt(Objects.requireNonNull(redisTemplate.opsForValue().get("stock")));
    //         if (stock>0){
    //             int realStock=stock-1;
    //             redisTemplate.opsForValue().set("stock",realStock+"");
    //             System.out.println("进入减库存的接口,服务器端口:" + port+"扣减成功,库存剩余===>"+realStock);
    //             return "扣减成功,库存剩余:"+realStock;
    //         }else {
    //             System.err.println("扣减失败,库存不足了呢");
    //             return "扣减失败,库存不足了呢";
    //         }
    //     } finally {
    //         //注意释放锁
    //         rLock.unlock();
    //     }
    // }


}

 此时可以启动两个实例,可以访问两个接口,都可以实现减少库存,如下图

http:://127.0.0.1:8081/order/getcommodity

http://127.0.0.1:8082/order/getcommodity

 

2:配置NGINX代理(模拟多台tomcat服务器同时访问)和Jmeter压测

在NGINX配置文件最后一个大括号内添加,下载地址(http://nginx.org/download/nginx-1.12.2.zip),这是Windows版本的

#自定义添加配置,注意是在配置文件的最后一个大括号内-----------------------------------------------------------------
	
	#大家可以指定为服务的域名或者项目的代号
    upstream upstream_name{
        server 127.0.0.1:8081;
        server 127.0.0.1:8082;
		}
		server {
			listen       8080;
			server_name  localhost;

			location / {
				proxy_pass http://upstream_name;
				proxy_set_header Host $host;
				proxy_set_header X-Real-IP $remote_addr;
				proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
			}
		}
    #自定义添加配置,注意是在配置文件的最后一个大括号内-----------------------------------------------------
	#访问的时候访问http://127.0.0.1:8080/order/getcommodity即可达到访问
	#http://127.0.0.1:8081/order/getcommodity
	#http://127.0.0.1:8082/order/getcommodity
	#这几个的目的,没有配置权重,就采用默认策略(负载均衡-轮询)

启动NGINX的方式是 

 是否启动成功,访问http://localhost  即可,如图即为正常启动后的NGINX默认页面.

配置Jmeter,配置模拟并发和访问接口,如下两张图(不太会配置的可以百度Jmeter的使用)

 下面红色框就是http://127.0.0.1:8080/order/getcommodity 拆分了一下,目的是为了NGINX代理分发请求到http:://127.0.0.1:8081/order/getcommodity和http:://127.0.0.1:8082/order/getcommodity

 3:启动项目,并行8081和8082两个demo项目

 对比结果(显然乱套了,数据并发访问异常)

 此时将控制层注释掉的接口恢复,把常规接口  getcommodity 的注释,重新对比(注意修改redis库存后再试)

 重启项目,对比结果

此时已经完全呈现出正确的数据,完成.

用法是不是和ReentrantLock 十分相似呢(可以参考我的另一篇博客https://zhangxiaoxiang.blog.youkuaiyun.com/article/details/108046261)

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值