Redis实现秒杀功能

在实际生活中,秒杀功能是比较常见的,如12306抢票、电商系统的秒杀活动等。所谓秒杀,从应用业务角度来看,是指在短时间内多个用户“争抢”某个资源,这里的资源在大部分秒杀场景里是商品;从技术角度来看,就是多个线程对资源进行操作。所以,要实现秒杀功能,就必须控制线程对资源的争夺,既要保证高效、并发,又要保证操作的正确性,符合实际业务需要。

对于秒杀的优化思路有:

  • 写入内存。
  • 实现多线程异步处理。
  • 实现分布式处理。

【实例】使用多线程的方式实现1000人秒杀100部手机的实例。

(1)在pom.xml配置信息文件中,添加Redis启动器、Jedis客户端的相关依赖:

<!-- Redis启动器 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>2.4.0</version>
</dependency>

<!-- Jedis客户端依赖 -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.3.0</version>
</dependency>

(2)秒杀实现(SecondKill.java):创建多线程,并利用 Redis 的事务功能,实现秒杀功能。

package com.pjb.seckill;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
import java.util.List;

/**
 * 秒杀抢购
 * @author pan_junbiao
 **/
public class SecondKill implements Runnable
{
    String key = "iphone";
    Jedis jedis = new Jedis("127.0.0.1",6379);
    String userInfo;

    public SecondKill(String userInfo)
    {
        this.userInfo = userInfo;
    }

    @Override
    public void run()
    {
        try
        {
            jedis.watch(key); //watchkeys

            String val = jedis.get(key);
            int valint = Integer.valueOf(val);

            if(valint<=100 && valint>=1)
            {
                //1、使用MULTI命令开启事务
                Transaction tx = jedis.multi();

                //2、事务命令入队
                tx.incrBy(key,-1);

                //3、使用EXEC命令执行事务
                //提交事务。如果此时watchkeys被改动了,则返回null
                List<Object> list = tx.exec();

                if(list==null || list.size()==0)
                {
                    String failuserinfo = "fail_" + userInfo;
                    String failinfo = "用户:" + failuserinfo + "商品争抢失败,抢购失败";
                    System.out.println(failinfo);
                    //抢购失败业务逻辑
                    jedis.setnx(failuserinfo,failinfo);
                }
                else
                {
                    for(Object succ : list)
                    {
                        String succuserinfo = "succ_" + succ.toString() + "_" + userInfo;
                        String succinfo = "用户:" + succuserinfo + " 抢购成功,当前抢购成功人数:" + (1 -(valint -100));
                        System.out.println(succinfo);
                        //抢购成功业务逻辑
                        jedis.setnx(succuserinfo,succinfo);
                    }
                }
            }
            else
            {
                String failuserinfo = "kcfail_" + userInfo;
                String failinfo1 = "用户:" + failuserinfo + " 商品被抢购完毕,抢购失败";
                System.out.println(failinfo1);
                jedis.setnx(failuserinfo,failinfo1);
                return;
            }
        }
        catch(Exception ex)
        {
            ex.printStackTrace();
        }
        finally
        {
            jedis.close();
        }
    }
}

(3)主程序(SecondKillTest.java):秒杀功能的入口程序,用于创建线程池,同时生成用户ID,调用秒杀功能代码,完成秒杀任务。

package com.pjb.seckill;

import com.pjb.util.RedisHelper;
import redis.clients.jedis.Jedis;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Redis秒杀功能的实现,1000人抢购100部手机
 * @author pan_junbiao
 **/
public class SecondKillTest
{
    public static void main(String[] args)
    {
        final String key = "iphone";
        //20个线程池并发数
        ExecutorService executor = Executors.newFixedThreadPool(20);

        final Jedis jedis = new Jedis("127.0.0.1",6379);
        jedis.del(key); //先删除
        jedis.set(key,"100"); //设置起始的抢购数
        jedis.close();

        for(int i=0; i<1000; i++)
        {
            String userInfo = "pan_junbiao的博客" + i;
            executor.execute(new SecondKill(userInfo));
        }
        executor.shutdown();
    }
}

执行结果:

猜你喜欢

转载自blog.csdn.net/pan_junbiao/article/details/111478937