leftso 38901 2 2019-07-10 11:54:22

文章位置:左搜> 编程技术> 正文

前言

继续上一篇Spring Boot Redis 秒杀实现 的一个修改版本,主要实现用ab工具进行网页正式访问的一个版本,其主要目的还是介绍Redis实现秒杀活动的一种方式。

Redis 秒杀活动项目结构图

项目结果图

与上一篇文章中的区别在于多了一个GoodService服务。该服务主要提供秒杀业务。

Redis秒杀活动业务层

$title(GoodsService.java)
package com.leftso.demo.demoredisseckill.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Transaction;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;

/**
 * 商品服务
 */
@Service
public class GoodsService {

    @Autowired
    JedisPool jedisPool;

    public static String productKey="GOODS_001";//某产品的ID
    int goodsStock=10;//某产品用于秒杀的库存
    String successKey="Success_User_List";//成功秒杀用户的集合


    /**
     * 初始化一些默认数据(正常情况这些数据来源于数据库)
     */
    @PostConstruct
    public void init(){
        Jedis jedis=jedisPool.getResource();
        jedis.set(productKey,String.valueOf(goodsStock));//设置产品默认库存数量
        while (jedis.lpop(successKey)!=null){

        }//清空秒杀成功人用户列表
        //end
       new Thread(()->{
           long size=jedis.llen(successKey);
           while (true){
               if (size==goodsStock){
                   break;
               }else{
                   size=jedis.llen(successKey);
               }
           }
           List<String> successUsers=new ArrayList<>();
           String  user=jedis.lpop(successKey);
           while (user!=null){
               successUsers.add(user);
               user=jedis.lpop(successKey);
           }
           System.out.println("活动结束>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>活动结束");
           System.out.println("抢购名单:"+successUsers);
           //可以在名单拿到后生成订单等其他业务操作。
           String num=jedis.get(productKey);
           System.out.println("剩余库存:"+num);
       }).start();
    }

    /**
     * 获取库存
     * @param productKey
     * @return
     */
    public int getStock(String productKey){
        try (Jedis jedis=jedisPool.getResource();){
            String val=jedis.get(productKey);
            if (val!=null){
                return Integer.valueOf(val);
            }
            return  -1;
        }
    }

