倒计时生产票、卷需求的实现

来源:互联网 发布:linux 写文件指令 编辑:程序博客网 时间:2024/05/16 23:52


       我们经常见到一些场景,开发者为了增加用户互动的趣味或者是实际业务的强烈需要,在系统中设置票券等道具,并定时发放给用户,用户拿到这些道具去做一些好玩的事情。例如我们系统在每天固定的时间点(0点、4点、8点、12点、16点、20点共6个时间点),以下统称为“生产点”,为所有注册账号(用户)生产一张票。票可以投给参加活动的选手。每张票有效期3天。票源源不断的被生产,源源不断的被使用或者失效,像是一个小的生态系统。



1. 初级方案

初期用户量很少。为了迅速上线。采取了简单的方案。

  ScheduledExecutorService executor = Executors.newSingleThreadExecutor;logger.info("BallotScheduler assign thread come in");Calendar cal = Calendar.getInstance();int h = cal.get(Calendar.HOUR_OF_DAY);int initialDelay = Integer.MAX_VALUE;Calendar next = Calendar.getInstance();if (h < 4) {next.set(Calendar.HOUR_OF_DAY, 4);} else if (h < 8) {next.set(Calendar.HOUR_OF_DAY, 8);} else if (h < 12) {next.set(Calendar.HOUR_OF_DAY, 12);} else if (h < 16) {next.set(Calendar.HOUR_OF_DAY, 16);} else if (h < 20) {next.set(Calendar.HOUR_OF_DAY, 20);} else {next.add(Calendar.DATE, 1);next.set(Calendar.HOUR_OF_DAY, 0);}next.set(Calendar.MINUTE, 0);next.set(Calendar.SECOND, 0);initialDelay = (int) ((next.getTimeInMillis() - cal.getTimeInMillis()) / 1000) - 1;logger.info("initialDelay: " + initialDelay);executor.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {long t = System.currentTimeMillis() + Constants.HOURMILLIS * 4 + 1000;Date d = new Date(t);redisUtils.setStr(Constants.BALLOT_NEXT_ASSIGN_TIMESTAMP, t + "");logger.info("BallotScheduler run thread come in-------" + d);int start = 0;Calendar now = Calendar.getInstance();Calendar overdate = Calendar.getInstance();overdate.add(Calendar.DATE, Constants.BALLOT_OVERDATE_DAY);while (true) {logger.info("start: {}; step: {}", start, step);List<User> users = userDao.getUsers(start, step);if (users == null || users.size() == 0) {logger.info("users == null");Calendar thisTime = Calendar.getInstance();thisTime.add(Calendar.SECOND, Constants.BALLOT_ASSIGN_DELAY_SECONDS);break;}List<UserBallot> ballots = new ArrayList<>();for (User user : users) {logger.info("user:" + user.getPassport());UserBallot ballot = new UserBallot();ballot.setCreatetime(now.getTime());ballot.setPassport(user.getPassport());ballot.setOverdate(overdate.getTime());ballots.add(ballot);}int rtn = userBallotDao.saveBatch(ballots);logger.info("save ballot rtn: {}", rtn);start += step;}}}, initialDelay, Constants.BALLOT_ASSIGN_DELAY_SECONDS, TimeUnit.SECONDS);


关键点:
initialDelay 计算出程序部署启动后当前时间距离下次“生产点”的时间。
Constants.BALLOT_ASSIGN_DELAY_SECONDS 四个小时的秒数
Constants.BALLOT_NEXT_ASSIGN_TIMESTAMP 将下次的“生产点”的时间放入redis。“系统将于X小时Y分Z秒 后产生一张新的加油票”就是通过这个值来换算的
while循环 批量给所有用户插入票。过期时间设为Constants.BALLOT_OVERDATE_DAY天后

这种方案思路和实现简单,但是用户量30000+,生产票延时太明显,直观表现是20点整时,APP上显示出产生了加油票,但实际并没有,过了一会儿才有票。

即便考虑将批量插入阶段采用多线程,在用户活跃度较高的时间点比如20点整,在定时任务与API程序共同部署在某台服务器的情况下,对系统的瞬时压力也是显而易见的。于是思考有什么更好的方案?



2. 改进方案

票在数据库中提前生产好。只是到了这6个“生产点”才让它们出来“见人”。


  Calendar cal = Calendar.getInstance();int h = cal.get(Calendar.HOUR_OF_DAY);int initialDelay = Integer.MAX_VALUE;Calendar next = Calendar.getInstance();if (h < 4) {next.set(Calendar.HOUR_OF_DAY, 4);} else if (h < 8) {next.set(Calendar.HOUR_OF_DAY, 8);} else if (h < 12) {next.set(Calendar.HOUR_OF_DAY, 12);} else if (h < 16) {next.set(Calendar.HOUR_OF_DAY, 16);} else if (h < 20) {next.set(Calendar.HOUR_OF_DAY, 20);} else {next.add(Calendar.DATE, 1);next.set(Calendar.HOUR_OF_DAY, 0);}next.set(Calendar.MINUTE, 0);next.set(Calendar.SECOND, 0);next.set(Calendar.MILLISECOND, 0);initialDelay = (int) ((next.getTimeInMillis() - cal.getTimeInMillis()) / 1000) - 1;logger.info("initialDelay: " + initialDelay);executor.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {long t = System.currentTimeMillis() + Constants.HOURMILLIS * 4 + 1000;redisUtils.setStr(Constants.BALLOT_NEXT_ASSIGN_TIMESTAMP, t + "");}}, initialDelay, Constants.BALLOT_ASSIGN_DELAY_SECONDS, TimeUnit.SECONDS);int init = 0;if (h < 3) {init = 3 - h;} else {init = 24 + 3 - h;}logger.info("initDelay: " + init);executor.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {Calendar cal = Calendar.getInstance();cal.set(Calendar.HOUR_OF_DAY, 0);cal.set(Calendar.MINUTE, 0);cal.set(Calendar.SECOND, 0);cal.set(Calendar.MILLISECOND, 0);for (int i = 0; i < 6; i++) {cal.add(Calendar.HOUR_OF_DAY, 4);long createtime = cal.getTimeInMillis();long overdate = createtime + Constants.DAYMILLIS * Constants.BALLOT_OVERDATE_DAY;batchSave(createtime, overdate);try {Thread.sleep(2000);} catch (InterruptedException e) {logger.error(e.getMessage(), e);}}// 删除过期的星票userBallotDao.deleteOverdate();}}, init, 24, TimeUnit.HOURS);

Constants.BALLOT_NEXT_ASSIGN_TIMESTAMP 的计算沿用了方案1的

每天init时间点(3点多)去做提前生产出接下来一天6个“生产点”的票。createtime设置为它应该“见人”的时间。

数据库passport(255), createtime设为唯一索引。防止重复插入。

1 0