共计 3771 个字符,预计需要花费 10 分钟才能阅读完成。
本篇内容介绍了“Redis 实现秒杀的问题怎么解决”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让丸趣 TV 小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
1、秒杀逻辑
秒杀:解决计数器和人员记录的事务操作
1.uid 和 proid 非空判断
2. 连接 redis
3. 拼接 key
库存 key
秒杀成功用户 key
4. 获取库存,如果库存为 null,秒杀还没开始
5. 判断用户是否重复秒杀操作
6. 判断商品数量,库存数量小于 1,秒杀结束
7. 秒杀过程
库存 -1
把秒杀成功用户添加清单里面
2、存在问题 2.1、连接超时
原因:由于大量创建连接,十分消耗性能,并且有时获取连接不及时,出现连接超时的情况
2.2、超卖
在并发的情况下发生的,就是在输出没有库存(秒杀结束)后还有商品售出导致库存数量为负数。
2.3、库存遗留
使用乐观锁解决问题 2 之后,出现问题 3
如果库存数量相对并发更多,由于使用乐观锁,第一个用户秒杀成功后会修改库存键的版本号,其他抢到的用户会因为版本号不同导致无法继续购买,就会有库存遗留问题
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 配置,然后注入对象即可)
dependency
groupId org.springframework.boot /groupId
artifactId spring-boot-starter-data-redis /artifactId /dependency dependency
groupId redis.clients /groupId
artifactId jedis /artifactId
version 3.2.0 /version /dependency
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 Object results = multi.exec();if(results == null || results.size()==0) {
System.out.println( 秒杀失败了....
jedis.close();
return false;}
3.3、乐观锁导致的库存遗留问题
使用 Lua 嵌入式脚本语言
将复杂的或者多步的 Redis 操作,写为一个脚本,一次提交给 Redis 运行,减少反复连接 reids 的次数。提升性能。
LUA 脚本是类似 redis 事务,有一定的原子性,不会被其他命令插队,可以完成 redis 事务性的操作
LUA 脚本功能,在 Redis 2.6 以上的版本才可以使用
利用 lua 脚本淘汰用户,解决超卖问题。
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;
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);
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 实现秒杀的问题怎么解决”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注丸趣 TV 网站,丸趣 TV 小编将为大家输出更多高质量的实用文章!