Redis秒杀案例分析 Redis数据库中记录商品库存和秒杀成功人员名单
prodid: 商品号
高并发下超卖问题
如果不加锁的情况下 多个用户在同一时间拿到的库存量是一样的 然后他们都去减少这个库存 最终导致库存数超卖 为负值
利用Redis默认的乐观锁淘汰用户解决超卖问题
jedis.watch(qtkey); 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(); multi.decr(qtkey); 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.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(); } } 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; .... .... ....