Redis(十)小案例实战(秒杀哦~~~~)

Redis(十)小案例实战(秒杀哦~~~~)

非分布式环境下秒杀,分布式高并发情况下会出现问题

<packaging>war</packaging>

<properties>
    <!--配置运行环境-->
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
    <java.version>1.11</java.version>
    <maven.compiler.source>1.11</maven.compiler.source>
    <maven.compiler.target>1.11</maven.compiler.target>
</properties>

<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>
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring/spring.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>
<?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:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--包注解扫描-->
    <context:component-scan base-package="com.szx.controller"/>
    <!--spring为连接Redis提供的模板工具类-->
    <bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
        <property name="connectionFactory" ref="connectionFactory"></property>
    </bean>
    <!--Redis连接工厂-->
    <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="106.75.245.83"></property>
        <property name="port" value="6379"/>
    </bean>


</beans>
@Controller
public class TestKill {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @RequestMapping("/kill")
    @ResponseBody
    //synchronized:此处锁,只能解决一个Tomcat的并发问题
    //synchronized:锁的是一个进程下的线程并发,如果分布式环境,多个线程并发,这种方案就失效了
    public synchronized String kill(){
        //在Redis中获取手机的库存量
        int phoneCount = Integer.valueOf(stringRedisTemplate.opsForValue().get("phone"));
        //判断手机数量是否足够秒杀
        if (phoneCount > 0){
            //减少库存
            phoneCount--;
            //将减少后的值放回Redis
            stringRedisTemplate.opsForValue().set("phone",phoneCount + "");         //此处为了将int转换为String(phoneCount + "")
            System.out.println("库存 -1,剩余库存:" + phoneCount);
        } else {
            System.out.println("库存不足!!!");
        }
        return "over!";
    }

}

实现分布式锁的思路

  • 因为redis是单线程的,所以命令也就具备原子性,使用setnx命令实现锁,保存k-v

    • 如果k不存在,保存(当前线程加锁),执行完成后,删除k表示释放锁
    • 如果k已存在,阻塞线程执行,表示有锁
  • 如果加锁成功,在执行业务代码的过程中出现异常,导致没有删除k(释放锁失败),那么就会造成死锁(后面的所有线程都无法执行)!

    • 设置过期时间,例如10秒后,redis自动删除
  • 高并发下,由于时间段等因素导致服务器压力过大或过小,每个线程执行的时间不同

    • 第一个线程,执行需要13秒,执行到第10秒时,redis自动过期了k(释放锁)
    • 第二个线程,执行需要7秒,加锁,执行第3秒(锁 被释放了,为什么,是被第一个线程的finally主动deleteKey释放掉了)【第一个线程太慢,导致释放的是第二个线程的锁】
    • 连锁反应,当前线程刚加的锁,就被其他线程释放掉了,周而复始,导致锁会永久失效
  • 给每个线程加上唯一的标识UUID随机生成,释放的时候判断是否是当前的标识即可

  • 问题又来了,过期时间如果设定?

    • 如果10秒太短不够用怎么办?
    • 设置60秒,太长又浪费时间
    • 可以开启一个定时器线程,当过期时间小于总过期时间的1/3时,增长总过期时间(吃仙丹续命!)

自己实现分布式锁,太难了!

Redisson

Redis 是最流行的 NoSQL 数据库解决方案之一,而 Java 是世界上最流行(注意,我没有说“最好”)的编程语言之一。

虽然两者看起来很自然地在一起“工作”,但是要知道,Redis 其实并没有对 Java 提供原生支持。

相反,作为 Java 开发人员,我们若想在程序中集成 Redis,必须使用 Redis 的第三方库。

而 Redisson 就是用于在 Java 程序中操作 Redis 的库,它使得我们可以在程序中轻松地使用Redis。

Redisson 在 java.util 中常用接口的基础上,为我们提供了一系列具有分布式特性的工具类。

@Controller
public class TestKill {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private Redisson redisson;

    @RequestMapping("/kill")
    @ResponseBody
    //synchronized:此处锁,只能解决一个Tomcat的并发问题
    //synchronized:锁的是一个进程下的多线程并发,如果分布式环境,多个线程并发,这种方案就失效了
    public synchronized String kill(){

        //实现分布式锁
        //定义商品ID
        String prouductKey = "HUAWEI-P40";
        //通过Redisson获取锁
        RLock lock = redisson.getLock(prouductKey);     //底层源码就是集成了setnx过期时间等操作
        
        //加锁
        //过期时间为 30 秒
        lock.lock(30, TimeUnit.SECONDS);

        try {
            //在Redis中获取手机的库存量
            int phoneCount = Integer.valueOf(stringRedisTemplate.opsForValue().get("phone"));
            //判断手机数量是否足够秒杀
            if (phoneCount > 0){
                //减少库存
                phoneCount--;
                //将减少后的值放回Redis
                stringRedisTemplate.opsForValue().set("phone",phoneCount + "");         //此处为了将int转换为String(phoneCount + "")
                System.out.println("库存 -1,剩余库存:" + phoneCount);
            } else {
                System.out.println("库存不足!!!");
            }
        } catch (NumberFormatException e) {
            e.printStackTrace();
        } finally {
            //释放锁
            lock.unlock();
        }
        return "over!";
    }

    @Bean
    public Redisson redisson(){
        Config config = new Config();
        //使用单例服务器
        config.useSingleServer().setAddress("redis://106.75.245.83:6379").setDatabase(0);
        //使用集群服务器
        //setScanInterval:心跳时间
        //addNodeAddress:节点地址
        //config.useClusterServers().setScanInterval(2000).addNodeAddress("","","");
        return  (Redisson) Redisson.create(config);
    }

}

实现分布式锁的方案其实有很多,我们之前用过的zookeeper的特点就是高可靠性,现在我们用的redis特点就是高性能。

目前分布式锁,应用最多的仍然是“Redis”

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值