高并发下,使用Redis实现分布式锁【经典案例:秒杀,抢购优惠券】

问题描述】synchronized只能解决一个tomcat的并发问题,synchronized锁的一个进程下的线程并发,如果分布式环境,多个进程并发,这种方案就失效了!
自己实现分布式锁的思路

1. 因为redis是单线程的,所以命令也就具备原子性,使用setnx命令实现锁,保存k-v
      如果k不存在,保存(当前线程加锁),执行完成后,删除k表示释放锁
      如果k已存在,阻塞线程执行,表示有锁
2. 如果加锁成功,在执行业务代码的过程中出现异常,导致没有删除k(释放锁失败),那么就会造成死锁(后面的所有线程都无法执行)!
      设置过期时间,例如10秒后,redis自动删除
3. 高并发下,由于时间段等因素导致服务器压力过大或过小,每个线程执行的时间不同
     第一个线程,执行需要13秒,执行到第10秒时,redis自动过期了k(释放锁)
     第二个线程,执行需要7秒,加锁,执行第3秒(锁 被释放了,为什么,是被第一个线程的finally主动deleteKey释放掉了)
      。。。连锁反应,当前线程刚加的锁,就被其他线程释放掉了,周而复始,导致锁会永久失效 
4. 给每个线程加上唯一的标识UUID随机生成,释放的时候判断是否是当前的标识即可
5. 问题又来了,过期时间如果设定?
     如果10秒太短不够用怎么办?
     设置60秒,太长又浪费时间
     可以开启一个定时器线程,当过期时间小于总过期时间的1/3时,增长总过期时间(吃仙丹续命!)

总之一句话,自己实现分布式锁比较难,我们可以借助Redssion

下面通过Redis实现分布式锁,解决高并发问题

Redisson介绍

  • Redis是最流行的 NoSQL 数据库解决方案之一,而 Java 是世界上最流行的编程语言之一。
  • 虽然两者看起来很自然地在一起“工作”,但是要知道,Redis 其实并没有对 Java 提供原生支持。
  • 相反,作为 Java 开发人员,我们若想在程序中集成 Redis,必须使用 Redis 的第三方库。
  • 而Redisson 就是用于在 Java 程序中操作 Redis 的库,它使得我们可以在程序中轻松地使用Redis。
  • Redisson 在 java.util 中常用接口的基础上,为我们提供了一系列具有分布式特性的工具类。

模拟秒杀项目结构】:
在这里插入图片描述

pom.xml文件:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  
  <modelVersion>4.0.0</modelVersion>  
  <groupId>com.DJL</groupId>  
  <artifactId>killers</artifactId>  
  <version>1.0-SNAPSHOT</version>  
  <packaging>war</packaging>  
  <dependencies> 
    <dependency> 
      <groupId>org.springframework</groupId>  
      <artifactId>spring-webmvc</artifactId>  
      <version>5.2.7.RELEASE</version> 
    </dependency>  
    <!--实现分布式锁的工具类-->  
    <dependency> 
      <groupId>org.redisson</groupId>  
      <artifactId>redisson</artifactId>  
      <version>3.6.1</version> 
    </dependency>  
    <!--spring操作redis的工具类-->  
    <dependency> 
      <groupId>org.springframework.data</groupId>  
      <artifactId>spring-data-redis</artifactId>  
      <version>2.3.2.RELEASE</version> 
    </dependency>  
    <!--redis客户端-->  
    <dependency> 
      <groupId>redis.clients</groupId>  
      <artifactId>jedis</artifactId>  
      <version>3.1.0</version> 
    </dependency>  
    <!--json解析工具-->  
    <dependency> 
      <groupId>com.fasterxml.jackson.core</groupId>  
      <artifactId>jackson-databind</artifactId>  
      <version>2.9.8</version> 
    </dependency> 
  </dependencies>  
  <build> 
    <plugins> 
      <plugin> 
        <groupId>org.apache.tomcat.maven</groupId>  
        <artifactId>tomcat7-maven-plugin</artifactId>  
        <configuration> 
          <port>8002</port>
          <path>/</path> 
        </configuration>  
        <executions> 
          <execution> 
            <!-- 打包完成后,运行服务 -->  
            <phase>package</phase>  
            <goals> 
              <goal>run</goal> 
            </goals> 
          </execution> 
        </executions> 
      </plugin> 
    </plugins> 
  </build> 
</project>

spring.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="controller"/>
    <bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
        <property name="connectionFactory" ref="connectionFactory"></property>
    </bean>
    <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="192.168.239.131"></property>
        <property name="port" value="6379"/>
    </bean>
</beans>

controller包下的TestKill文件(模拟秒杀的业务,同时使用了分布式锁):

package controller;

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

/**
 * @author DJL
 */
@RestController
public class TestKill {

    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Autowired
    Redisson redisson;

    @RequestMapping("/kill")
    public synchronized String kill() {
        //定义商品id
        String productKey = "HUAWEI-P40";
        //通过redssion获取锁
        RLock rlock = redisson.getLock(productKey);//底层源码就是集成了setnx、过期时间等操作
        //上锁(过期时间为30秒)
        rlock.lock(30, TimeUnit.SECONDS);
        try {
            //1.从redis中获取手机的库存量
            int phoneCount = Integer.parseInt(stringRedisTemplate.opsForValue().get("phone"));
            if (phoneCount > 0) {
                phoneCount--;
                stringRedisTemplate.opsForValue().set("phone", phoneCount + "");
                System.out.println("库存-1,剩余:" + phoneCount);
            } else {
                System.out.println("库存不足");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rlock.unlock();
        }

        return "over";
    }

    @Bean
    public Redisson redisson() {
        Config config = new Config(); // 使用单个redis服务器
        config.useSingleServer().setAddress("redis://192.168.239.131:6379").setDatabase(0); // 使用集群redis
        // config.useClusterServers().setScanInterval(2000).addNodeAddress("redis://192.168 .204.141:6379","redis://192.168.204.142:6379","redis://192.168.204.143:6379");
        return (Redisson) Redisson.create(config);
    }
}

我们启动两个工程,一个tomcat端口8001,一个tomcat端口8002,同时使用nginx进行负载均衡,同时我开启了两个虚拟机,一个虚拟机上使用Redis存储数据,另外一个虚拟机进行负载均衡

(1)对虚拟机上Nginx的Nginx.conf配置文件做出如下修改:

upstream sga{ 
    server 192.168.204.1:8001; //192.168.204.1是我本机的ip地址,8001是tomcat的端口号
    server 192.168.204.1:8002; //8002是另外一个工程的tomcat端口号
}
server { 
	listen 80; 
	server_name localhost; 
	#charset koi8-r; 
	#access_log logs/host.access.log main; 
	location / {
		proxy_pass http://sga; 
		root html; 
	index index.html index.htm; 
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值