Redis6
9.3)Redis的事务秒杀案例
9.3.1)解决计数器和人员记录的事务操作
秒杀主要包括两个操作:1)商品库存 - 1 2)秒杀成功的该用户加到秒杀成功者清单里
9.3.2)不考虑并发的秒杀案例实现
新建工程 Seckill ,这是一个WEB工程
创建页面:index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>iPhone 13 Pro !!! 1元秒杀!!!
</h1>
<form id="msform" action="${pageContext.request.contextPath}/doseckill" enctype="application/x-www-form-urlencoded">
<%--固定商品id设置为0101--%>
<input type="hidden" id="prodid" name="prodid" value="0101">
<input type="button" id="miaosha_btn" name="seckill_btn" value="秒杀点我"/>
</form>
</body>
<script type="text/javascript" src="${pageContext.request.contextPath}/script/jquery/jquery-3.1.0.js"></script>
<script type="text/javascript">
$(function(){
$("#miaosha_btn").click(function(){
var url=$("#msform").attr("action");
$.post(url,$("#msform").serialize(),function(data){
if(data=="false"){
alert("抢光了" );
$("#miaosha_btn").attr("disabled",true);
}
} );
})
})
</script>
</html>
创建 SecKillServlet,页面按钮点击秒杀调用doPost()方法
public class SecKillServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public SecKillServlet() {
super();
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 通过随机数生成用户id
String userid = new Random().nextInt(50000) + "";
// 接收调用的商品id
String prodid = request.getParameter("prodid");
// 调用秒杀方法
boolean isSuccess = SecKill_redis.doSecKill(userid, prodid);
// 根据秒杀的返回结果返回相应信息
response.getWriter().print(isSuccess);
}
}
创建 SecKill_redis.java,秒杀主方法,秒杀的全流程在这里开发
public class SecKill_redis {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 11079);
System.out.println(jedis.ping());
jedis.close();
}
//秒杀主方法--全过程
public static boolean doSecKill(String uid, String prodid) throws IOException {
//步骤1 根据入参用户ID-uid和商品ID-prodid 进行非空判断
if (uid == null || prodid == null) {
return false;
}
//步骤2 连接redis
Jedis jedis = new Jedis("127.0.0.1", 11079);
//步骤3 拼接key
// 3.1 库存key
String kcKey = "sk:" + prodid + ":qt";
// 3.2 秒杀成功的用户key
String userKey = "sk:" + prodid + ":user";
// 步骤4 获取库存,如果库存null,秒杀还没有开始
String kc = jedis.get(kcKey);
if (kc == null) {
System.out.println("秒杀还没有开始,请等待");
jedis.close();
return false;
}
// 步骤5 判断用户是否重复秒杀操作
// 判断set集合中的用户信息,因为不能存在重复的用户【不能重复秒杀】所以使用Set存储用户信息
if (jedis.sismember(userKey, uid)) {
System.out.println("已经秒杀成功了,不能重复秒杀");
jedis.close();
return false;
}
// 步骤6 判断如果商品数量,库存数量小于1,秒杀结束
if (Integer.parseInt(kc) <= 0) {
System.out.println("秒杀已经结束了");
jedis.close();
return false;
}
// 步骤7 秒杀的过程
//7.1 库存-1
jedis.decr(kcKey);
//7.2 把秒杀成功用户添加清单里面
jedis.sadd(userKey, uid);
System.out.println("秒杀成功了..");
jedis.close();
return true;
}
}
测试:启动Tomcat工程
浏览器访问:http://localhost:8080/Seckill/
点击 “秒杀按钮” ,此时还未向 Redis 中添加 商品信息,效果如下图:
向Redis中加入商品信息,命令如下:【添加了5个商品】
127.0.0.1:11079> flushdb OK 127.0.0.1:11079> keys * (empty array) 127.0.0.1:11079> set sk:0101:qt 5 OK 127.0.0.1:11079> keys * 1) "sk:0101:qt"
刷新页面后再次点击秒杀按钮”,效果如下图:
弹框不再弹出,且后台提示 —— 秒杀成功了..
在Redis中查看相关商品和用户信息:
127.0.0.1:11079> get sk:0101:qt
"4"
127.0.0.1:11079> smembers sk:0101:user
1) "452"
发现商品数量已减少1,用户列表新增了秒杀成功的用户
再次点击秒杀按钮”,直到第六次,效果如下图:
弹框再次弹出,且后台提示 —— 秒杀已经结束了
在Redis中查看相关商品和用户信息:
127.0.0.1:11079> get sk:0101:qt
"0"
127.0.0.1:11079> smembers sk:0101:user
1) "452"
2) "16928"
3) "27602"
4) "41023"
5) "41347"
发现商品信息已经为0,同时用户列表中已经有了5个秒杀成功的用户
并发测试:
在Linux系统中安装:
yum install httpd-tools
通过ab进行测试
在根目录新建 postfile 文件,内容如下:
prodid=0101&
在根目录执行下列命令,执行并发测试
ab -n 1000 -c 100 -p ~/postfile -T application/x-www-form-urlencoded http://172.19.83.237:8080/Seckill/doseckill
【1000个请求、100个并发,调用172.19.83.237(个人主机)上的秒杀方法】
输出:
存在问题:
- 已经秒杀结束了,但还存在秒杀成功的情况
- 【超卖问题】Redis 中的商品信息为 -3,不能出现商品为负数的情况
- 【连接超时问题】Redis 无法同时处理过多的请求,不能处理的请求需要等待,等待时间过长会报连接超时错误