0

0

一起聊聊Redis实现秒杀的问题

WBOY

WBOY

发布时间:2022-05-27 11:40:34

|

3389人浏览过

|

来源于CSDN

转载

本篇文章给大家带来了关于redis的相关知识,其中主要介绍了关于实现秒杀的相关内容,包括了秒杀逻辑、存在的链接超时、超卖和库存遗留的问题,下面一起来看一下,希望对大家有帮助。

一起聊聊Redis实现秒杀的问题

推荐学习:Redis视频教程

1、秒杀逻辑

秒杀:解决计数器和人员记录的事务操作

  1. 1.uid和proid非空判断
  2. 2.连接redis
  3. 3.拼接key
    • 库存key
    • 秒杀成功用户key
  4. 4.获取库存,如果库存为null,秒杀还没开始
  5. 5.判断用户是否重复秒杀操作
  6. 6.判断商品数量,库存数量小于1,秒杀结束
  7. 7.秒杀过程
    • 库存-1
    • 把秒杀成功用户添加清单里面

2、存在问题

2.1、连接超时

原因:由于大量创建连接,十分消耗性能,并且有时获取连接不及时,出现连接超时的情况

2.2、超卖

在并发的情况下发生的,就是在输出没有库存(秒杀结束)后还有商品售出导致库存数量为负数。
在这里插入图片描述

2.3、库存遗留

使用乐观锁解决问题2之后,出现问题3

如果库存数量相对并发更多,由于使用乐观锁,第一个用户秒杀成功后会修改库存键的版本号,其他抢到的用户会因为版本号不同导致无法继续购买,就会有库存遗留问题

Multiavatar
Multiavatar

Multiavatar是一个免费开源的多元文化头像生成器,可以生成高达120亿个虚拟头像

下载

3、解决

3.1、连接超时

使用连接池,工具类如下:

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, "127.0.0.1", 6379, 60000);
				}
			}
		}
		return jedisPool;
	}}//使用JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();Jedis jedis = jedisPoolInstance.getResource();

springBoot版本(pom.xml引入,application.yml配置,然后注入对象即可)


    org.springframework.boot
    spring-boot-starter-data-redis
    redis.clients
    jedis
    3.2.0
spring:
  redis:
    host: 127.0.0.1    port: 6379
    database: 0
    timeout: 1800000
    lettuce:
      pool:
        max-active: 20
        max-wait: -1
        max-idle: 5
        min-idle: 0
    @Autowired
    private RedisTemplate redisTemplate;

3.2、超卖问题

使用Redis事务,乐观锁 + watch

//监视库存
jedis.watch(kcKey);//中间代码忽略

//7 秒杀过程
//使用事务
Transaction multi = jedis.multi();//组队操作
multi.decr(kcKey);multi.sadd(userKey,uid);//执行
List results = multi.exec();if(results == null || results.size()==0) {
    System.out.println("秒杀失败了....");
    jedis.close();
    return false;}

3.3、乐观锁导致的库存遗留问题

使用Lua嵌入式脚本语言

  1. 将复杂的或者多步的 Redis 操作,写为一个脚本,一次提交给Redis运行,减少反复连接 reids的次数。提升性能。
  2. LUA脚本是类似 redis 事务,有一定的原子性,不会被其他命令插队,可以完成redis事务性的操作
  3. LUA脚本功能,在Redis 2.6以上的版本才可以使用
  4. 利用 lua 脚本淘汰用户,解决超卖问题。
  5. redis 2.6 版本以后,通过 lua 脚本解决争抢问题,实际上是 redis 利用其单线程的特性,用任务队列的方式解决多任务并发问题
local userid=KEYS[1];				//1、2行定义两个变量,					
local prodid=KEYS[2];
local qtkey="sk:"..prodid..":qt";	//3,4行定义拼接key
local usersKey="sk:"..prodid..":usr";
local userExists=redis.call("sismember",usersKey,userid); //5-8,判断用户是否存在,不存在return 2
if tonumber(userExists)==1 then
    return2;
end
local num=redis.call("get",qtkey);	//9-11,判断商品是否存在
if tonumber(num)<=0 then
    return 0;
else								//12-15,用户和商品操作
    redis.call("decr",qtkey);
    redis.call("sadd",usersKey,userid);
end
return1;  							//最后一行return 1;  秒杀成功

完整代码如下:

// 定义两段Lua脚本(使用Lua脚本可以解决乐观锁带来的库存遗留问题)
	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" ;
 
 
	public static boolean doSecKill(String uid,String prodid) throws IOException {
 
		JedisPool jedispool =  JedisPoolUtil.getJedisPoolInstance();
		Jedis jedis=jedispool.getResource();
		jedis.select(2);
 
		// 通过jedis的scriptLoad方法加载Lua脚本
		String sha1=  jedis.scriptLoad(secKillScript);
		//通过jedis的evalsha方法调用Lua脚本
		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;
	}

推荐学习:Redis视频教程

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
go语言 注释编码
go语言 注释编码

本专题整合了go语言注释、注释规范等等内容,阅读专题下面的文章了解更多详细内容。

2

2026.01.31

go语言 math包
go语言 math包

本专题整合了go语言math包相关内容,阅读专题下面的文章了解更多详细内容。

1

2026.01.31

go语言输入函数
go语言输入函数

本专题整合了go语言输入相关教程内容,阅读专题下面的文章了解更多详细内容。

1

2026.01.31

golang 循环遍历
golang 循环遍历

本专题整合了golang循环遍历相关教程,阅读专题下面的文章了解更多详细内容。

0

2026.01.31

Golang人工智能合集
Golang人工智能合集

本专题整合了Golang人工智能相关内容,阅读专题下面的文章了解更多详细内容。

1

2026.01.31

2026赚钱平台入口大全
2026赚钱平台入口大全

2026年最新赚钱平台入口汇总,涵盖任务众包、内容创作、电商运营、技能变现等多类正规渠道,助你轻松开启副业增收之路。阅读专题下面的文章了解更多详细内容。

76

2026.01.31

高干文在线阅读网站大全
高干文在线阅读网站大全

汇集热门1v1高干文免费阅读资源,涵盖都市言情、京味大院、军旅高干等经典题材,情节紧凑、人物鲜明。阅读专题下面的文章了解更多详细内容。

73

2026.01.31

无需付费的漫画app大全
无需付费的漫画app大全

想找真正免费又无套路的漫画App?本合集精选多款永久免费、资源丰富、无广告干扰的优质漫画应用,涵盖国漫、日漫、韩漫及经典老番,满足各类阅读需求。阅读专题下面的文章了解更多详细内容。

67

2026.01.31

漫画免费在线观看地址大全
漫画免费在线观看地址大全

想找免费又资源丰富的漫画网站?本合集精选2025-2026年热门平台,涵盖国漫、日漫、韩漫等多类型作品,支持高清流畅阅读与离线缓存。阅读专题下面的文章了解更多详细内容。

19

2026.01.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
进程与SOCKET
进程与SOCKET

共6课时 | 0.4万人学习

Redis+MySQL数据库面试教程
Redis+MySQL数据库面试教程

共72课时 | 6.5万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号