【redis6】第十一章(秒杀案例)

news2024/11/15 8:32:59

计数器和人员记录

在这里插入图片描述

秒杀页面

<%@ 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">
	<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>

Servlet

package com.atguigu;

import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Random;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.ws.soap.AddressingFeature.Responses;

/**
 * 秒杀案例
 */
public class SecKillServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

    public SecKillServlet() {
        super();
    }

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

		String userid = new Random().nextInt(50000) +"" ;
		String prodid =request.getParameter("prodid");
		
		//boolean isSuccess=SecKill_redis.doSecKill(userid,prodid);
		boolean isSuccess= SecKill_redisByScript.doSecKill(userid,prodid);
		response.getWriter().print(isSuccess);
	}

}

单机秒杀过程

	//秒杀过程
	public static boolean doSecKill(String uid,String prodid) throws IOException {
		//1 uid和prodid非空判断
		if(uid == null || prodid == null) {
			return false;
		}

		//2 连接redis
		Jedis jedis = new Jedis("192.168.44.168",6379);

		//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 判断用户是否重复秒杀操作
		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;
	}

使用工具模拟并发多请求

使用工具ab模拟测试
CentOS6 默认安装
CentOS7需要手动安装

yum install httpd-tools

使用帮助

[root@localhost lln]# ab -help
Usage: ab [options] [http[s]://]hostname[:port]/path
Options are:
    -n requests     Number of requests to perform
    -c concurrency  Number of multiple requests to make at a time
    -t timelimit    Seconds to max. to spend on benchmarking
                    This implies -n 50000
    -s timeout      Seconds to max. wait for each response
                    Default is 30 seconds
    -b windowsize   Size of TCP send/receive buffer, in bytes
    -B address      Address to bind to when making outgoing connections
    -p postfile     File containing data to POST. Remember also to set -T
    -u putfile      File containing data to PUT. Remember also to set -T
    -T content-type Content-type header to use for POST/PUT data, eg.
                    'application/x-www-form-urlencoded'
                    Default is 'text/plain'

vim postfile 模拟表单提交参数,以&符号结尾;存放当前目录。
内容:prodid=0101&

ab -n 2000 -c 200 -k -p ~/postfile -T application/x-www-form-urlencoded http://192.168.2.115:8081/Seckill/doseckill

超卖问题(乐观锁)

在这里插入图片描述

在这里插入图片描述
利用乐观锁淘汰用户,解决超卖问题。
在这里插入图片描述

	//秒杀过程
	public static boolean doSecKill(String uid,String prodid) throws IOException {
		//1 uid和prodid非空判断
		if(uid == null || prodid == null) {
			return false;
		}

		//2 连接redis
		Jedis jedis = new Jedis("192.168.44.168",6379);

		//3 拼接key
		// 3.1 库存key
		String kcKey = "sk:"+prodid+":qt";
		// 3.2 秒杀成功用户key
		String userKey = "sk:"+prodid+":user";
		
		-----------------------------------------
		//监视库存
		jedis.watch(kcKey);
		-----------------------------------------

		//4 获取库存,如果库存null,秒杀还没有开始
		String kc = jedis.get(kcKey);
		if(kc == null) {
			System.out.println("秒杀还没有开始,请等待");
			jedis.close();
			return false;
		}

		// 5 判断用户是否重复秒杀操作
		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 秒杀过程
		-----------------------------------------
		//使用事务
		Transaction multi = jedis.multi();

		//组队操作
		multi.decr(kcKey);
		multi.sadd(userKey,uid);

		//执行
		List<Object> results = multi.exec();

		if(results == null || results.size()==0) {
			System.out.println("秒杀失败了....");
			jedis.close();
			return false;
		}

		//7.1 库存-1
		//jedis.decr(kcKey);
		//7.2 把秒杀成功用户添加清单里面
		//jedis.sadd(userKey,uid);
		-----------------------------------------

		System.out.println("秒杀成功了..");
		jedis.close();
		return true;
	}

连接超时问题(连接池)

在这里插入图片描述
节省每次连接redis服务带来的消耗,把连接好的实例反复利用。

通过参数管理连接的行为

链接池参数

  • MaxTotal:控制一个pool可分配多少个jedis实例,通过pool.getResource()来获取;如果赋值为-1,则表示不限制;如果pool已经分配了MaxTotal个jedis实例,则此时pool的状态为exhausted。
  • maxIdle:控制一个pool最多有多少个状态为idle(空闲)的jedis实例;
  • MaxWaitMillis:表示当borrow一个jedis实例时,最大的等待毫秒数,如果超过等待时间,则直接抛JedisConnectionException;
  • testOnBorrow:获得一个jedis实例的时候是否检查连接可用性(ping());如果为true,则得到的jedis实例均是可用的;

连接池工具类

package com.atguigu;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class JedisPoolUtil {
	private static volatile JedisPool jedisPool = null;

	private JedisPoolUtil() {
	}

	public static JedisPool getJedisPoolInstance() {
		if (null == jedisPool) {
			synchronized (JedisPoolUtil.class) {
				if (null == jedisPool) {
					JedisPoolConfig poolConfig = new JedisPoolConfig();
					poolConfig.setMaxTotal(200);
					poolConfig.setMaxIdle(32);
					poolConfig.setMaxWaitMillis(100*1000);
					poolConfig.setBlockWhenExhausted(true);
					poolConfig.setTestOnBorrow(true);  
				 
					jedisPool = new JedisPool(poolConfig, "192.168.44.168", 6379, 60000 );
				}
			}
		}
		return jedisPool;
	}

	public static void release(JedisPool jedisPool, Jedis jedis) {
		if (null != jedis) {
			jedisPool.returnResource(jedis);
		}
	}

}

通过连接池得到jedis对象

//2 连接redis
//Jedis jedis = new Jedis("192.168.44.168",6379);
//通过连接池得到jedis对象
JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
Jedis jedis = jedisPoolInstance.getResource();

库存遗留

ab -n 2000 -c 100 -p postfile -T 'application/x-www-form-urlencoded' http://192.168.137.1:8080/seckill/doseckill

已经秒光,可是还有库存。原因,就是乐观锁导致很多请求都失败。先点的没秒到,后点的可能秒到了。

LUA脚本

在这里插入图片描述

LUA脚本在Redis中的优势

在这里插入图片描述

在这里插入图片描述

local userid=KEYS[1]; 
local prodid=KEYS[2];
local qtkey="sk:"..prodid..":qt";
local usersKey="sk:"..prodid.":usr"; 
local userExists=redis.call("sismember",usersKey,userid);
if tonumber(userExists)==1 then 
  return 2;
end
local num= redis.call("get" ,qtkey);
if tonumber(num)<=0 then 
  return 0; 
else 
  redis.call("decr",qtkey);
  redis.call("sadd",usersKey,userid);
end
return 1;

package com.atguigu;

import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.LoggerFactory;

import ch.qos.logback.core.joran.conditional.ElseAction;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.ShardedJedisPool;
import redis.clients.jedis.Transaction;

public class SecKill_redisByScript {
	
	private static final  org.slf4j.Logger logger =LoggerFactory.getLogger(SecKill_redisByScript.class) ;

	public static void main(String[] args) {
		JedisPool jedispool =  JedisPoolUtil.getJedisPoolInstance();
 
		Jedis jedis=jedispool.getResource();
		System.out.println(jedis.ping());
		
		Set<HostAndPort> set=new HashSet<HostAndPort>();

	//	doSecKill("201","sk:0101");
	}
	
	static String secKillScript ="local userid=KEYS[1];\r\n" + 
			"local prodid=KEYS[2];\r\n" + 
			"local qtkey='sk:'..prodid..\":qt\";\r\n" + 
			"local usersKey='sk:'..prodid..\":usr\";\r\n" + 
			"local userExists=redis.call(\"sismember\",usersKey,userid);\r\n" + 
			"if tonumber(userExists)==1 then \r\n" + 
			"   return 2;\r\n" + 
			"end\r\n" + 
			"local num= redis.call(\"get\" ,qtkey);\r\n" + 
			"if tonumber(num)<=0 then \r\n" + 
			"   return 0;\r\n" + 
			"else \r\n" + 
			"   redis.call(\"decr\",qtkey);\r\n" + 
			"   redis.call(\"sadd\",usersKey,userid);\r\n" + 
			"end\r\n" + 
			"return 1" ;
			 
	static String secKillScript2 = 
			"local userExists=redis.call(\"sismember\",\"{sk}:0101:usr\",userid);\r\n" +
			" return 1";

	public static boolean doSecKill(String uid,String prodid) throws IOException {

		JedisPool jedispool =  JedisPoolUtil.getJedisPoolInstance();
		Jedis jedis=jedispool.getResource();

		 //String sha1=  .secKillScript;
		String sha1=  jedis.scriptLoad(secKillScript);
		Object result= jedis.evalsha(sha1, 2, uid,prodid);

		  String reString=String.valueOf(result);
		if ("0".equals( reString )  ) {
			System.err.println("已抢空!!");
		}else if("1".equals( reString )  )  {
			System.out.println("抢购成功!!!!");
		}else if("2".equals( reString )  )  {
			System.err.println("该用户已抢过!!");
		}else{
			System.err.println("抢购异常!!");
		}
		jedis.close();
		return true;
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/187596.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RocketMQ5.0.0消息发送

一、消息消息实体类为org.apache.rocketmq.common.message.Message&#xff0c;其主要属性如下。// 消息所属topic private String topic; // 消息Flag&#xff08;RocketMQ不作处理&#xff09;&#xff0c;即&#xff1a;用户处理 private int flag; // 扩展属性 private Map…

零代码实现EDI标准报文转换

在与客户进行沟通的时候&#xff0c;经常有客户对EDI实施很感兴趣&#xff0c;一方面是客户具有相应的IT基础和技术力量&#xff0c;并且后续可能会有更多合作伙伴的EDI接入&#xff0c;因此客户有自主实施的想法&#xff1b;另一方面也可以在一定程度上为企业节约成本。 知行…

谷歌seo排名需要的链接数量?谷歌seo排名需要多久?

本文主要分享要实现谷歌排名需要多少条英文外链&#xff0c;以及时间成本的预估。 本文由光算创作&#xff0c;有可能会被修改或剽窃&#xff0c;我们佛系对待这种行为吧。 谷歌seo排名需要的链接数量是多少&#xff1f; 答案是&#xff1a;需要1000~2000条GPB外链 为什么一…

对数据中台的梳理与思考

Gartmer:《数据中台在中国已经接近炒作的顶峰》 PowerData&#xff1a;接近顶峰?那就说明还有上升的空间嘛 本篇文章聊聊数据中台爆火背后的逻辑。 一、概念篇 1、什么是中台 中台是将系统的通用化能力进行打包整合&#xff0c;通过接口的形式赋能到外部系统&#xff0c;从而…

嵌入式Linux-线程的回收/取消/分离

1. 线程的回收 1.1 回收线程的概念 春节七天连假已经过完啦&#xff0c;也该回收一下我们放假的线程了&#xff01; 听过很多回收旧手机、旧冰箱和旧彩电…&#xff0c;那么回收线程又是什么呢&#xff1f; 在父、子进程当中&#xff0c;父进程可通过 wait()函数&#xff08;…

尚硅谷谷粒商城Rabbit MQ

文章目录1. 概述2. 相关概念2.1 RabbitMQ简介&#xff1a;2.2核心概念2.2.1 Message2.2.2 Publisher2.2.3 Exchange2.2.4 Queue2.2.5 Binding2.2.6Connection2.2.7 Channel2.2.8 Consumer2.2.9Virtual Host2.2.10Broker3.Docker安装rabbit MQ4、RabbitMQ运行机制4.1AMQP 中的消…

【信管10.2】规划识别风险及定性分析

规划识别风险及定性分析了解完风险相关的知识以及项目风险的管理过程之后&#xff0c;我们就进入到每个风险过程的学习。风险管理过程的内容并不算少&#xff0c;直逼范围、进度、成本、质量四大核心模块&#xff0c;也是我们需要重点关注的内容。当年的论文我写得就是风险管理…

IDEA中Maven打包遇到的问题

问题1 问题描述 使用Maven进行打包&#xff0c;点击package&#xff0c;Run控制台的信息出现中文乱码的情况 解决方法 -DarchetypeCataloginternal -Dfile.encodingGBK问题2 问题描述 程序能够正常运行&#xff0c;但是使用Maven对程序进行打包&#xff0c;在编译过程中出现…

注册Github账号详细教程【超详细篇 适合新手入门】

前言 &#x1f4dc; “ 作者 久绊A ” 专注记录自己所整理的Java、web、sql等&#xff0c;IT技术干货、学习经验、面试资料、刷题记录&#xff0c;以及遇到的问题和解决方案&#xff0c;记录自己成长的点滴 目录 一、GitHub的简介 1、大概介绍 2、详细介绍 二、如何注册自己…

算法训练营 day29 回溯算法 组合总和III 电话号码的字母组合

算法训练营 day29 回溯算法 组合总和III 电话号码的字母组合 组合总和III 216. 组合总和 III - 力扣&#xff08;LeetCode&#xff09; 找出所有相加之和为 n 的 k 个数的组合&#xff0c;且满足下列条件&#xff1a; 只使用数字1到9 每个数字 最多使用一次 返回 所有可能的…

16.Map系列、集合嵌套、不可变集合

目录 一.Map 1.1 Map集合概述 1.2 Map集合体系 1.3 Map集合体系特点 1.4 Map集合实现类特点 1.5 Map集合的API 1.6 Map集合的遍历方式 1.6.1 键找值的方式遍历 1.6.2 键值对的方式遍历 1.6.3 Lambda表达式的方式 1.7 HashMap 1.7.1 HashMap的特点 1.7.2 底层原理 …

python求不同分辨率图像的峰值信噪比,一文搞懂

可以使用 Python 的 NumPy 和 OpenCV 库来实现这个任务。提前准备一张图片作为素材。 文章目录什么是峰值信噪比PSNR 峰值信噪比补充说明使用 OpenCV 库来实现这个任务PSNR 的计算值受图像的亮度影响计算不同分辨率图像的 PSNRpython 求不同分辨率图像的峰值信噪比 | 其他知识点…

Java面试题:finalize的原理和工作缺点是什么

finalize是 Object 中的一个方法&#xff0c;如果子类重写它&#xff0c;垃圾回收时此方法会被调用&#xff0c;可以在其中进行资源释放和清理工作。其次将资源释放和清理放在 finalize 方法中非常不好&#xff0c;非常影响性能&#xff0c;严重时甚至会引起 OOM&#xff0c;从…

LabVIEW对NI Linux RT应用程序性能进行基准测试

LabVIEW对NI Linux RT应用程序性能进行基准测试如果应用程序具有苛刻的性能要求&#xff0c;则应为应用程序创建性能基准测试&#xff0c;以确保它满足性能要求。性能要求高度依赖于应用程序&#xff0c;应确定哪些性能指标很重要。下面介绍了典型的实时应用程序性能指标。如果…

USBIP

USBIP USB/IP 是一个开源项目&#xff0c;已合入 Kernel&#xff0c;在 Linux 环境下可以通过使用 USB/IP 远程共享 USB 设备。 USB Client&#xff1a;使用USB的终端&#xff0c;将server共享的usb设备挂载到本地。 USB Server&#xff1a;分享本地的usb设备至远程。 USBIP…

Python的入门知识汇集

创建 Python的创始人为Guido van Rossum。1989年圣诞节期间,Guido为了打发圣诞节的无趣,决心开发一个新的脚本解释程序,做为ABC 语言的一种继承。之所以选中Python(大蟒蛇的意思)作为程序的名字,是因为他是一个叫Monty Python的喜剧团体的爱好者。 什么是Pyhton Pytho…

委派模式——从SLF4J说起

作者&#xff1a;vivo 互联网服务器团队- Xiong yangxin 将某个通用解决方案包装成成熟的工具包&#xff0c;是每一个技术建设工作者必须思考且必须解决的问题。本文从业内流行的既有工具包入手&#xff0c;解析实现思路&#xff0c;沉淀一般方法。为技术建设的初学者提供一些实…

Gorm连接以及CURD实战+测试

Gorm CRUD 前言 Gorm是go的一个ORM框架&#xff0c;官方文档地址为-> GORM 指南 本文将介绍与gorm有关的CRUD操作&#xff0c;操作数据库类型为mysql数据库 数据库连接 func Open(dialector Dialector, opts …Option) (db *DB, err error) 该函数用于进行gorm连接对应…

中国市场手机出货量跌穿3亿部,苹果也顶不住了,只有三星暗爽

多家市调机构都给出了2022年中国智能手机市场的数据&#xff0c;数据虽然有些出入&#xff0c;不过都认为中国市场的手机出货量跌穿了3亿部&#xff0c;创下近10年来的新低纪录&#xff0c;国产手机尤其惨&#xff0c;而曾逆势增长的苹果也开始出现下滑。市调机构IDC给出的数据…

词法分析器Flex学习1 - Flex识别关键字

以前曾写过2篇Flex和Bison入门应用的文章&#xff1b; https://blog.csdn.net/bcbobo21cn/article/details/112343850 https://blog.csdn.net/bcbobo21cn/article/details/106193648 我只记得Flex是词法分析器&#xff0c;Bison是语法分析器&#xff1b; 只是一些入门的介绍&…