第一步:根据活动id查询所有已生成的券,并根据券总数生成未生成的券(性能问题不在这边)
/**
* 普通生成券的方法
*
* @param promotionActivityListVo
* @return
*/
private int generateCouponByActivityId(PromotionActivityListVo promotionActivityListVo, final int nums) {
try {
//生成券开始
String sql = generateSimpleInsertCouponSql(promotionActivityListVo);
List<String> params = new ArrayList<String>();
List<String> couponNos = couponTemplateDao.getCouponNoByPromotionId(promotionActivityListVo.getId());
Set<String> couponVoSet = new HashSet<String>(couponNos.size());
for (String counponNo : couponNos) {
couponVoSet.add(counponNo);
}
int i = 0;
while (i < nums) {
String couponNo = promotionActivityListVo.getCouponCode() + com.szy.utils.RandomStringUtils.getRandomString(4);
boolean compareFlag = couponVoSet.contains(couponNo);
if (compareFlag == false) {
i++;
params.add(couponNo);
couponVoSet.add(couponNo);
}
}
//生成券结束
//把券插入数据库(性能问题出在这 如一次生成200万 没有用多线程并且没有用spring的批量提交时要4个小时)
//(换成多线程和spring的批量插入只要10分钟)
return params.size();
}
} catch (Exception e) {
log.error(e);
return 0;
}
return 0;
}
第二步:把券插入数据库,把总记录数分成多个线程分批插入数据库,用CountDownLatch同步线程
if (params.size() > 0) {
int[] indexs = ThreadUtils.getIndex(params.size(), 1000, 10);//分配线程数
CountDownLatch latch = new CountDownLatch(indexs.length - 1);
for (int j = 1; j < indexs.length; j++) {
taskExecutor.execute(new SaveCouponNoTask(latch, sql, params.subList(indexs[j - 1], indexs[j])));
}
new StatCouponNoSaveTask(latch, System.currentTimeMillis()).doTask();
// 如果是商品券,则将本活动生成的券号插入到redis中,便于后续批量随机取值,赠送券不用
if (promotionActivityListVo.getCommodityType() == 2) {
// 生成券和获取券使用同一个锁锁住,避免在获取券且使用脚标删除的过程中会将新生成的券删除掉
String lockCouponNoKey = "PromotionActivity:" + promotionActivityListVo.getId() + ":getEnableCoupon";
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
if (RedisUtils.getLock(lockCouponNoKey, uuid, 10)) {
try {
String batchCouponNoKey = "PromotionActivity:" + promotionActivityListVo.getId() + ":couponSet";
Set<String> couponNosSet = new HashSet(params);
CouponToRedisUtils.couponToRedis(batchCouponNoKey, couponNosSet);
} catch (Exception e) {
log.error("生成商品券插入redis中失败!错误:" + e.getMessage());
} finally {
RedisUtils.unlock(lockCouponNoKey, uuid);
}
}
}
第三步:线程任务所做的事情
@Override
public void run() {
try {
int each = 10000;
int times = Double.valueOf(Math.floor(params.size() / each)).intValue();
int totalCount = 0;
for (int i = 0; i < times; i++) {
int beforeCount = totalCount;
totalCount += each;
// System.out.println("线程开始执行,ID:" + Thread.currentThread().getId() + ",当前时间:" + System.currentTimeMillis());
couponTemplateBiz.batchInsertCoupon(sql, params.subList(beforeCount, totalCount));
// System.out.println("线程ID:" + Thread.currentThread().getId() + ",当前时间:" + System.currentTimeMillis());
}
if (totalCount < params.size()) {
couponTemplateBiz.batchInsertCoupon(sql, params.subList(totalCount, params.size()));
}
} catch (Exception e) {
logger.error("生成券插入数据库异常", e);
} finally {
latch.countDown();
}
}
public int batchInsertCoupon(String sql, final List<String> params) {
int[] count = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setString(1, params.get(i));
}
public int getBatchSize() {
return params.size();
}
});
return count.length;
}