# spark-project 姓名:郭元 QQ:616526018 在这段时间,我主要学习了北风网的Spark大型项目实战,一下是我的认识。 --------------------------------- 本项目的主要开发流程如下: 数据分析(来源数据的分析) 需求分析(基于上述数据,要实现什么样的需求和功能) 技术方案设计(基于来源数据与需求,以及你所掌握的spark技术,设计方案来实现需求功能) 数据库设计(技术方案设计完了以后,要配合着技术方案,设计数据库中表) 编码实现(基于上述所有的东西,使用你掌握的spark技术,来编码,实现功能) 功能测试(包括本地测试和生产环境测试,spark的client和cluster的说明) 性能调优(spark core、spark sql、spark streaming) troubleshooting(项目上线以后,要及时解决出现的线上故障与报错) 解决数据倾斜(后期维护过程中,可能会出现的严重的性能问题) --------------------------------- 在本次项目中主要涉及了4个模块: 模块1:用户访问session分析模块 用户session分析业务:复杂业务逻辑,session聚合统计、session随机抽取、top10热门品类、top10活跃用户 技术点:数据的过滤与聚合、自定义Accumulator、按时间比例随机抽取算法、二次排序、分组取topN 性能调优方案:普通调优、jvm调优、shuffle调优、算子调优 troubleshooting经验 数据倾斜解决方案:7种方案 模块2:页面单跳转化率模块 页面单跳转化率计算业务 页面切片生成以及页面流匹配算法 模块3:各区域热门商品统计模块 Spark SQL 区域级别的热门商品的统计业务 技术点:Hive与MySQL异构数据源、RDD转换为DataFrame、注册和使用临时表、 自定义UDAF聚合函数、自定义get_json_object等普通函数、Spark SQL的高级内置函数(if与case when等)、开窗函数(高端) Spark SQL数据倾斜解决方案 模块4:广告点击流量实时统计模块 广告点击流量的实时统计的业务 技术点:动态黑名单机制(动态生成黑名单以及黑名单过滤)、transform、updateStateByKey、transform与Spark SQL整合、window滑动窗口、高性能写数据库 HA方案:高可用性方案,3种 性能调优:常用的性能调优的技巧 ----------------------------------------- 在本次项目中,我最开心的是学到了项目中包的划分,以及为什么这个样子设计。我认为这个很重要。 首先一个项目,一般都会包含以下几个包 constant-----常量包 conf -----这个包主要用于设置项目的配置,他的作用主要就是读取我们的配置文件,获取用户 设置的值 domain ----- domain包里面主要存放javaBean,和数据库里面的表对应 model ----- model包里面主要存放javaBean,主要存放项目中用到的javaBean,注意区分domain jdbc ----- 这个里面主要涉及操作数据库的文件,在本次项目中,首先通过单例模式导致该类只 实例化一个,在其实例化的时候,需要调用其构造方法,我们在构造方法里面建立了 一个简单的数据库连接池,数据库连接池本来就实现了可以说是队列的模式,即连接池 用光的时候,其他等待建立连接的任务需要等待,直到有其他的链接别释放。这这个里面 我学会到了数据库连接池的简单建立方法,最令我开心的是,在这个里面我学会了函数 作为变量进行传入(其实就是定义一个接口)。 util ----- 工具包,一般用于存放项目中用到的一些方法,里面注意分类,多建立几个类, 例如:DateUtils.java NumberUtils.java ParamUtils.java .......分类管理 dao ----- dao 包 factory ---工厂方法包 ---- 如果程序里面有100个地方使用到了TaskDAOImpl,如果不使用工厂设计模式 那么程序里面出现的所有的ITaskDAO task = new TaskDAOImpl都要全部修改 维护将非常困难。有了工厂方法,我们可以在该包下面的一个类中,建立一个 方法,如下: public static ITaskDAO getTaskDAO() { return new TaskDAOImpl(); } 这个样子,如果有一天ITaskDAO实现类变化,我们只需要到该方法下面改变 返回值就可以了 impl ----dao包下面接口的实现类 TaskDAOImpl.java --举例,impl包下的一个接口实现类 ITaskDAO.java --举例,dao包下面的一个接口 DAO模式 Data Access Object:数据访问对象 首先,你的应用程序,肯定会有业务逻辑的代码。在三层架构的web系统中,业务逻辑的代码, 就是在你的Service组件里面;在我们的spark作业中,业务逻辑代码就是在我们的spark作业里面。 如果说,你不用DAO模式的话,那么所有的数据库访问的代码和逻辑会全部耦合在业务逻辑代码里面。 比如,你的业务逻辑代码中,可能会充斥着JDBCHelper,定义SQL语句,处理查询返回结果等等代码。会 导致业务逻辑和数据访问严重耦合。导致以后如果你只是想修改数据访问的代码,那么还得在一大堆业务 逻辑的代码中去找,找到这段代码,然后去做对应的修改。 因此我们要将数据库访问的代码,封装在DAO中,然后呢,业务逻辑的代码,就可以直接去调用DAO组件, 实现数据库操作的逻辑。如果这样做了以后,那么在业务逻辑的代码中,就不可能出现SQL语句、查询结果处 理等等这些东西。当后面对系统进行维护的时候,如果你只是要优化一条SQL语句,那么你肯定不用跑到业务 逻辑的代码里面去找到这条SQL语句,然后去修改它。。。 只需要到业务逻辑代码调用的DAO组件里面去,找到对应的SQL语句,修改,即可。 引入了DAO模式以后,就大大降低了业务逻辑层和数据访问层的耦合,大大提升了后期的系统维护的效率, 并降低了时间成本。 我们自己在实现DAO模式的时候,通常来说,会将其分为两部分,一个是DAO接口;一个是DAO实现类。 我们的业务的代码,通常就是面向接口进行编程;那么当接口的实现需要改变的时候,直接定义一个新的 实现即可。但是对于我们的业务代码来说,只要面向接口开发就可以了。DAO的改动对业务代码应该没有任 何的影响。 所以,如果我们要在我们的代码中插入数据到数据库,我们首先需要在domain包下面建一个与数据库相应表对应的javaBean, 然后我们需要到dao下面建立一个接口,定义一个接口,该接口里面定义一下我们操作数据库的方法,然后我们到dao包下面的 impl包下面定义一个实现该接口的类,实现其里面定义的方法,最后,我们需要在dao包下面的factory包下面的工厂类下面添加 一个方法,用于返回该实现类。 这个样子,我们在我们的程序中,主需要面向接口变成,例如: SessionDetail sessionDetail = new SessionDetail();---javaBean ........---赋值 ISessionDetailDAO sessionDetailDAO = ----接口 DAOFactory.getSessionDetailDAO(); --工厂方法用来获取该接口实现类 sessionDetailDAO.insert(sessionDetail); ---面向接口编程,调用其中的方法 就可以了 ------------------------------------------ 内部类和外部类: 外部类: 最普通的,我们平时见到的那种类,就是在一个后缀为.java的文件中,直接定义的类,比如 public class Student { private String name; private int age; } 内部类: 内部类,顾名思义,就是包含在外部类中的类,就叫做内部类。内部类有两种,一种是静态内部类,一种是非静态内部类。 public class School { private static School instance = null; static class Teacher {} } public class School { private String name; class Teacher {} } 静态内部类和非静态内部类之间的区别主要如下: 1、内部原理的区别: 静态内部类是属于外部类的类成员,是一种静态的成员,是属于类的,就有点类似于private static Singleton instance = null; 非静态内部类,是属于外部类的实例对象的一个实例成员,也就是说,每个非静态内部类,不是属于外部类的,是属于外部类的 每一个实例的,创建非静态内部类的实例以后,非静态内部类实例,是必须跟一个外部类的实例进行关联和有寄存关系的。 2、创建方式的区别: 创建静态内部类的实例的时候,只要直接使用“外部类.内部类()”的方式,就可以,比如new School.Teacher(); 创建非静态内部类的实例的时候,必须要先创建一个外部类的实例,然后通过外部类的实例,再来创建内部类的实例, new School().Teader() 通常来说,我们一般都会为了方便,会选择使用静态内部类。 匿名内部类: public interface ISayHello { String sayHello(String name); } public class SayHelloTest { public static void main(String[] args) { ISayHello obj = new ISayHello() { public String sayHello(String name) { return "hello, " + name } } System.out.println(obj.sayHello("leo")) } } 匿名内部类的使用场景,通常来说,就是在一个内部类,只要创建一次,使用一次,以后就不再使用的情况下,就可以。 那么,此时,通常不会选择在外部创建一个类,而是选择直接创建一个实现了某个接口、或者继承了某个父类的内部类, 而且通常是在方法内部,创建一个匿名内部类。 在使用java进行spark编程的时候,如果使用的是java7以及之前的版本,那么通常在对某个RDD执行算子,并传入算子的函数 的时候,通常都会传入一个实现了某个Spark Java API中Function接口的匿名内部类。 ------------------------------------ javaBean,domain,model JavaBean通常怎么用?通常来说,会将一个JavaBean,与数据库中的某个表一一对应起来 比如说,有一个student表,create table student(name varchar(30), age integer),那么这个表,如果要操作的话, 通常来说,会在程序中,建立一个对应的JavaBean,这个JavaBean中,所有的field,都是和表中的字段一一对应起来的。 然后呢,在执行增删改查操作的时候,其实都是面向JavaBean来操作的,比如insertStudent()方法,就应该接收一个参数, Student对象;findAllStudent()方法,就应该将返回类型设置为List<Student>列表 domain的概念:在系统中,通常会分很多层,比如经典的三层架构,控制层、业务层、数据访问层(DAO层) 此外,还有一个层,就是domain层 domain层,通常就是用于放置这个系统中,与数据库中的表,一一对应起来的JavaBean的 三层架构+domain层+model层(J2EE web系统) 浏览器->后台->控制层->业务层->数据访问层->数据库 domain->domain->domain->SQL domain/model<- domain和model可能都是JavaBean;之间的区别,只是用途不太一样,domain通常就代表了与数据库表一一对应的JavaBean; model通常代表了不与数据库一一对应的JavaBean,但是封装的数据,是前端的JS脚本,需要使用的一些数据。 ----------------------------------- 工厂模式 如果没有工厂模式,可能会出现的问题: ITaskDAO接口和TaskDAOImpl实现类;实现类是可能会更换的;那么,如果你就使用普通的方式来创建DAO,比如 ITaskDAO taskDAO = new TaskDAOImpl(),那么后续,如果你的TaskDAO的实现类变更了,那么你就必须在你的程序中, 所有出现过TaskDAOImpl的地方,去更换掉这个实现类。这是非常非常麻烦的。 如果说,你的TaskDAOImpl这个类,在你的程序中出现了100次,那么你就需要修改100个地方。这对程序的维护是一场灾难。 工厂设计模式 对于一些种类的对象,使用一个工厂,来提供这些对象创建的方式,外界要使用某个类型的对象时,就直接通过工厂来获取即可。 不用自己手动一个一个地方的去创建对应的对象。 那么,假使我们有100个地方用到了TaskDAOImpl。不需要去在100个地方都创建TaskDAOImpl()对象,只要在100个地方, 都使用TaskFactory.getTaskDAO()方法,获取出来ITaskDAO接口类型的对象即可。 如果后面,比如说MySQL迁移到Oracle,我们重新开发了一套TaskDAOImpl实现类,那么就直接在工厂方法中,更换掉这个类即可。 不需要再所有使用到的地方都去修改。 -----JSON--------------------- 什么是JSON? 就是一种数据格式;比如说,我们现在规定,有一个txt文本文件,用来存放一个班级的成绩;然后呢,我们规定, 这个文本文件里的学生成绩的格式,是第一行,就是一行列头(姓名 班级 年级 科目 成绩),接下来,每一行就是 一个学生的成绩。那么,这个文本文件内的这种信息存放的格式,其实就是一种数据格式。 学生 班级 年级 科目 成绩 张三 一班 大一 高数 90 李四 二班 大一 高数 80 ok,对应到JSON,它其实也是代表了一种数据格式,所谓数据格式,就是数据组织的形式。比如说,刚才所说的学生成绩, 用JSON格式来表示的话,如下: [{"学生":"张三", "班级":"一班", "年级":"大一", "科目":"高数", "成绩":90}, {"学生":"李四", "班级":"二班", "年级":"大一", "科目":"高数", "成绩":80}] 其实,JSON,很简单,一点都不复杂,就是对同样一批数据的,不同的一种数据表示的形式。 JSON的数据语法,其实很简单:如果是包含多个数据实体的话,比如说多个学生成绩,那么需要使用数组的表现形式, 就是[]。对于单个数据实体,比如一个学生的成绩,那么使用一个{}来封装数据,对于数据实体中的每个字段以及对应的值, 使用key:value的方式来表示,多个key-value对之间用逗号分隔;多个{}代表的数据实体之间,用逗号分隔。 扩展一下: JSON在企业级项目开发过程中,扮演的角色是无比重要的。最常用的地方,莫过于基于Ajax的前端和后端程序之间的通信。 比如说,在前端页面中,可以不刷新页面,直接发送一个Ajax异步请求到后端,后端返回一个JSON格式的数据,然后前端 使用JSON格式的数据,渲染页面中的对应地方的信息。 在我们的项目中,JSON是起到了什么作用呢?我们在task表中的task_param字段,会存放不同类型的任务对应的参数。 比如说,用户访问session分析模块与页面单跳转化率统计模块的任务参数是不同的,但是,使用同一张task表来存储 所有类型的任务。那么,你怎么来存储不同类型的任务的不同的参数呢?你的表的字段是事先要定好的呀。 所以,我们采取了,用一个task_param字段,来存储不同类型的任务的参数的方式。task_param字段中,实际上会存储 一个任务所有的字段,使用JSON的格式封装所有任务参数,并存储在task_param字段中。就实现了非常灵活的方式。 如何来操作JSON格式的数据? 比如说,要获取JSON中某个字段的值。我们这里使用的是阿里的fastjson工具包。使用这个工具包,可以方便的将字符 串类型的JSON数据,转换为一个JSONObject对象,然后通过其中的getX()方法,获取指定的字段的值。 ----------------- 有关性能调优等笔记见《spark性能调优.docx》