Redis秒杀案例分析
Redis数据库中记录商品库存和秒杀成功人员名单
prodid: 商品号
高并发下超卖问题
如果不加锁的情况下 多个用户在同一时间拿到的库存量是一样的 然后他们都去减少这个库存 最终导致库存数超卖 为负值
利用Redis默认的乐观锁淘汰用户解决超卖问题
//增加乐观锁
jedis.watch(qtkey);
//3.判断库存
String qtkeystr = jedis.get(qtkey);
if(qtkeystr==null || "".equals(qtkeystr.trim())) {
System.out.println("未初始化库存");
jedis.close();
return false ;
}
int qt = Integer.parseInt(qtkeystr);
if(qt<=0) {
System.err.println("已经秒光");
jedis.close();
return false;
}
//增加事务
Transaction multi = jedis.multi();
//4.减少库存
//jedis.decr(qtkey);
multi.decr(qtkey);
//5.加人
//jedis.sadd(usrkey, uid);
multi.sadd(usrkey, uid);
//执行事务
List<Object> list = multi.exec();
//判断事务提交是否失败
if(list==null || list.size()==0) {
System.out.println("秒杀失败");
jedis.close();
return false;
}
System.err.println("秒杀成功");
jedis.close();
`已经秒杀完 但仍然有库存的情况`
已经秒光,可是还有库存。原因,就是乐观锁导致很多请求都失败。先点的没秒到,后点的可能秒到了
使用Lua脚本
将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis的次数。提升性能。
LUA脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作。
但是注意redis的lua脚本功能,只有在Redis 2.6以上的版本才可以使用。
利用lua脚本淘汰用户,解决超卖问题。
redis 2.6版本以后,通过lua脚本解决争抢问题,实际上是redis 利用其单线程的特性,用任务队列的方式解决多任务并发问题。
local userid=KEYS[1];
local prodid=KEYS[2];
local qtkey="sk:"..prodid..":qt";
local usersKey="sk:"..prodid.":usr';
local userExists=redis.call("sismember",usersKey,userid);
if tonumber(userExists)==1 then
return 2;
end
local num= redis.call("get" ,qtkey);
if tonumber(num)<=0 then
return 0;
else
redis.call("decr",qtkey);
redis.call("sadd",usersKey,userid);
end
return 1;
SpringBoot配置Redis连接池
连接超时问题 – 通过连接池解决
#首先是redis各个参数的配置,在 application-dev.properties中配置如下:
#redis配置
redis.server.host=192.168.50.162
redis.server.port=6379
redis.server.password=password
redis.server.timeOut=5000
redis.server.maxIdle=50
redis.server.maxWaitMillis=5000
redis.server.maxTotal=500
对 redis 配置参数进行读取和绑定,配置属性注入到 JedisProperties
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = JedisProperties.JEDIS_PREFIX)
public class JedisProperties {
public static final String JEDIS_PREFIX = "redis.server";
private String host;
private int port;
private String password;
private int maxTotal;
private int maxIdle;
private int maxWaitMillis;
private int timeOut;
public int getTimeOut() {
return timeOut;
}
public void setTimeOut(int timeOut) {
this.timeOut = timeOut;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public int getMaxTotal() {
return maxTotal;
}
public void setMaxTotal(int maxTotal) {
this.maxTotal = maxTotal;
}
public int getMaxIdle() {
return maxIdle;
}
public void setMaxIdle(int maxIdle) {
this.maxIdle = maxIdle;
}
public int getMaxWaitMillis() {
return maxWaitMillis;
}
public void setMaxWaitMillis(int maxWaitMillis) {
this.maxWaitMillis = maxWaitMillis;
}
}
配置了 Redis 连接池之后,将 Redis 连接池 注入到 RedisClient 中,并生成 RedisClient Bean
import com.mljr.auth.config.properties.JedisProperties;
import com.mljr.auth.util.RedisClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
@Configuration
@EnableConfigurationProperties(JedisProperties.class)
@ConditionalOnClass(RedisClient.class)
public class JedisConfig {
private Logger logger = LoggerFactory.getLogger(JedisConfig.class);
@Autowired
private JedisProperties prop;
@Bean(name = "jedisPool")
public JedisPool jedisPool() {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(prop.getMaxTotal());
config.setMaxIdle(prop.getMaxIdle());
config.setMaxWaitMillis(prop.getMaxWaitMillis());
return new JedisPool(config, prop.getHost(), prop.getPort(), prop.getTimeOut(), prop.getPassword());
}
@Bean
@ConditionalOnMissingBean(RedisClient.class)
public RedisClient redisClient(@Qualifier("jedisPool") JedisPool pool) {
logger.info("初始化……Redis Client==Host={},Port={}", prop.getHost(), prop.getPort());
RedisClient redisClient = new RedisClient();
redisClient.setJedisPool(pool);
return redisClient;
}
}
配置一些常用的 redis 的操作:
import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; public class RedisClient { private JedisPool jedisPool; public void set(String key, Object value) { Jedis jedis = null; try { jedis = jedisPool.getResource(); jedis.set(key, value.toString()); }catch (Exception e){ e.printStackTrace(); }finally { jedis.close(); } } /** * 设置过期时间 * @param key * @param value * @param exptime * @throws Exception */ public void setWithExpireTime(String key, String value, int exptime) { Jedis jedis = null; try { jedis = jedisPool.getResource(); jedis.set(key, value, "NX", "EX", 300); } catch (Exception e){ e.printStackTrace(); }finally { jedis.close(); } } public String get(String key) { Jedis jedis = null; try { jedis = jedisPool.getResource(); return jedis.get(key); } catch (Exception e){ e.printStackTrace(); }finally { if (jedis != null) jedis.close(); } return null; } public JedisPool getJedisPool() { return jedisPool; } public void setJedisPool(JedisPool jedisPool) { this.jedisPool = jedisPool; } }
大功告成,使用注解的方式引入就可以使用了
@Autowired
private RedisClient redisClient;
....
....
....