实现数据存储和文件下载异步
1.线程池配置
@Configuration
@EnableAsync
public class ThreadPoolConfig {
private static final Logger logger = LoggerFactory.getLogger(ThreadPoolConfig.class);
@Value("${async.executor.thread.core_pool_size}")
private int corePoolSize;
@Value("${async.executor.thread.max_pool_size}")
private int maxPoolSize;
@Value("${async.executor.thread.queue_capacity}")
private int queueSize;
@Value("${async.executor.thread.name.prefix}")
private String namePrefix;
@Bean(name = "asyncServiceExecutor")
public Executor asyncServiceExecutor(){
logger.info("=======ThreadPool Started========");
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueSize);
executor.setThreadNamePrefix(namePrefix);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
yml配置文件的配置
#yml的配置
# 异步线程配置
# 配置核心线程数
async.executor.thread.core_pool_size: 10
# 配置最大线程数
async.executor.thread.max_pool_size: 20
# 配置队列大小
async.executor.thread.queue_capacity: 99999
# 配置线程池中的线程的名称前缀
async.executor.thread.name.prefix: async-service-
2.@Async异步不生效
在解决bean的注入问题后,开始执行异步流程,在逻辑上按道理是没有问题的,使用@Async注解,让线程池开启线程去异步执行,但此时发现,方法并没有生效,仍然是先打印"11111",文件下载完写入磁盘后才开始输出“文件下载了没有”。
于是查询资料才知道异步不生效的原因是不能在同一个class里去调用异步方法,这是为什么呢?
先看一下@EnableAsync注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AsyncConfigurationSelector.class})
public @interface EnableAsync {
Class<? extends Annotation> annotation() default Annotation.class;
//一下两个方法都涉及到了代理
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default 2147483647;
}
那么在什么情况下会用到代理呢?根据面向切面的编程来理解,只有当我不知道调哪个的时候,aop通过动态代理反射来获取相应bean的class然后再实例化。那么也就是说,当你把异步和需要调用异步的方法定义在了同一个类的时候,这个时候不需要借助动态代理去生成相关的bean实例,也就是绕过了动态代理直接执行本类中的方法调用,因此异步就不生效了。
3.单独定义类编写异步任务方法
因此我又重新定义了一个类来单独实现异步方法。
这个时候启动后,这里可以看到一个io线程,一个异步线程, io线程在执行数据解析生成table然后进行插入操作,异步线程在下载图片写入磁盘,可以看到“文件开始下载了吗?”在提示“异步了吗“之前,表示没有阻塞在downLoad方法里,可以看到io线程在执行数据库的插入操作,async-service-xx的线程在执行图片文件下载操作
至此异步成功。
再来看看逻辑是否正确:
未下载前,state为0
下载后,state为1,相应的表字段存储了url + 文件路径
那么整个数据存储和文件下载异步的设计成功。
4.需要考虑的问题
1.任务在阻塞队列里,没有执行,宕机了怎么办?
这个问题延续了任务重试参数持久化的思想,先将需要下载的任务持久化到数据库,state置为0,表示任务还没有执行,那么再开启异步执行任务,当任务执行完之后将相应的参数state置为1,或者删除,这里采用了改变state的策略,方便对比。
2.将文件路径更新到数据库字段过长导致更新失败的问题
先是考虑了两种方案,一种是让异步线程去更改字段长度,二是让url先预留一定的长度来防止字段长度过短导致下次拼接url和文件路径更新数据库失败的问题。但是如果让异步线程执行更改,这样可能会执行多次同样的操作,这样的话效率就比较低了。然后经过思考还是决定用第一种方案,预留大概100长度左右的字符串长度,以空格区分开,然后通过split方法截断参数得到url去请求文件下载。
3.数据插入失败,文件路径无法更新怎么办?
数据插入失败比较多的情况有两种,一是列字段超过了65535的限制,二是编码格式不对utf8mb4无法存到utf8的数据库,在目前看来那边的平台只有第二种情况比较多,需要提前预防。目前的考虑是因为即使文件多次下载重复其实也不影响什么(如果一定要去重那就需要读文件MD5码与下载的MD5码进行对比了),所以最终还是以整个任务流程成功为准,即使文件下载成功,数据插入失败了也不算成功,state变量不更新。