    /**
     * 秒杀商品
     *
     * @param productKey
     * @return
     */
    public boolean seckill(String productKey, String userName) {

        if (getStock(productKey)<=0){
            System.out.println("商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。");
            return false;
        }
        try(Jedis jedis = jedisPool.getResource();) {
            jedis.watch(productKey);
            String val = jedis.get(productKey);
            int valInt = Integer.valueOf(val);
            if (valInt >= 1) {
                Transaction tx = jedis.multi();
                tx.incrBy(productKey, -1);//原子操作
                List<Object> list = tx.exec();
                if (list == null || list.isEmpty()) {
                    //System.out.println("用户:" + userName + " 抢购失败。");
                    this.seckill(productKey, userName);//再抢
                } else {
                    System.out.println("用户:" + userName + " 抢购成功!!!");
//                    jedis.setnx(productKey,)
                    jedis.rpush(successKey, userName);//成功用户添加入队列
                    //处理成功后的业务逻辑
                    return true;
                }
            } else {
                System.out.println("商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。");
                return false;
            }
            return false;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}
主要模拟环境与之前的文章一样。作为秒杀模拟场景。

其他相关文件清单:
pom.xml依赖
$title(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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.leftso.demo</groupId>
    <artifactId>demo-redis-seckill</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo-redis-seckill</name>
    <description>Redis 实现产品秒杀</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.0.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>


redis配置
$title(JedisConfig.java)
package com.leftso.demo.demoredisseckill;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.util.HashSet;
import java.util.Set;

@Configuration
public class JedisConfig {
    /***
     * 单机连接池
     * @return
     */
    @Bean
    public JedisPool jedisPool() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        // 设置配置
        jedisPoolConfig.setMaxTotal(2048);
        jedisPoolConfig.setMaxIdle(400);
        jedisPoolConfig.setMaxWaitMillis(100);
        jedisPoolConfig.setTestOnBorrow(false);//jedis 第一次启动时,会报错
        jedisPoolConfig.setTestOnReturn(true);
        JedisPool pool = new JedisPool(jedisPoolConfig, "127.0.0.1", 6379);
        return pool;
    }

    /***
     * redis集群用(这里暂时没用)
     * @return
     */
//    @Bean
    public JedisCluster jedisCluster() {
        //创建jedisCluster对象,有一个参数 nodes是Set类型,Set包含若干个HostAndPort对象
        Set<HostAndPort> nodes = new HashSet<>();
        nodes.add(new HostAndPort("127.0.0.1", 7001));
        nodes.add(new HostAndPort("127.0.0.1", 7002));
        nodes.add(new HostAndPort("127.0.0.1", 7003));
        nodes.add(new HostAndPort("127.0.0.1", 7004));
        nodes.add(new HostAndPort("127.0.0.1", 7005));
        nodes.add(new HostAndPort("127.0.0.1", 7006));
        JedisCluster jedisCluster = new JedisCluster(nodes);
        //使用jedisCluster操作redis
//        jedisCluster.set("test", "my forst jedis");
//        String str = jedisCluster.get("test");
//        System.out.println(str);
//        //关闭连接池
//        jedisCluster.close();
        //注意由于集群式单例,不要再其他地方关闭连接池!!!!由系统关闭时统一关闭。
        return jedisCluster;
    }
}
web 接口
$title(SecKillController.java)
package com.leftso.demo.demoredisseckill.controller;

import com.leftso.demo.demoredisseckill.service.GoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;

/**
 * 秒杀接口
 */
@RestController
public class SecKillController {
    @Autowired
    GoodsService goodsService;

    @GetMapping("/seckill")
    public Object seckill() {
        //创建随机用户名
        String userName="用户+"+ UUID.randomUUID().toString().replace("-","").toUpperCase();
        boolean suc=goodsService.seckill(GoodsService.productKey,userName);
        return suc?"Succes":"Fail";
    }
}

Redis 秒杀活动测试

这里的测试主要使用ab工具进行测试。

首先启动秒杀应用(注意启动一次只能测一次。再次测试请重启服务,例子简单就这么处理的。哈哈)

ab测试命令:
ab -c 1000 -n 3000 http://localhost:8080/seckill
执行结果:
ab测试结果

通过测试的访问来看,错误请求有2994.好吧我目前也不知道啥情况。反正后台的输出是正常的。来看看后台的日志吧
 
2019-07-10 11:49:10.592  INFO 2656 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 9 ms
用户:用户+28C21F33CBD3452EB5C67B14A3939356 抢购成功!!!
用户:用户+126334102697433C8BB5B5D6E4061E38 抢购成功!!!
用户:用户+9ED7D0B472E9490E9C780B61B881E41B 抢购成功!!!
用户:用户+5AD862F2A389473AB2B4FB5EDC187617 抢购成功!!!
用户:用户+A264C1B9CB69498D8402B2E6C0B47589 抢购成功!!!
用户:用户+FC3D45232C73446E8032FDC36B0B8430 抢购成功!!!
用户:用户+D0F11B01EAB24CB385C23580CFC4E671 抢购成功!!!
用户:用户+73FD6DAED8564FEF8315CC4D6181ED77 抢购成功!!!
用户:用户+6967C042ADCA4FC9B42B70A51EE10752 抢购成功!!!
用户:用户+BBB5FF4878844B79BD11DC8C15EA9500 抢购成功!!!
商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。
商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。
商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。
商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。
商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。
活动结束>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>活动结束
抢购名单:[用户+28C21F33CBD3452EB5C67B14A3939356, 用户+126334102697433C8BB5B5D6E4061E38, 用户+9ED7D0B472E9490E9C780B61B881E41B, 用户+5AD862F2A389473AB2B4FB5EDC187617, 用户+A264C1B9CB69498D8402B2E6C0B47589, 用户+FC3D45232C73446E8032FDC36B0B8430, 用户+D0F11B01EAB24CB385C23580CFC4E671, 用户+73FD6DAED8564FEF8315CC4D6181ED77, 用户+6967C042ADCA4FC9B42B70A51EE10752, 用户+BBB5FF4878844B79BD11DC8C15EA9500]
剩余库存:0
商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。
商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。
商品已抢购完毕>>>>>>>>>>>>>>>>>>>>>>>>>>欢迎下次再来。

细数了下获奖名单还是没错的。哈哈

谁知道ab为啥那么多错误请求?评论告诉我一下谢谢。