使用线程池异步执行定时任务

分手后的思念是犯贱 2024-03-17 11:10 161阅读 0赞

文章目录

  • 前言
  • 一、需求分析
  • 二、使用步骤
    • 1.配置线程池
    • 2.编写能量过期方法
    • 3.编写异步执行方法

前言

最近项目中需要做一个定时任务在某个固定的时间去执行一个任务,由于该定时任务的代码量已经超出可读性规范,并且处于性能的考虑,故使用线程池来做一个异步执行的方法


一、需求分析

产品给到的需求是,让用户获得的能量根据有效期的规则配置,去实现一个能量过期的效果,因为是springboot单体项目,使用XXLJOB就过于繁琐,而且项目中没有其它的定时任务,私以为使用Scheduled执行比较简单,易用。然后编写过程中发现,代码行数太多,按照不能超过80行的规范,故将一部分业务拆分出去,并使用线程池异步执行。

二、使用步骤

1.配置线程池

代码如下:

  1. import org.springframework.context.annotation.Bean;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.scheduling.annotation.EnableAsync;
  4. import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
  5. import java.util.concurrent.ThreadPoolExecutor;
  6. @Configuration
  7. @EnableAsync//开启异步
  8. public class ThreadPoolTaskConfig {
  9. @Bean("threadPoolTaskExecutor")
  10. public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
  11. ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
  12. executor.setCorePoolSize(16);
  13. executor.setQueueCapacity(1024);
  14. executor.setMaxPoolSize(64);
  15. executor.setKeepAliveSeconds(30);
  16. executor.setThreadNamePrefix("thread-");
  17. executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
  18. executor.initialize();
  19. return executor;
  20. }
  21. }

犹豫还要考虑性能安全的问题,所以使用Redisson,做一个锁

  1. import org.redisson.Redisson;
  2. import org.redisson.api.RedissonClient;
  3. import org.redisson.config.Config;
  4. import org.springframework.beans.factory.annotation.Value;
  5. import org.springframework.context.annotation.Bean;
  6. import org.springframework.context.annotation.Configuration;
  7. @Configuration
  8. public class RedissonConfig {
  9. @Value("${spring.redis.host}")
  10. private String host;
  11. @Value("${spring.redis.password}")
  12. private String password;
  13. @Value("${spring.redis.port}")
  14. private Integer port;
  15. @Bean
  16. public RedissonClient getRedisson() throws Exception {
  17. RedissonClient redisson = null;
  18. Config config = new Config();
  19. //.setPassword(password)
  20. config.useSingleServer().setAddress("redis://" + host + ":" + port);
  21. redisson = Redisson.create(config);
  22. System.out.println(redisson.getConfig());
  23. return redisson;
  24. }
  25. }

需要注意的是,其中的@Value已经在yml配置文件中配置好了,这里不再记录

2.编写能量过期方法

代码如下:

  1. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  2. import com.hsbc.component.AddOutEnergyComponent;
  3. import com.hsbc.enums.EnergyType;
  4. import com.hsbc.mapper.EnergyRecordMapper;
  5. import com.hsbc.model.EnergyRecord;
  6. import com.hsbc.model.EnergyRule;
  7. import com.hsbc.service.IEnergyRecordsService;
  8. import com.hsbc.service.IEnergyRuleService;
  9. import com.hsbc.enums.DelFlagEnum;
  10. import lombok.extern.slf4j.Slf4j;
  11. import org.apache.commons.collections4.CollectionUtils;
  12. import org.redisson.api.RLock;
  13. import org.redisson.api.RedissonClient;
  14. import org.springframework.beans.factory.annotation.Autowired;
  15. import org.springframework.context.annotation.Configuration;
  16. import org.springframework.scheduling.annotation.EnableScheduling;
  17. import org.springframework.scheduling.annotation.Scheduled;
  18. import javax.annotation.Resource;
  19. import java.text.SimpleDateFormat;
  20. import java.util.*;
  21. import java.util.concurrent.TimeUnit;
  22. @Configuration
  23. @EnableScheduling//开启定时任务
  24. @Slf4j
  25. public class EneryOutTask {
  26. @Autowired
  27. private EnergyRecordMapper energyRecordMapper;
  28. @Resource
  29. private RedissonClient redissonClient;
  30. @Autowired
  31. private AddOutEnergyComponent addOutEnergyComponent;
  32. public static final String ENERGY_OUT_LOCK_KEY = "energy:out:lock:%s";
  33. /**
  34. * 定时任务
  35. * 每月月初00:00:00执行
  36. * 已过期的能量处理
  37. */
  38. @Scheduled(cron = "0 0 0 1 * ?")
  39. public void outEnergy() {
  40. Date date = new Date();
  41. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
  42. String format = sdf.format(date);
  43. String accessLock = String.format(ENERGY_OUT_LOCK_KEY,format);
  44. try {
  45. RLock lock = redissonClient.getLock(accessLock);
  46. boolean isLock = lock.tryLock(-1, 100, TimeUnit.SECONDS);
  47. if (isLock) {
  48. try {
  49. /*获取到本月的最后时间*/
  50. Calendar calendar = Calendar.getInstance();
  51. calendar.add(Calendar.MONTH, -1);
  52. calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
  53. calendar.set(Calendar.HOUR_OF_DAY, calendar.getActualMaximum(Calendar.HOUR_OF_DAY));
  54. calendar.set(Calendar.MINUTE, calendar.getActualMaximum(Calendar.MINUTE));
  55. calendar.set(Calendar.SECOND, calendar.getActualMaximum(Calendar.SECOND));
  56. calendar.set(Calendar.MILLISECOND, calendar.getActualMaximum(Calendar.MILLISECOND));
  57. QueryWrapper<EnergyRecord> queryWrapper = new QueryWrapper<>();
  58. queryWrapper.le("validity_time", calendar.getTime());
  59. queryWrapper.eq("del_flag", DelFlagEnum.NO_DEL.getCode());
  60. /*过期和代扣的能量不统计*/
  61. queryWrapper.ne("type", EnergyType.REDUCE_ENERY.getCode());
  62. queryWrapper.ne("type", EnergyType.EXPIRE.getCode());
  63. /*所有到期的能量*/
  64. List<EnergyRecord> energyRecords = energyRecordMapper.selectList(queryWrapper);
  65. Map<String, Integer> outEnergyMap = new HashMap<>(energyRecords.size()); //会员即将到期的能量map
  66. Map<String, List<String>> outEnergyIdsMap = new HashMap<>(); //全部到期准备删除的能量id
  67. for (EnergyRecord energyRecord : energyRecords) {
  68. Integer outEnergy = outEnergyMap.get(energyRecord.getMemberId());
  69. if (outEnergy == null) {
  70. outEnergy = 0;
  71. }
  72. outEnergy += energyRecord.getResidualEnergy();
  73. outEnergyMap.put(energyRecord.getMemberId(), outEnergy);
  74. List<String> list = outEnergyIdsMap.get(energyRecord.getMemberId());
  75. if (list == null) {
  76. list = new ArrayList<>();
  77. }
  78. list.add(energyRecord.getId());
  79. outEnergyIdsMap.put(energyRecord.getMemberId(), list);
  80. }
  81. for (Map.Entry<String, Integer> entry : outEnergyMap.entrySet()) {
  82. /*异步添加*/
  83. addOutEnergyComponent.add(entry.getKey(), entry.getValue(), outEnergyIdsMap.get(entry.getKey()));
  84. }
  85. } finally {
  86. log.info("操作业务完毕,开始释放锁");
  87. lock.unlock();
  88. }
  89. } else {
  90. log.info("当前过期能量定时任务已被执行!!");
  91. }
  92. } catch (Exception e) {
  93. log.error("已过期能量定时任务报错", e);
  94. }
  95. }
  96. }

