Java 使用Redis实现秒杀功能

秒杀功能

秒杀场景现在已经非常常见了,各种电商平台都有秒杀的产品,接下来我们模拟一个秒杀的项目,最终能够确保高并发下的线程安全。界面比较简单,但是功能基本实现。

界面

Java 使用Redis实现秒杀功能_第1张图片

点击“秒杀点我”按钮后台就会输出秒杀结果。

第一版

使用Redis缓存数据库,使用一个key-value存储秒杀商品数量,使用set集合存储秒杀成功的用户。我们以商品0101为示例,设置商品的初始数量为200件。不考虑并发问题,实现功能。

html、jsp、servlet文件不重要省略。

package com.redis.secondskill;

import java.util.List;

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

public class SS0 {
	public static boolean doSecKill(String uid,String prodid) {
		JedisPool jedisPool = JedisPollTool.getInstance();
		Jedis jedis = jedisPool.getResource();
		String productCountStr = "sec:"+prodid+":count";
		String productUserStr = "sec:"+prodid+":user";
		String productCount = jedis.get(productCountStr);
		if(null == productCount) {
			System.out.println("秒杀还没有开始");
			JedisPollTool.distroy(jedisPool, jedis);
			return false;
		}
		if(jedis.sismember(productUserStr, uid)) {
			System.out.println(uid + "用户已经秒杀成功");
			JedisPollTool.distroy(jedisPool, jedis);
			return false;
		}
		int prodCount = Integer.parseInt(productCount);
		if(prodCount <= 0) {
			System.out.println("秒杀结束");
			JedisPollTool.distroy(jedisPool, jedis);
			return false;
		}
		jedis.decr(productCountStr);
		jedis.sadd(productUserStr, uid);
		
		JedisPollTool.distroy(jedisPool, jedis);
		System.out.println(uid + "秒杀成功");
		return true;
	}
}

使用linux httpd-tools工具进行并发测试。

ab -n 1000 -c 200 -p /test/file.txt -T "application/x-www-form-urlencoded" 192.168.0.101:8080/redis-demo/ss

结果

Java 使用Redis实现秒杀功能_第2张图片

从结果大致来看,没有什么问题,来查看一个后台Redis的数据

秒杀的结果里面居然有负数,证明卖超了。

第二版

使用Redis的事务,保证没有超卖的情况发生。

package com.redis.secondskill;

import java.util.List;

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

public class SS1 {
	public static boolean doSecKill(String uid,String prodid) {
		JedisPool jedisPool = JedisPollTool.getInstance();
		Jedis jedis = jedisPool.getResource();
		String productCountStr = "sec:"+prodid+":count";
		String productUserStr = "sec:"+prodid+":user";
		jedis.watch(productCountStr);	//开始监视
		String productCount = jedis.get(productCountStr);
		if(null == productCount) {
			System.out.println("秒杀还没有开始");
			JedisPollTool.distroy(jedisPool, jedis);
			return false;
		}
		if(jedis.sismember(productUserStr, uid)) {
			System.out.println(uid + "用户已经秒杀成功");
			JedisPollTool.distroy(jedisPool, jedis);
			return false;
		}
		int prodCount = Integer.parseInt(productCount);
		if(prodCount <= 0) {
			System.out.println("秒杀结束");
			JedisPollTool.distroy(jedisPool, jedis);
			return false;
		}
		Transaction transaction = jedis.multi();
		transaction.decr(productCountStr);
		transaction.sadd(productUserStr, uid);
		List exec = transaction.exec();
		
		
		if(exec == null || exec.size() == 0) {
			System.out.println("秒杀失败,稍后重试");
			JedisPollTool.distroy(jedisPool, jedis);
			return false;
		}
		JedisPollTool.distroy(jedisPool, jedis);
		System.out.println(uid + "秒杀成功");
		return true;
	}
}
 
  

结果

Java 使用Redis实现秒杀功能_第3张图片

由于使用了watch和事务,每次的并发线程访问中只有一个线程能够提交成功,可以保证不出现超卖的现象,但是对于一些用户来说是极其不公平的。

第三版

使用Lua脚本来实现,因为Redis是单线程的,又是C语言编写的,可以使用Lua调用Redis的命令,Lua会具有排他性,所以能够保证安全。

package com.redis.secondskill;

import java.util.HashSet;
import java.util.Set;

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

public class SS2 {
	
	static String luaScript ="local userid=KEYS[1];\r\n" + 
	"local prodid=KEYS[2];\r\n" + 
	"local qtkey='sec:'..prodid..\":count\";\r\n" + 
	"local usersKey='sec:'..prodid..\":user\";\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) {
		JedisPool jedisPool = JedisPollTool.getInstance();
		Jedis jedis = jedisPool.getResource();
		String sha1 = jedis.scriptLoad(luaScript);
		
		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(uid + "抢购成功!!!!");
		}else if("2".equals( reString )  )  {
				System.err.println("该用户已抢过!!");
		}else{
				System.err.println("抢购异常!!");
		}
		JedisPollTool.distroy(jedisPool, jedis);
 		return true;
 		
	}
}

结果

Java 使用Redis实现秒杀功能_第4张图片

这才是我们最希望看到的结果!

你可能感兴趣的:(Redis)