IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> 在线程池中限制多次重复操作数据库 -> 正文阅读

[大数据]在线程池中限制多次重复操作数据库

一、业务背景

工作中遇到了一个需求如下:

1、给项目中的所有定时任务添加自检监控,确保能及时知道定时任务是否成功执行
2、有写定时任务中的方法并没有业务操作,只是通过线程池提交任务方式,开启多个子线程去执行任务,这些子线程的执行情况也需要记录。
3、多个执行任务的子线程中,若有其中一个失败,则整个监控结果定义为失败;全部成功,则整个监控结果定义为成功。
4、含有子线程失败时,只需要记录其中一个子线程的失败信息即可(因为错误信息可能都一样);所有子线程成功时,则说明定时任务成功,但得确保设置成功结果只操作了数据库一次(防止每个线程都操作设置一次成功)。
5、尽量做到代码简洁,因为这个自检监控并非主要业务逻辑,尽可能减少代码入侵。

二、分析实现

ThreadPoolExecutor pool = new ThreadPoolExecutor (100,100,60L,TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(200))

//定时任务
@Scheduled(initialDelay = 10000,fixedDelay = 3000)
public void test(){
	List<App> appList = appService.list();
	for(App app : appList){
		pool.execute(()->{
			try{
			//省略部分逻辑
			//假设程序出错
			int i = 10/0;
			}catch (Exception e){
				
			}
		});
	}
}

//设置检查结果
public void setCheckResult(String id,String flag,String erro){
	//省略...
}

如上述简化后得代码,已经提供了定时任务和设置检查结果得方法,要求实现:
1、实现监控功能
2、代码简洁
3、性能尽量最优,也就是尽可能的降低对数据库操作,也不推荐用锁

2.1 方案一:将setCheckResult至于在方法结尾

ThreadPoolExecutor pool = new ThreadPoolExecutor (100,100,60L,TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(200))

//定时任务
@Scheduled(initialDelay = 10000,fixedDelay = 3000)
public void test(){
	List<App> appList = appService.list();
	for(App app : appList){
		pool.execute(()->{
			try{
			//省略部分逻辑
			//假设程序出错
			int i = 10/0;
			}catch (Exception e){
				
			}
		});
	}

	setCheckResult(...);
}

//设置检查结果
public void setCheckResult(...){
	//省略...
}

这是最初的监控方案,但是对于含子线程的定时任务,这种方案是达不到监控效果的。因为主线程执行完了,子线程未必执行完,也无法监控到子线程的执行状况

2.2 方案二:将setCheckResult至于run方法中

ThreadPoolExecutor pool = new ThreadPoolExecutor (100,100,60L,TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(200))

//定时任务
@Scheduled(initialDelay = 10000,fixedDelay = 3000)
public void test(){
	List<App> appList = appService.list();
	for(App app : appList){
		pool.execute(()->{
			try{
			//省略部分逻辑
			//假设程序出错
			int i = 10/0;
			setCheckResult(...);
			}catch (Exception e){
				setCheckResult(...);
			}
		});
	}
}

//设置检查结果
public void setCheckResult(...){
	//省略...
}

这个方案看似达到监控效果了,但实际不仅没达到,而且还会造成多次重复写数据库,耗费资源。
假设一:所有线程失败,所有都重复执行了setCheckResult方法
假设二:所有线程都成功,setCheckResult也重复执行了
假设三:部分线程成功,部分失败。setCheckResult属于共享资源,若最后一个抢到资源的线程,决定了定时任务的成功与否。

因此,方案二也不可取。

2.3 方案三:设置两个标志位

ThreadPoolExecutor pool = new ThreadPoolExecutor (100,100,60L,TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(200))

//定时任务
@Scheduled(initialDelay = 10000,fixedDelay = 3000)
public void test(){
	Map<String, String> errMap = new ConcurrentHashMap<>(2); 

	List<App> appList = appService.list();
	for(App app : appList){
		pool.execute(()->{
			try{
				//省略部分逻辑
				//假设程序出错
				int i = 10/0;
				
		      } catch (Exception e) {
		           if (errMap.get(ERRMSG) == null) {
	                    errMap.put(ERRMSG, ERRMSG);
	                   	setCheckResult(...);
		           }
		      } finally {
		           if (errMap.get(ERRMSG) == null && errMap.get(SUCCESS) == null) {
		               errMap.put(SUCCESS, SUCCESS);
		               setCheckResult(...);
		           }
		      }
		});
	}
}

//设置检查结果
public void setCheckResult(...){
	//省略...
}

该方案,可以实现当多个线程出现错误时,成功限制操作数据库次数;当所有线程执行成功时,也成功限制了无效操作数据库次数。若决定map不好用,还可以采用AtomicReference,如下:

ThreadPoolExecutor pool = new ThreadPoolExecutor (100,100,60L,TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(200))

//定时任务
@Scheduled(initialDelay = 10000,fixedDelay = 3000)
public void test(){
	AtomicReference<Boolean> error = new AtomicReference<>(false);
	AtomicReference<Boolean> success = new AtomicReference<>(false);

	List<App> appList = appService.list();
	for(App app : appList){
		pool.execute(()->{
		try {    
			//省略部分逻辑
			//假设程序出错
			int i = 10/0;
		} catch (Exception e) {    
			if (!error.get()) {        
				error.set(true);        
				//...    
			}
		} finally {    
			if (!error.get() && !success.get()) {        
				success.set(true);        
				//...    
			}
		}

		});
	}
}

//设置检查结果
public void setCheckResult(...){
	//省略...
}

三、小结

记录下某些特定场景的解决方案,备用。

  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2021-11-24 08:01:04  更:2021-11-24 08:03:00 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 8:46:12-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码