spark+scala+spring整合提高搬砖效率

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/creazierHIT/article/details/79953030

0.背景

为什么会想到把这三个整合在一起? 当然是工作中遇到不舒服的地方。

最近数据的需求特别多,有时候自己定位问题也经常要跑数据,通常就是spark+scala的常规画风。虽然是提同一个jar包,但执行的每个包的路径都不一样,这就导致我要不断的去改脚本,很不舒服。提交spark job的画风通常是这样子的:

spark-submit --cluster hadoop-spark2.0 \
	--class com.acceml.hit.User.PvCount \
    "xxx.jar" ${params1} ${params2} ${params3}

spark-submit --cluster hadoop-spark2.0 \
	--class com.acceml.hit.ShanghaiUser.UvCount \
    "xxx.jar" ${params1} ${params2} ${params3}

spark-submit --cluster hadoop-spark2.0 \
	--class com.acceml.hit.User.xxx.View \
    "xxx.jar" ${params1} ${params2} ${params3}
用spring整合了一下,提交一个job只要指定它执行的类名即可。如下,三条命令分别解析pv、uv、曝光...
sh log_parser.sh PvCount 20180412
sh log_parser.sh UvCount 20180412
sh log_parser.sh View 20180412

1.实现

1.1 思路

说白了,这上面就是不需要指定包路径,想让程序根据类名执行相应的逻辑,利用控制反转在spring中简直再简单不过了。java代码如下:

@Service
public class TaskEngine {
    //定义task名字到Task的一个映射
    private final Map<String, Task> name2Task = new HashMap<>();
    //自动注入所有Task的子类,这里task只是一个interface.
    @Autowired
    public TaskEngine(List<Task> tasks) {
        tasks.forEach(task -> name2Task.put(task.getClass().getSimpleName(), task));
    }
    public static void main(String[] args) throws Exception {
        //spring注入
        ApplicationContext appContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        TaskEngine taskEngine = (TaskEngine) appContext.getBean("taskEngine");
        //根据指定参数跑job.
        taskEngine.name2Task.get(args[0]).runTask();
    }
}
由于spark开发中我不喜欢用java,写起来太冗长,虽然java8支持lambda表达式,但是java版本和spark兼容的问题可能又是一个坑,用scala写job提交也可以借鉴自动注入,然后根据参数(类名)去选择提交哪个job,就此开始了如下尝试。

1.2 scala+srping实现依赖注入的功能

1、定义一个scala的trait:

trait LogParser extends Serializable {
    def name(): String =  {
        this.getClass.getSimpleName
    }
    def run(params: ParseParams, sparkSession: SparkSession)
}

2、所有要执行的业务逻辑程序都实现它:

@Component
class DemoParser extends LogParser {
    override def run(params: ParseParams, sparkSession: SparkSession): Unit = {
        //业务逻辑.
    }
}

3、定义一个LogparserFactory的Bean用spring的自动注入把LogParser所有子类注入进来。

@Service
class LogParserFactory {
    private var logParsers: java.util.List[LogParser] = _
    //自动注入所有子类.
    @Autowired
    def this(list: java.util.List[LogParser]) {
        this()
        logParsers = list
    }
}

4、spark主程序加载所有Bean,并选择所需要的逻辑去执行.

//spring注入
val appContext = new ClassPathXmlApplicationContext("applicationContext.xml")
//获取得到相应的Bean
val logParserFactory = appContext.getBean("logParserFactory").asInstanceOf[LogParserFactory]
logParserFactory.getLogParsers()
      .filter(e => e.name().equals(className))
      .foreach(e => e.run(params, sparkSession))

5、新来了一个需求就啥都不用改,再写一个类就可以了.

@Component
class View extends LogParser {
    override def run(params: ParseParams, sparkSession: SparkSession): Unit = {
        //业务逻辑.
    }
}

2.遇到的坑

2.1 spring版本不统一.

其他的依赖包里面经常会依赖不同版本的spring,导致程序运行时报错NoClassDefFoundError.这个时候用mvn dependency:tree查看一下。exclude掉其他version的spring就好了。

2.2 spring的xsi规则配置

由于spring是在spark集群中跑,xsi有可能定义为http形式可能获取不到,所以指定到classpath的路径下本地获取:

xsi:schemaLocation="http://www.springframework.org/schema/beans
            classpath:/org/springframework/beans/factory/xml/spring-beans-4.1.xsd
http://www.springframework.org/schema/context
            classpath:/org/springframework/context/config/spring-context-4.1.xsd">

3.源码

Acceml/offline_job_babelgithub.com图标

4.约束

  • 类名不能重复,不然会报错,这是spring的性质决定的.

祝搬砖愉快 ⊙ω⊙


作者简介:

毕业于哈尔滨工业大学,就职小米科技,负责小米广告在线引擎的研发。

更多后端知识,请关注知乎专栏:春风得意码蹄疾


阅读更多 登录后自动展开
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页