分布式秒杀系统二

分布式秒杀系统二

秒杀场次的获取

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();
			}
		}
	}
问题
  1. 不能以客户端的时间为准:应该以服务器的时间为准。
  2. 如果服务器集群之后,负载均衡会导致请求分发到不同的集群服务器上,服务器的时间不一致问题:设置一个时间服务器,所有集群服务器写一个脚本,一段时间去获取时间服务器的时间使得误差尽量减少。
  3. 如果要获取服务器的时间,则前端每一秒都要请求一次服务器,服务器压力过大:只在第一次获取服务器时间,后面通过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();
            }
        }
    }
}

抢购功能实现

如何防止提前下单
  1. 下单前,查询缓存,获取当前商品开始的秒杀时间,和当前时间做一个对比,如果当前时间再秒杀时间之后,则表示可以开启秒杀(会导致redis中的key急剧增加)
  2. 利用redis的HashSet(HashSet的访问速度为O(1))
    1. redis存放一个字符串类型的nowTime(yyMMddHH)。
    2. 并且存放一系列HashSet:key为secKill_{time},value为当前time秒杀场次下的商品列表的id。如key:seckill_20210511,value:876,176,25。
    3. 当请求秒杀某个商品876时,先获取nowTime当前场次,然后和secKill_拼接为key,查看是否存在value为876的值,如果在则可以进行抢购。
    4. 还需要一个定时任务(使用Elastic-Job和Quartz):每两个小时修改redis中的nowTime,并且删除之前场次的集合。
如何防止重复提交
  1. 页面生成时,会生成一个隐藏的UUID号,提交请求后,会带着这个UUID,服务器接收到请求,然后服务器将UUID存入redis,如果redis中已经存在,说明当前请求时重复请求,拒绝处理即可。(只能防止误触碰的重复提交,如果有人恶意的通过工具重复发送请求,没办法判断)
  2. 提交必须输入验证码(好处:1.防止重复提交 2.防止恶意提交,秒杀器,脚本。3.拉长服务器的请求处理时间 坏处:1.普通验证码容易被破解)
购买提醒:全程代码实战,本系列课程建议有Java开发经验2年以上的学员观看和购买。录制本套教程的初衷,通过从业10年接触过很多的技术开发人员,尤其在面试一些技术人员的时候,发现他们的技术知识更新较慢,很多人渴望接触到高并发系统和一些高级技术架构,为了帮助更多人能够提升自己和接触到这类技术架构,并满足企业的人才需求,利用业余时间我开始录制这套教程。通过录制教程有很多学员给我反馈信息,给了我很大的鼓舞,当然也有吐槽,我想说的是技术是没有边界的,脱离一线业务场景去谈技术,都是耍流氓的。如对我录制的教程内容有建议请及时交流。本套课程历经1年时间研发,案例来源于真实业务场景抽离,由从业10年企业一线架构师实录,没有基础不建议购买。购买后提供企业级多方位指导,通过本套案例可以让你学习目前主流的微服务技术架构和多种企业级高并发和海量数据、高可用、分布式、支付、多语言、前后端分离等技术的综合应用解决方案。在开始本课程前给大家科普几个概念: 高并发是指在比较短的时间内有大量的访问者访问目标系统,系统负载饱和或者过载宕机。 高并发的应用,我们应该都有用过或者见过,比如天猫、京东、拼多多、亚马逊的秒杀抢购还有12306的抢票。我们在体验应用的时候,可能并不会像到这种高并发系统背后的技术实现难度。高并发系统都存在这几种问题,高并发读、高并发写、访问高峰突发性、反馈结果的即时性。在抢购的时候,尤其是抢购火车票的时候,我们经常会疯狂的刷库存,几亿用户产生非常大的高并发读; 通过以上的科普相信大家对课程有一个基本的认知了,本套教程以应用最为广泛的电商系统为标本,给大家构建一个亿级微服务秒杀系统,让大家跟着我的步骤能学习行为背后的原理。本课程采用全新的微服务架构,运用了很多工业界企业解决方案和高级技术,带大家手把手实现一个高性能,高并发,高可用等的亿级微服务秒杀系统,本课程会包含很多高级的内容,比如微服务架构、分布式部署方案、多线程、支付、多语言、全链路性能压力测试等,让大家在实战中学习知识,在实战中不断进步。该课程是一个完整的微服务架构秒杀系统项目代码,案例具有很高的商业价值,大家可以根据自己的业务进行修改,便可以使用。本套课程可以满足世面上绝大多数企业级的业务场景,本课程全部代码可以直接部署企业,普通集群,支撑**并发;集群规模大,支撑亿级并发。本课程包含的技术: IDEA集成开发工具 SpringBoot2.0.2.RELEASE SpringCloudFinchley.RELEASE Thymeleaf(模板引擎技术) 微信支付 支付宝支付 银联支付 分布式数据库Mycat MySQL Druid RabbitMQ 分布式事务 分布式锁 事件驱动 多线程 MyBatis QuartzEhcache Redis Hystrix 单点登陆CAS Nginx Lua Restful AOP技术 性能压力测试Jemter VUE+jQuery+Ajax+NodeJS Python Go语言课程亮点: 1.与企业无缝对接、真实工业界产品 2.主流支付全覆盖(微信、支付宝、银联) 3.前后端分离(主流技术架构) 4.实现高并发请求和实现高可用架构解决方案 5.多语言(Java、Go、Python) 6.亿级微服务秒杀系统(支撑海量数据) 7.大型系统分布式部署方案 8.全链路性能压力测试  9.分布式事务解决方案 10.事件驱动设计解决方案 11.多线程技术的实战应用 12.高并发下的服务降级、限流实战 13.分布式架构师下实现分布式定时调度 14.集成MyBatis实现多数据源路由实战 15.集成Redis缓存实战 16.Eureka注册中心 17.OpenFeign声明式服务调用 18.Hystrix服务熔断降级方式 19.基于Hystrix实现接口降级实战 20.集成SpringCloud实现统一整合方案 21.全程代码实操,提供全部代码和资料 22.提供答疑和提供企业技术方案咨询购买提醒: 我本人在企业从业10年,因为热爱,所以坚持,下一个10年依然会在企业一线服务,因此对于课程中的技术点可以提供全方面的业务场景解决方案。我本人并非培训机构脱离一线业务场景的讲师,从业多年接触过大量的真实业务场景案例,后面会逐步通过教程案例分享我多年的实战经验,送给同行一句话:技术是服务于业务的,脱离一线业务场景就是耍流氓。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值