场景描述:
跨年红包雨活动,分为五场,2018-12-31 20:00以后每个整点一场,每场红包雨持续1分钟。
有以下几个问题需要思考
思考一:10W人同时使用系统保证服务正常
从某云购买负载均衡SLB 10台 按量付费 规格是标准型2 按使用流量计费
nginx服务器,前端页面一定要用静态页面实现
思考二:如何保证奖品发出数量<=总奖品数量。
把所有的奖品按照单个奖品的总数平均分配到10台服务器,每台服务器上的单个奖品在平均分为5场
把以上分配好的奖品生成记录插入数据库。如某优惠券有100个:平均每台服务器上分10个,平均每台服务器上的每场分2个奖品,生成100条记录插入数据库,每条记录有对应的服务器编号,场次编号,奖品id等信息。
后台语言java实现,每场奖品数据随机打乱放入队列中。每来一个请求,若中奖,则从队列中移除该奖品,同时把该用户的中奖信息保存到redis。涉及到线程安全问题,一定要做好。保证一个机器上的某一场的某个奖品不会被多个人同时中奖。
负载均衡一定要设置ip进入的规则“轮询”,把每个请求按顺序逐一分配到不同的server,保证每台服务器上的奖品都能被均匀领到。
思考三:程序的灵活性,如何控制每场开始时间、奖品数量、中奖机率等等。
机器编号如何生成?不应该打10个包,每个包配置一个服务器编号,假如有100个不是要疯了?
可以用redis的自增方法,每启动一台服务获取key的值后保存到内存中,并且对该key的value+1操作。
提供接口,可以随时更改中奖机率和设置开始时间等等,每场间隔一个小时,有足够时间执行下一场的操作。
......
抽奖方法的核心代码:
public String init(@PathVariable(value = "openId") String openId) {
// 本地缓存
Repository repo = Repository.instance;
// 一轮结束
if (repo.endFlag > 0) {
return ResultUtil.error(201, blessArr[(int) (Math.random() * blessArr.length)]);
}
// 查看当前奖励是否开始
if (System.currentTimeMillis() < repo.prizeStart) {
return ResultUtil.error(201, blessArr[(int) (Math.random() * blessArr.length)]);
}
// 直接比较,不用任何变量,如果数值超过128不能用==比较
if (repo.prizeNum != (int) (Math.random() * repo.prizeRate)) {
// 不要使用Exception的机制,内存和时间消耗很大
return ResultUtil.error(201, blessArr[(int) (Math.random() * blessArr.length)]);
}
// 将中奖信息保存到缓存(setNX排他) 奖品批次:openId : 奖品内容(需要序列化,这里用toString代替)
PeAcHbyPrize prize;
if (redisUtil.incr("flag:" + repo.prizeGroup + openId, 1) <= repo.prizeCount) {
// 取得奖品
prize = repo.pollPrize();
if (null == prize) {
// 在本服务器上没奖,可能在别的服务器还有,要回退中奖次数
redisUtil.decr("flag:" + repo.prizeGroup + openId, 1);
return ResultUtil.error(201, blessArr[(int) (Math.random() * blessArr.length)]);
}
redisUtil.lSet("prize:" + repo.prizeGroup + openId, JSONObject.toJSONString(prize));
} else {
return ResultUtil.error(201, blessArr[(int) (Math.random() * blessArr.length)]);
}
log.info("{}--{}", openId, prize.getId());
return ResultUtil.success(prize);// 成功
}