前言
继续上一篇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
执行结果:
通过测试的访问来看,错误请求有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为啥那么多错误请求?评论告诉我一下谢谢。
原创文章,作者:1402239773,如若转载,请注明出处:https://blog.ytso.com/243625.html