使用redis的事务完成秒杀案例,流程图如下:
问题一:连接超时
连接超时问题可以使用redis连接池来进行解决
public static JedisPool getJedisPoolInstance() {
if (null == jedisPool) {
synchronized (JedisPoolUtil.class) {
if (null == jedisPool) {
JedisPoolConfig poolConfig = new JedisPoolConfig();
// 设置连接池中一个pool可以设置多少个jedis实例
poolConfig.setMaxTotal(200);
// 设置一个pool中最多有多少个状态为idle(空闲)
poolConfig.setMaxIdle(32);
// 表示当borrow一个jedis实例时,最大的等待毫秒数,如果超过等待时间,则直接抛JedisConnectionException;
poolConfig.setMaxWaitMillis(100*1000);
poolConfig.setBlockWhenExhausted(true);
// 获得一个jedis实例的时候是否检查连接可用性(ping());如果为true,则得到的jedis实例均是可用的;
poolConfig.setTestonBorrow(true); // ping PONG
jedisPool = new JedisPool(poolConfig, "192.168.119.130", 6379, 60000 );
}
}
}
return jedisPool;
}
问题二:超卖问题
超卖问题可以使用redis的事务watch(乐观锁)解决超卖问题
// 创建事务(使用watch乐观锁的时候会有库存遗留的问题) Transaction multi = jedis.multi(); // 组队操作 // 将商品数量减一 multi.decr(productKey); // 将秒杀用户key入库 multi.sadd(userKey, uid); // 执行 List
问题三:库存遗留问题
乐观锁导致很多请求都失败,先点的没秒到,后点的可能秒到了,使用LUA脚本解决库存遗留问题。
LUA脚本在Redis中的优势
将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis的次数。提升性能。LUA脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作。
static String secKillscript =
"local userid=KEYS[1];rn" +
"local prodid=KEYS[2];rn" +
"local qtkey='sk:'..prodid..":qt";rn" +
"local usersKey='sk:'..prodid..":usr";rn" +
"local userExists=redis.call("sismember",usersKey,userid);rn" +
"if tonumber(userExists)==1 then rn" +
" return 2;rn" +
"endrn" +
"local num= redis.call("get" ,qtkey);rn" +
"if tonumber(num)<=0 then rn" +
" return 0;rn" +
"else rn" +
" redis.call("decr",qtkey);rn" +
" redis.call("sadd",usersKey,userid);rn" +
"endrn" +
"return 1" ;
最终代码实现如下:
// 定义两段Lua脚本(使用Lua脚本可以解决乐观锁带来的库存遗留问题)
static String secKillscript =
"local userid=KEYS[1];rn" +
"local prodid=KEYS[2];rn" +
"local qtkey='sk:'..prodid..":qt";rn" +
"local usersKey='sk:'..prodid..":usr";rn" +
"local userExists=redis.call("sismember",usersKey,userid);rn" +
"if tonumber(userExists)==1 then rn" +
" return 2;rn" +
"endrn" +
"local num= redis.call("get" ,qtkey);rn" +
"if tonumber(num)<=0 then rn" +
" return 0;rn" +
"else rn" +
" redis.call("decr",qtkey);rn" +
" redis.call("sadd",usersKey,userid);rn" +
"endrn" +
"return 1" ;
static String secKillscript2 =
"local userExists=redis.call("sismember","{sk}:0101:usr",userid);rn" +
" 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;
}



