spring batch的java config实践

背景

在后台服务开发中, 经常要用到多线程技术进行加速执行, 每家公司都有内部多线程的框架, 这些框架不是文档不规范, 就是只能适用特定场景.
基于这些原因, spring batch带来了更易用, 性能更好的解决方案.

基本概念

JobRepository

job仓库, 提供了JobLauncher, Job, Setp的CRUD实现

JobLauncher

job的启动器, 可以传入job所需参数

Job

一个任务概念, 可以包含多个step, 且对step的执行顺序进行编排

Step

具体步骤, 基本包含reader, writer, reader后可选processor, 或者使用tesklet

下面用一个图来说明他们之间的关系

spring-batch-reference-model.png

概念还是挺简单的, 就是框架有点复杂, 用起来坑不少

实践代码

我这里使用java config形式使用spring batch, 需要额外注意的是, 所有带有@Bean的方法名不要重复

  1. build.gradle
buildscript {
    ext {
        springBootVersion = '1.5.2.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'war'

sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
    compile('org.springframework.boot:spring-boot-starter-batch')
}

这里引用了spring boot starter batch, 只是为了解决jar包依赖问题, 实际使用时没有使用spring boot.

创建任务使用的obj, TestObj.java

public class TestObj {
    private String id;
    private int index;
    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public int getIndex() {
        return index;
    }

    public void setIndex(int index) {
        this.index = index;
    }
    
}

主逻辑BatchConfiguration.java

@EnableBatchProcessing
public class BatchConfiguration {
    
    Object lock = new Object();
    
    Logger logger = Logger.getRootLogger();
    @Autowired
    private JobBuilderFactory jobBuilderFactory;

    @Autowired
    private StepBuilderFactory stepBuilderFactory;

    @Bean
    public Step step1() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(10);
        taskExecutor.setMaxPoolSize(10);
        taskExecutor.setAllowCoreThreadTimeOut(true);
        taskExecutor.afterPropertiesSet();

        return stepBuilderFactory.get("step1")
            .<TestObj, TestObj> chunk(10)
            .reader(new ItemReader<TestObj>() {
                private List<TestObj> list = null;
    
                @Override
                public synchronized TestObj read() throws Exception {
                    if (list == null) {
                        list = new ArrayList<TestObj>();
                        for (int i = 0; i < 10000; i++) {
                            TestObj obj = new TestObj();
                            obj.setId(UUID.randomUUID().toString());
                            obj.setIndex(i);
                            list.add(obj);
                        }
                        System.out.println("----------------"+list.size());
                    }
                    if (!this.list.isEmpty()) {
                        TestObj t = this.list.remove(0);
                        logger.info("step1==========read data:" + t.getIndex()));
                        return t;
                    }
                    return null;
                }
            })
            .processor(new ItemProcessor<TestObj, TestObj>() {
                public TestObj process(TestObj item) {
                    logger.debug("step1==============process: " + item.getIndex());
                    return item;
                }
            })
            .writer(new ItemWriter<TestObj>() {
                @Override
                public void write(List<? extends TestObj> items) throws Exception {
                    logger.debug("step1=============write batch start: " + items.size());
                    for (TestObj item : items) {
                        logger.debug("step1=============write: " + item.getIndex());
                    }
                    logger.info("step1=============write batch end: " + items.size());
                }
            })
            .taskExecutor(taskExecutor)
            .build();
    }
    
    @Bean
    public Step step2() {
        return stepBuilderFactory.get("step2").tasklet(new Tasklet() {
            public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) {
                logger.debug("step2========================Tasklet");
                return RepeatStatus.FINISHED;
            }
        }).build();
    }

    @Bean
    public Job job1(Step step1, Step step2) throws Exception {
        return jobBuilderFactory.get("job1").incrementer(new RunIdIncrementer()).start(step1).next(step2).build();
    }

}

最后是启动类Main.java

public class Main {

    public static void main(String[] args) {
        Logger logger = Logger.getRootLogger();
        
        logger.setLevel(Level.INFO);
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.register(BatchConfiguration.class);
        ctx.refresh();
        JobLauncher  jobLauncher = ctx.getBean(JobLauncher.class);
        Job job = (Job)ctx.getBean("job1");
        try {
            jobLauncher.run(job, new JobParameters());
            logger.debug("------job1 finished");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

其中需要注意以下几点

  1. @EnableBatchProcessing 会默认给出一些基础配置
  • JobRepository - bean name "jobRepository"
  • JobLauncher - bean name "jobLauncher"
  • JobRegistry - bean name "jobRegistry"
  • PlatformTransactionManager - bean name "transactionManager"
  • JobBuilderFactory - bean name "jobBuilders"
    StepBuilderFactory - bean name "stepBuilders"
  1. 此处使用了多线程进行执行任务, 其中taskExecutor.setAllowCoreThreadTimeOut(true);表示当没有任务时(默认为60s), 线程池中线程会自动销毁
  2. 自定义的ItemReader实现类中的read()方法需要加synchronized(多线程环境一定要加), 在官方文档上有提过一嘴, 如果不是使用多线程可以不加, 在官方很多默认实现中, 有一些是线程安全的, 有一些则不是, 如果非线程安全, 使用时都需要加上synchronized关键字
  3. 如果read()方法, 返回null, 则整个任务结束.
  4. chunk(10)表示当每次传入write的list的个数为10时, write执行一次, 为主要的调优方法
  5. 实际使用中, processor可以去掉

一些说明

spring batch本身有很多功能以及高级特性(比如监听, 任务流, spring batch admin), 本文中不做展开, 这里只针对最常用情况给出一个可用版本, 在我实际使用过程中, 发现大多数文章的例子基本都无法使用或者是使用xml或者不能单独执行.

很多时候还是要多看官方文档, 只是官方文档有点太平铺直叙了

参考

spring batch官方文档

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,099评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,355评论 6 343
  • 在大型企业中,由于业务复杂、数据量大、数据格式不同、数据交互格式繁杂,并非所有的操作都能通过交互界面进行处理。而有...
    无敌西瓜黄博文阅读 3,175评论 1 39
  • 天澄气佳,景和物休,木陨微风,鳞腾细流。宇清禽举,云水澹忧。丛篁发响,柔柯拂头。祝融销退,时涉素秋。衔璧日之映照,...
    商夷君阅读 214评论 0 0
  • 二月的开始是从爬山开启的,又一次挑战了华山,这次的经历让我对自己的体力又有了新的认识,自己一直在进步,同样的也...
    Jackjia阅读 122评论 0 0