背景
雙11逝撬,618等各大電商節(jié)越來越多浴骂,最吸引人的莫不是秒殺商品的活動。那秒殺活動的背后宪潮,又是用什么技術(shù)實現(xiàn)的呢溯警?是不是很好奇趣苏?另外不懂秒殺的,會被面試官虐嗎梯轻?不要好奇食磕,不要擔(dān)心,小編給你介紹一個小案例喳挑,開拓下思維彬伦。
環(huán)境
- jdk1.8+
- spring boot
- redis
思路
- 模擬用戶發(fā)起秒殺操作,首先判斷庫存是否足夠伊诵,如果庫存為0单绑,則秒殺結(jié)束
2.如果庫存足夠,則判斷用戶是否拿到該商品的redis分布式鎖日戈,如果拿到鎖询张,則進入下單業(yè)務(wù)邏輯孙乖,如果沒拿到鎖浙炼,則30秒后再次進入秒殺邏輯 3. 進入下單邏輯后,還需要再次判斷下庫存唯袄,庫存為0弯屈,則秒殺結(jié)束,反之生成訂單恋拷。(這里是為了進一步防止超賣發(fā)生)
搭建
生成springboot項目资厉,pom引入相關(guān)依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
application.yml添加redis相關(guān)配置
各位可以根據(jù)實際情況來配置
server:
port: 6004
spring:
application:
name: spring-boot-ms
# Redis配置
redis:
host: 你自己的redis的ip地址
# Redis服務(wù)器連接端口
port: 你自己的redis的端口
# Redis服務(wù)器連接密碼(默認(rèn)為空)
password: liyajie@2021
timeout: 30000
# 連接池最大連接數(shù)(使用負(fù)值表示沒有限制)
maxTotal: 50
# 連接池中的最大空閑連接
maxIdle: 10
numTestsPerEvictionRun: 1024
timeBetweenEvictionRunsMillis: 30000
minEvictableIdleTimeMillis: 1800000
softMinEvictableIdleTimeMillis: 10000
# 連接池最大阻塞等待時間(使用負(fù)值表示沒有限制)
maxWaitMillis: 1500
testOnBorrow: true
testWhileIdle: true
blockWhenExhausted: false
JmxEnabled: true
定義redis配置類
@Configuration
@PropertySource("classpath:application.yml")
public class RedisConfig {
@Value("${redis.host}")
private String host;
@Value("${redis.port}")
private int port;
@Value("${redis.timeout}")
private int timeout;
@Value("${redis.password}")
private String password;
@Value("${redis.maxIdle}")
private int maxIdle;
@Value("${redis.maxTotal}")
private int maxTotal;
@Value("${redis.maxWaitMillis}")
private int maxWaitMillis;
@Value("${redis.blockWhenExhausted}")
private Boolean blockWhenExhausted;
@Value("${redis.JmxEnabled}")
private Boolean JmxEnabled;
@Bean
public JedisPool jedisPoolFactory() {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(maxIdle);
jedisPoolConfig.setMaxTotal(maxTotal);
jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
// 連接耗盡時是否阻塞, false報異常,true阻塞直到超時, 默認(rèn)true
jedisPoolConfig.setBlockWhenExhausted(blockWhenExhausted);
// 是否啟用pool的jmx管理功能, 默認(rèn)true
jedisPoolConfig.setJmxEnabled(JmxEnabled);
jedisPoolConfig.setTestOnBorrow(true);
JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout, password);
return jedisPool;
}
}
定義測試controller類
@RestController
@Slf4j
public class MsController {
@Autowired
private RedisUtils redisUtils;
@Autowired
MsService msService;
//總庫存
private long nKuCuen = 0;
//商品key名字
private String shangpingKey = "computer_key";
//獲取鎖的超時時間 秒
private int timeout = 30 * 1000;
/**
* 設(shè)置分布式鎖
* @param
* @return boolean
* @author liyajie
* @createTime 2021/12/16 14:52
**/
@GetMapping("/setnx/{key}/{val}")
public boolean setnx(@PathVariable String key, @PathVariable String val) {
return redisUtils.setnx(key, val);
}
/**
* 刪除分布式鎖
* @param
* @return int
* @author liyajie
* @createTime 2021/12/16 14:52
**/
@GetMapping("/delnx/{key}/{val}")
public int delnx(@PathVariable String key, @PathVariable String val) {
return redisUtils.delnx(key, val);
}
/**
* 搶單
* @param
* @return List<String>
* @author liyajie
* @createTime 2021/12/16 14:54
**/
@GetMapping("/qiangdan")
public List<String> qiangdan() {
//搶到商品的用戶
List<String> shopUsers = new ArrayList<>();
//構(gòu)造很多用戶
List<String> users = Collections.synchronizedList(new ArrayList<String>());
IntStream.range(0, 100000).parallel().forEach(b -> {
users.add("神器-" + b);
});
//初始化庫存
nKuCuen = 10;
//模擬開搶
users.parallelStream().forEach(b -> {
String shopUser = this.qiang(b);
if (!StringUtils.isEmpty(shopUser)) {
shopUsers.add(shopUser);
}
});
return shopUsers;
}
private String qiang(String b) {
//用戶開搶時間
long startTime = System.currentTimeMillis();
//未搶到的情況下,30秒內(nèi)繼續(xù)獲取鎖
while ((startTime + timeout) >= System.currentTimeMillis()) {
//商品是否剩余
if (nKuCuen <= 0) {
break;
}
if (redisUtils.setnx(shangpingKey, b)) {
//用戶b拿到鎖
log.info("用戶{}拿到鎖...", b);
try {
//商品是否剩余
if (nKuCuen <= 0) {
break;
}
//模擬生成訂單耗時操作蔬顾,方便查看:神器-50 多次獲取鎖記錄
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//搶購成功宴偿,商品遞減,記錄用戶
nKuCuen -= 1;
//搶單成功跳出
log.info("用戶{}搶單成功跳出...所剩庫存:{}", b, nKuCuen);
return b + "搶單成功诀豁,所剩庫存:" + nKuCuen;
} finally {
log.info("用戶{}釋放鎖...", b);
//釋放鎖
redisUtils.delnx(shangpingKey, b);
}
} else {
log.info("用戶{}等待獲取鎖...", b);
// 用戶b沒拿到鎖窄刘,在超時范圍內(nèi)繼續(xù)請求鎖,不需要處理
/*if (b.equals("神器-50") || b.equals("神器-69")) {
log.info("用戶{}等待獲取鎖...", b);
}*/
}
}
return "";
}
}
測試
總結(jié)
這個秒殺小案例主要用到的知識點就是redis的分布式鎖的唯一性舷胜,希望大家溫故知新娩践。
需要源碼的可以關(guān)注公眾號【溫故知新之java】,更多干活與你分享烹骨。