Redis事务-秒杀并发模拟
使用工具ab模拟测试:yum install httpd-tools
ab --help:使用信息
ab -n 1000 -c 100 http://localhost8080/SecKill :一个1000请求中有100个并发操作
vim postfile 模拟表单提交参数,以&符号结尾;存放当前目录,内容:prodid=0101&
ab -n 1000 -c 100 -p ~/postfile -T application/x-www-form-urlencoded http://172.22.3.22:8080/seckill/getThing
存在问题:
并发导致库存出现负值
连接超时问题
一、连接超时问题
连接池:节省每次连接redis服务带来的消耗,把连接好的实例反复使用,通过参数管理连接的行为
二、超卖问题
利用乐观锁淘汰用户,解决超卖问题。
package com.atguigu.seckill.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.List;
import java.util.Random;
@RestController
@RequestMapping("/seckill")
public class SecKController {
@Autowired
private RedisTemplate redisTemplate;
@GetMapping
public String test(){
redisTemplate.opsForValue().set("name", "lucky");
String name = (String)redisTemplate.opsForValue().get("name");
System.out.println(name);
return name;
}
//秒杀过程
@PostMapping("/getThing")
public boolean getThing(@RequestParam String prodid){
redisTemplate.setEnableTransactionSupport(true);
String uid = new Random().nextInt(50000) +"" ;
//1 uid和prodid非空判断
if(uid == null || prodid == null){
return false;
}
//2、拼接key
//2.1 秒杀库存key
String kcKey = "sk:" + prodid +":qt";
//2.2 秒杀成功用户key
String userKey = "sk:" + prodid + ":user";
//6 秒杀过程
SessionCallback<Object> callback = new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
// 监视库存
redisTemplate.watch(kcKey);
//3 获取库存,如果库存null,秒杀没有开始
Object kc = redisTemplate.opsForValue().get(kcKey);
if(kc == null){
System.out.println("秒杀还没开始,请等待");
return false;
}
String kcnum = String.valueOf(kc);
//4 判断用户是否重复秒杀操作
if(redisTemplate.opsForSet().isMember(userKey, uid)){
System.out.println("已经秒杀成功,不能重复秒杀");
return false;
}
//5 判断如果商品数量<=0,秒杀结束
if(Integer.parseInt(kcnum)<=0){
System.out.println("秒杀已结束");
return false;
}
// 开启事务
operations.multi();
operations.opsForValue().decrement(kcKey);
operations.opsForSet().add(userKey, uid);
// 执行事务
List<Object> results= operations.exec();
if(results == null || results.size() == 0){
return false;
}
return true;
}
};
boolean results = (boolean)redisTemplate.execute(callback);
if(results == false){
System.out.println("秒杀失败.......");
return results;
}
System.out.println("秒杀成功了");
return results;
}
}
三、库存遗留问题
乐观锁造成库存遗留问题:比如2000个人购买500件产品,恰好一个人买了把版本号从1.0修改为1.1,剩余1999个人都无法购买,导致还剩余499件商品库存遗留。【悲观锁】
解决:Lua脚本语言,将复杂或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis次数,提升性能。
Lua脚本是类似redis事务,有一定的原子性,不会被其它命令插队,可以完成一些redis事务的操作。
利用lua脚本淘汰用户,解决超卖问题。实际上redis利用其单线程特性,用任务队列的方式解决多任务并发问题