分布式秒杀系统二
文章目录
秒杀场次的获取
public static String getSecKillTime(int i){
//根据当前时间 计算第一场的时间
Calendar calendar = Calendar.getInstance();
//获得当前的小时
int h = calendar.get(Calendar.HOUR_OF_DAY);
if(h % 2 != 0){
h = h - 1;
}
calendar.set(Calendar.HOUR_OF_DAY,h);
calendar.set(Calendar.MINUTE,0);
calendar.set(Calendar.SECOND,0);
calendar.set(Calendar.MILLISECOND,0);
//计算场次
calendar.add(Calendar.HOUR_OF_DAY,i*2);
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(calendar.getTime());
}
@RequestMapping("/times")
public ResultData<List<String>> getSecKillTime(){
List<String> times = new ArrayList<>();
//计算5个场次的时间
for (int i = 0; i < 5; i++) {
String time = DateUtil.getSecKillTime(i);
times.add(time);
}
return new ResultData<List<String>>().setData(times);
}
对秒杀场次进行缓存
添加依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
</dependencies>
配置redis
spring:
redis:
host: 127.0.0.1
password: 12345
cache:
redis:
#10分钟清空一次缓存
time-to-live: 600000
添加注解
该注解表示执行下面方法时先从缓存中查找,如果没有则从数据库中查找,并且插入到redis中(名字通过cacheNames和key进行拼接)
@Cacheable(cacheNames = "seckill",key = "'times'+#time")
public List<Goods> getSecGoodsListByTime(String time) {
System.out.print("查询数据库了");
QueryWrapper<Goods>queryWrapper = new QueryWrapper<Goods>().eq("begin_time",time);
List<Goods> goods = goodsMapper.selectList(queryWrapper);
return goods;
}
该注解表示每当执行方法都将缓存中的数据库删除,用于增加商品时使用:防止增加完商品后缓存没有变化,进而查商品总从缓存中查找而找不到新增加的商品。
@CachePut(cacheNames="seckill",key="'XXXXXX'")
倒计时的实现
第一版
export default{
name : "SecKillTime",
props:['overTime'],
data(){
return {
h:"00",
m:"00",
s:"00"
}
},
methods:{
djs(){
var _this =this
//什么时候结束
var over = new Date(this.overTime);
//当前时间
var now = new Date();
//转换成毫秒值
var begin = now.getTime();
var end = over.getTime();
//中间差距的时间,相差多少毫秒
var times = end-begin;
var hour = parseInt(times/1000/60/60);
var min = parseInt(times/1000/60%60);
var sec = parseInt(times/1000%60);
this.h = this.formatTime(hour);
this.m = this.formatTime(min);
this.s = this.formatTime(sec);
setTimeout(function(){
// begin-=1000;
_this.djs();
},1000);
},
formatTime(s){
return s<10?("0"+s):s;
}
},
watch:{
overTime(v){
this.djs();
}
}
}
问题
- 不能以客户端的时间为准:应该以服务器的时间为准。
- 如果服务器集群之后,负载均衡会导致请求分发到不同的集群服务器上,服务器的时间不一致问题:设置一个时间服务器,所有集群服务器写一个脚本,一段时间去获取时间服务器的时间使得误差尽量减少。
- 如果要获取服务器的时间,则前端每一秒都要请求一次服务器,服务器压力过大:只在第一次获取服务器时间,后面通过nowtime+=1000人为的设置。
第二版
var now;
var begin;
export default {
name: "SecKillTime",
props: ['overTime'],
data() {
return {
h: "00",
m: "00",
s: "00"
}
},
methods: {
djs() {
var _this = this
//什么时候结束
var over = new Date(this.overTime);
//当前时间
// var now = new Date();
//转换成毫秒值
var end = over.getTime();
//中间差距的时间,相差多少毫秒
var times = end - begin;
var hour = parseInt(times / 1000 / 60 / 60);
var min = parseInt(times / 1000 / 60 % 60);
var sec = parseInt(times / 1000 % 60);
this.h = this.formatTime(hour);
this.m = this.formatTime(min);
this.s = this.formatTime(sec);
setTimeout(function() {
begin += 1000;
_this.djs();
}, 1000);
},
formatTime(s) {
return s < 10 ? ("0" + s) : s;
},
getServerNow() {
var _this = this;
this.utils.ajax({
url: this.utils.urls.now,
success: function(data) {
now = new Date(data);
begin = now.getTime();
_this.djs();
}
}, this);
}
},
watch: {
overTime(v) {
this.getServerNow();
}
}
}
切换页面时定时器累加问题
//定义一个全局timer
var timer;
//方法中调用setTimeout
timer = setTimeout(function() {
begin += 1000;
_this.djs();
}, 1000);
//通过$once来监听定时器,在beforeDestroy钩子可以被清除。
mounted() {
this.$once('hook:beforeDestroy', () => {
clearTimeout(timer) // 此处的timer即 上文const的 timer
})
}
父子组件
父组件
//1. 传递参数和方法
<times :overTime="overTime" :overFunc="overFunc"/>
//2. 导入子组件
import times from "./SecKillTime.vue"
export default {
name: "SecKillGoods",
//3. 导入子组件
components:{
times
},
data() {
return {
//4. 传递的数据
overTime:"",
}
},
methods: {
//5. 传递的方法(当子组件触发时执行)
overFunc(){
this.flag = true;
}
}
}
子组件
export default {
name: "SecKillTime",
//1. 通过props获取父组件传来的数据和方法
props: {
overTime :{
type:String
},
overFunc:{
type:Function
}
},
methods: {
// 省略其他
djs() {
if(times <= 0 ){
//2. 调用父组件方法
this.overFunc();
}
}
}
}
抢购功能实现
如何防止提前下单
- 下单前,查询缓存,获取当前商品开始的秒杀时间,和当前时间做一个对比,如果当前时间再秒杀时间之后,则表示可以开启秒杀(会导致redis中的key急剧增加)
- 利用redis的HashSet(HashSet的访问速度为O(1))
- redis存放一个字符串类型的nowTime(yyMMddHH)。
- 并且存放一系列HashSet:key为secKill_{time},value为当前time秒杀场次下的商品列表的id。如key:seckill_20210511,value:876,176,25。
- 当请求秒杀某个商品876时,先获取nowTime当前场次,然后和secKill_拼接为key,查看是否存在value为876的值,如果在则可以进行抢购。
- 还需要一个定时任务(使用Elastic-Job和Quartz):每两个小时修改redis中的nowTime,并且删除之前场次的集合。
如何防止重复提交
- 页面生成时,会生成一个隐藏的UUID号,提交请求后,会带着这个UUID,服务器接收到请求,然后服务器将UUID存入redis,如果redis中已经存在,说明当前请求时重复请求,拒绝处理即可。(只能防止误触碰的重复提交,如果有人恶意的通过工具重复发送请求,没办法判断)
- 提交必须输入验证码(好处:1.防止重复提交 2.防止恶意提交,秒杀器,脚本。3.拉长服务器的请求处理时间 坏处:1.普通验证码容易被破解)

2321

被折叠的 条评论
为什么被折叠?