3.编写异步执行方法

代码如下:

  1. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  2. import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
  3. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  4. import com.hsbc.enums.DelFlagEnum;
  5. import com.hsbc.enums.EnergyReadEnum;
  6. import com.hsbc.enums.EnergyType;
  7. import com.hsbc.mapper.EnergyRecordMapper;
  8. import com.hsbc.model.EnergyRecord;
  9. import lombok.extern.slf4j.Slf4j;
  10. import org.apache.commons.collections.CollectionUtils;
  11. import org.springframework.beans.factory.annotation.Autowired;
  12. import org.springframework.scheduling.annotation.Async;
  13. import org.springframework.stereotype.Component;
  14. import java.util.Date;
  15. import java.util.List;
  16. @Component
  17. @Slf4j
  18. public class AddOutEnergyComponent {
  19. @Autowired
  20. private EnergyRecordMapper energyRecordMapper;
  21. //配置线程池
  22. @Async("threadPoolTaskExecutor")
  23. public void add(String memberId, Integer energy, List<String> outIds) {
  24. QueryWrapper<EnergyRecord> queryWrapper = new QueryWrapper<>();
  25. queryWrapper.eq("member_id", memberId);
  26. queryWrapper.orderByDesc("id");
  27. Page<EnergyRecord> page = new Page<>(1, 1);
  28. Page<EnergyRecord> energyRecordPage = energyRecordMapper.selectPage(page, queryWrapper);
  29. int lastEnergy = 0;
  30. if (CollectionUtils.isNotEmpty(energyRecordPage.getRecords())) {
  31. EnergyRecord energyRecord = energyRecordPage.getRecords().get(0);
  32. lastEnergy = energyRecord.getBeforeEnergy() + energyRecord.getEnergyNumber();
  33. }
  34. EnergyRecord energyRecord = new EnergyRecord();
  35. energyRecord.setMemberId(memberId);
  36. energyRecord.setBeforeEnergy(lastEnergy);
  37. energyRecord.setType(EnergyType.EXPIRE.getCode());
  38. energyRecord.setEnergyNumber(-energy);
  39. energyRecord.setCreateTime(new Date());
  40. energyRecord.setAdditional("能量到期");
  41. energyRecord.setStatus(EnergyReadEnum.READ.getCode());
  42. energyRecord.setDelFlag(DelFlagEnum.DEL.getCode());
  43. energyRecordMapper.insert(energyRecord);
  44. //修改过期能量为已删除
  45. energyRecordMapper.update(null,new UpdateWrapper<EnergyRecord>().in("id",outIds).set("del_flag",DelFlagEnum.DEL));
  46. }
  47. }

其中该需求是在能量过期以后,在能量记录表中添加数据,所以涉及另外一张表的数据插入操作,至此已经完全实现需求的功能

发表评论

表情:
评论列表 (有 0 条评论,161人围观)

还没有评论,来说两句吧...

相关阅读