redis解决高并发抢购
对于前后端不分离的程序可以用悲观锁,对于前后端分离的程序可以用redis分布式锁
分布式锁 setnx key value,将key设置为value,当键不存在时,才能成功,若键存在,什么也不做,成功返回1,失败返回0 。 SETNX实际上就是SET IF NOT Exists的缩写
图中当redis分布式锁为1的时候就锁起来,其他线程在外面隔几秒休眠,当起来的时候发现锁为0然后进入项目再上锁,就这样循环
1.前后端不分离抢购:
加上悲观锁synchronized后100个库存有100个人抢到
如果不加的话会有好几百个人抢到
在启动类中初始化商品的key和value:
@SpringBootApplication
public class RedisQgApplication {
@Autowired
private StringRedisTemplate stringRedisTemplate;
//@Bean 可能是因为希望它在 Spring 容器初始化时执行,而不仅仅是一个普通的方法调用。
// 如果不加 @Bean,这个方法就不会被 Spring 容器识别并调用。
//初始化被抢购商品
@Bean
public void init(){
stringRedisTemplate.opsForValue().set("goods:1001", "100");
}
public static void main(String[] args) {
SpringApplication.run(RedisQgApplication.class, args);
}
}
在controller里设置抢购程序:
思路:
- 判断是否购买过该商品
- 判断是否还有库存
- 库存减一
- 添加用户购买标识
@RestController
@RequestMapping("/index")
public class IndexController {
int userId=100;
int goodsId=1001;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@GetMapping("/qg")
public synchronized String qg(){
userId++;
//判断是否已经购买过该商品
if (stringRedisTemplate.hasKey("user:"+userId+":"+goodsId)){
//user在redis中有key就代表已经购买过了
return "已经购买过该商品";
}
//判断是否还有库存
Integer count = Integer.parseInt(stringRedisTemplate.opsForValue().get("goods:"+goodsId));
if (count<=0){
return "库存不足";
}
//减去库存
count--;
stringRedisTemplate.opsForValue().set("goods:"+goodsId, count.toString());
//购买标识
stringRedisTemplate.opsForValue().set("user:"+userId+":"+goodsId, "0");
return "抢购成功";
}
}
2.前后端分离抢购:
使用SETNX:分布式锁 setnx key value,将key设置为value,当键不存在时,才能成功,若键存在,什么也不做,成功返回1,失败返回0 。 SETNX实际上就是SET IF NOT Exists的缩写
controller:
思路:
- 先判断redis分布式锁上了没,上锁了就休眠
- 判断是否购买过该商品,买过的走
- 判断是否还有库存,没库存了走
- 库存减一
- 添加用户购买标识,购买完成走
- 购买成功后释放锁
@RestController
@RequestMapping("/index")
public class IndexController {
int userId=100;
int goodsId=1001;
@Autowired
private RedisUtil redisUtil;
@GetMapping("/qg")
public synchronized String qg(){
userId++;
//设置一个redis锁上锁时休眠
while (redisUtil.lock("lock_:"+goodsId,10L)){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判断是否已经购买过该商品
if (redisUtil.getStr("user:"+userId+":"+goodsId)!=null){
//user在redis中有key就代表已经购买过了
return "已经购买过该商品";
}
String msg;
//判断是否还有库存
Integer count = Integer.parseInt(redisUtil.getStr("goods:"+goodsId));
if (count<=0){
msg="库存不足";
}else {
//减去库存
count--;
redisUtil.setStr("goods:"+goodsId, count.toString());
//购买标识
redisUtil.setStr("user:"+userId+":"+goodsId, "0");
//购买成功后释放锁
redisUtil.unLock("lock_:"+goodsId);
msg="抢购成功";
}
return msg;
}
}
工具类:
这个工具类中setnx锁
@Component
public class RedisUtil {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Autowired
RedisTemplate<Object, Object> redisTemplate;
@Resource(name = "stringRedisTemplate")
ValueOperations<String, String> valOpsStr;
@Resource(name = "redisTemplate")
ValueOperations<Object, Object> valOpsObj;
/**
* 根据指定key获取String
* @param key
* @return
*/
public String getStr(String key){
return valOpsStr.get(key);
}
/**
* 设置Increment缓存
* @param key
*/
public long getIncrement(String key){
return valOpsStr.increment(key);
}
/**
* 设置Increment缓存
* @param key
* @param val
*/
public long setIncrement(String key, long val){
return valOpsStr.increment(key,val);
}
/**
* 设置Str缓存
* @param key
* @param val
*/
public void setStr(String key, String val){
valOpsStr.set(key,val);
}
/***
* 设置Str缓存
* @param key
* @param val
* @param expire 超时时间
*/
public void setStr(String key, String val,Long expire){
valOpsStr.set(key,val,expire, TimeUnit.MINUTES);
}
/**
* 删除指定key
* @param key
*/
public void del(String key){
stringRedisTemplate.delete(key);
}
/**
* 根据指定o获取Object
* @param o
* @return
*/
public Object getObj(Object o){
return valOpsObj.get(o);
}
/**
* 设置obj缓存
* @param o1
* @param o2
*/
public void setObj(Object o1, Object o2){
valOpsObj.set(o1, o2);
}
/**
* 删除Obj缓存
* @param o
*/
public void delObj(Object o){
redisTemplate.delete(o);
}
/***
* 加锁的方法
* @return
*/
public boolean lock(String key,Long expire){
RedisConnection redisConnection=redisTemplate.getConnectionFactory().getConnection();
//设置序列化方法
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
if(redisConnection.setNX(key.getBytes(),new byte[]{1})){
redisTemplate.expire(key,expire,TimeUnit.SECONDS);
redisConnection.close();
return true;
}else{
redisConnection.close();
return false;
}
}
/***
* 解锁的方法
* @param key
*/
public void unLock(String key){
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.delete(key);
}
}
启动类:
同时启动类中的创建key也得替换成redisutil的
@SpringBootApplication
public class RedisQgApplication {
@Autowired
private RedisUtil redisUtil;
//@Bean 可能是因为希望它在 Spring 容器初始化时执行,而不仅仅是一个普通的方法调用。
// 如果不加 @Bean,这个方法就不会被 Spring 容器识别并调用。
//初始化被抢购商品
@Bean
public void init(){
redisUtil.setStr("goods:1001", "100");
}
public static void main(String[] args) {
SpringApplication.run(RedisQgApplication.class, args);
}
}
3.jmeter应用模拟高并发:
下载apache-jmeter后点开start.bat不要关,等几秒就会弹出这个程序
点击新建
新建线程组
添加查看结果树:这是用来查看结果的