Spark零基础入门——Spark入门基础 本节主要内容 (一)Spark简介 (二)Spark集群安装 (三)Spark开发环境搭建 (四)Spark源码阅读环境构建 (五)Spark应用程序调试 (六)spark-shell使用 2018/11/15
Spark入门基础——(一)Spark简介 从Hadoop说起 Map Reduce迭代计算示意图 2018/11/15
Spark入门基础——(一)Spark简介 从Hadoop说起 三大重要应用领域: 批处理 交互式数据查询 实时计算 2018/11/15
Spark入门基础——(一)Spark简介 从Hadoop说起 生态系统技术混杂 SQL On Hadoop: (1)Impala, Cloudera公司 (2)Hive,Facebook公司 (3)Pesto,Facebook公司 (4)Stinger, Hortonworks公司 2018/11/15
Spark入门基础——(一)Spark简介 从Hadoop说起 Hadoop生态圈带来的挑战 A different technology stack is required to solve each type of use case, because some solutions are not reusable across different use cases. Proficiency in several technologies is required for productivity Some technologies face version compatibility issues It is unsuitable for faster data-sharing needs across parallel jobs. 2018/11/15
Spark入门基础——(一)Spark简介 Spark提供一栈式解决方案,即常说的“One stack rules them all” 2018/11/15
Spark入门基础——(一)Spark简介 2018/11/15
Spark入门基础——(一)Spark简介 2018/11/15
Spark入门基础——(一)Spark简介 2018/11/15
Spark入门基础——(一)Spark简介 (1)计算速度更快 数据全部装配到内存,速度可以达到Hadoop的100倍 即使内存不够用,需要写磁盘,其速度也比Hadoop快10倍 2018/11/15
Spark入门基础——(一)Spark简介 (2)支持多语言编程,包括Java、Scala、Python、SQL及R语言 (3)支持不同版本的Hadoop平台,同时又拥有独立的资源管理器 2018/11/15
Spark入门基础——(一)Spark简介 (4)提供REPL(Read-Eval-Print-Loop)交互式命令行 (5)一栈式解决方案,可以集中精力解决计算业务。 2018/11/15
Spark入门基础——(一)Spark简介 2018/11/15
Spark入门基础——(二)Spark集群安装 1.Ubuntu 操作系统安装 2. ZooKeeper 3.4.5集群搭建 3. Hadoop 2.4.1集群搭建 4. Spark 1.6.1 集群搭建 2018/11/15
Spark入门基础——(二)Spark集群安装 1.Ubuntu 操作系统安装 1. 主机规划 主机名 IP地址 进程号 SparkMaster 192.168.1.103 ResourceManager DataNode、NodeManager、JournalNode、QuorumPeerMain、Master、Worker SparkSlave01 192.168.1.101 ResourceManager DataNode、NodeManager、JournalNode、QuorumPeerMain NameNode、DFSZKFailoverController(zkfc)、Worker SparkSlave02 192.168.1.102 DataNode、NodeManager、JournalNode、QuorumPeerMain NameNode、DFSZKFailoverController(zkfc)、Worker 2018/11/15
Spark入门基础——(二)Spark集群安装 说明: 1.在hadoop2.X中通常由两个NameNode组成,一个处于active状态,另一个处于standby状态。Active NameNode对外提供服务,而Standby NameNode则不对外提供服务,仅同步active namenode的状态,以便能够在它失败时快速进行切换。 hadoop2.X官方提供了两种HDFS HA的解决方案,一种是NFS,另一种是QJM。这里我们使用简单的QJM。在该方案中,主备NameNode之间通过一组JournalNode同步元数据信息,一条数据只要成功写入多数JournalNode即认为写入成功。通常配置奇数个JournalNode 2. zookeeper集群,用于ZKFC(DFSZKFailoverController)故障转移,当Active NameNode挂掉了,会自动切换Standby NameNode为standby状态 3.hadoop-2.2.0中依然存在一个问题,就是ResourceManager只有一个,存在单点故障,hadoop-2.4.1解决了这个问题,有两个ResourceManager,一个是Active,一个是Standby,状态由zookeeper进行协调 2018/11/15
/etc/network/interfaces文件内容 Spark入门基础——(二)Spark集群安装 3. 修改主机IP地址 利用vi /etc/network/interfaces修改主要IP 主机 /etc/network/interfaces文件内容 SparkMaster auto loiface lo inet loopback auto eth0 iface eth0 inet static address 192.168.1.103 netmask 255.255.255.0 gateway 192.168.1.1 SparkSlave01 auto loiface lo inet loopback auto eth0 iface eth0 inet static address 192.168.1.101 netmask 255.255.255.0 gateway 192.168.1.1 SparkSlave02 auto loiface lo inet loopback auto eth0 iface eth0 inet static address 192.168.1.102 netmask 255.255.255.0 gateway 192.168.1.1 2018/11/15
domain localdomain search localdomain nameserver 8.8.8.8 Spark入门基础——(二)Spark集群安装 4. 修改域名解析服务器 由于需要联网安装OpenSSH等实现名密码登录,因此这边需要配置对应的域名解析服务器 主机 /etc/resolv.conf文件内容 SparkMaster domain localdomain search localdomain nameserver 8.8.8.8 SparkSlave01 SparkSlave02 2018/11/15
Spark入门基础——(二)Spark集群安装 5.修改主机名与IP地址映射 主机 /etc/resolv.conf文件内容 SparkMaster 127.0.0.1 SparkMaster localhost.localdomain localhost 192.168.1.101 SparkSlave01 192.168.1.102 SparkSlave02 192.168.1.103 SparkMaster ::1 localhost ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters ff02::3 ip6-allhosts 2018/11/15
Spark入门基础——(二)Spark集群安装 SparkSlave01 127.0.0.1 SparkSlave01 localhost.localdomain localhost 192.168.1.101 SparkSlave01 192.168.1.102 SparkSlave02 192.168.1.103 SparkMaster ::1 localhost ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters ff02::3 ip6-allhosts SparkSlave02 127.0.0.1 SparkSlave02 localhost.localdomain localhost 192.168.1.101 SparkSlave01 192.168.1.102 SparkSlave02 192.168.1.103 SparkMaster ::1 localhost ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters ff02::3 ip6-allhosts 2018/11/15
Spark入门基础——(二)Spark集群安装 6.安装SSH (三台主机执行相同命令) sudo apt-get install openssh-server 然后确认sshserver是否启动了: ps -e |grep ssh 7.设置无密码登录 (三台主机执行相同命令) 执行命令:ssh-keygen -t rsa 执行完这个命令后,会生成两个文件id_rsa(私钥)、id_rsa.pub(公钥) 将公钥拷贝到要免登陆的机器上 cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys 或 ssh-copy-id -i SparkMaster ssh-copy-id -i SparkSlave02 ssh-copy-id -i SparkSlave01 2018/11/15
Spark入门基础——(二)Spark集群安装 2. ZooKeeper 3.4.5集群搭建 本集群用的ZooKeeper版本是3.4.5,将/hadoopLearning/zookeeper-3.4.5/conf目录下的zoo_sample.cfg文件名重命名为zoo.cfg vi conf/zoo.cfg,在文件中填入以下内容: # The number of milliseconds of each tick tickTime=2000 # The number of ticks that the initial # synchronization phase can take initLimit=10 # The number of ticks that can pass between # sending a request and getting an acknowledgement syncLimit=5 # the directory where the snapshot is stored. # do not use /tmp for storage, /tmp here is just # example sakes. # ZK文件存放目录 dataDir=/hadoopLearning/zookeeper-3.4.5/zookeeper_data # the port at which the clients will connect clientPort=2181 2018/11/15
Spark入门基础——(二)Spark集群安装 # Be sure to read the maintenance section of the # administrator guide before turning on autopurge. # #http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance # The number of snapshots to retain in dataDir #autopurge.snapRetainCount=3 # Purge task interval in hours # Set to "0" to disable auto purge feature #autopurge.purgeInterval=1 server.1=SparkSlave01:2888:3888 server.2=SparkSlave02:2888:3888 server.3=SparkMaster:2888:3888 2018/11/15
Spark入门基础——(二)Spark集群安装 在/hadoopLearning/zookeeper-3.4.5/目录下创建zookeeper_data 然后cd zookeeper_data进入该目录,执行命令 touch myid echo 3 > myid 利用scp -r zookeeper-3.4.5 root@SparkSlave01:/hadoopLearning/ scp -r zookeeper-3.4.5 root@SparkSlave02:/hadoopLearning/ 将文件拷贝到其它服务器上,然后分别进入zookeeper_data目录执行SparkSlave01服务器上echo 1> myid SparkSlave02服务器上echo 2> myid root@SparkMaster:/hadoopLearning/zookeeper-3.4.5/bin ./zkServer.sh start 在其它两台机器上执行相同操作 root@SparkMaster:/hadoopLearning/zookeeper-3.4.5/bin zkServer.sh status JMX enabled by default Using config: /hadoopLearning/zookeeper-3.4.5/bin/../conf/zoo.cfg Mode: leader 至此ZooKeeper集群搭建完毕 2018/11/15
Spark入门基础——(二)Spark集群安装 3. Hadoop 2.4.1集群搭建 将Hadoop安装路径HAD00P_HOME=/hadoopLearning/hadoop-2.4.1加入到环境变量 export JAVA_HOME=/hadoopLearning/jdk1.7.0_67 export JRE_HOME=${JAVA_HOME}/jre export HAD00P_HOME=/hadoopLearning/hadoop-2.4.1 export SCALA_HOME=/hadoopLearning/scala-2.10.4 export ZOOKEEPER_HOME=/hadoopLearning/zookeeper-3.4.5 export CLASS_PATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib export PATH=${JAVA_HOME}/bin:${HAD00P_HOME}/bin:${HAD00P_HOME}/sbin:${ZOOKEEPER_HOME}/bin:${SCALA_HOME}/bin:/hadoopLearning/idea-IC-141.1532.4/bin:$PATH 2018/11/15
Spark入门基础——(二)Spark集群安装 修改hadoo-env.sh export JAVA_HOME=/usr/java/jdk1.7.0_55 修改core-site.xml <configuration> <!-- 指定hdfs的nameservice为ns1 --> <property> <name>fs.defaultFS</name> <value>hdfs://ns1</value> </property> <!-- 指定hadoop临时目录 --> <name>hadoop.tmp.dir</name> <value>/hadoopLearning/hadoop-2.4.1/tmp</value> <!-- 指定zookeeper地址 --> <name>ha.zookeeper.quorum</name> <value>SparkMaster:2181,SparkSlave01:2181,SparkSlave02:2181</value> </configuration> 2018/11/15
Spark入门基础——(二)Spark集群安装 修改hdfs-site.xml <configuration> <!--指定hdfs的nameservice为ns1,需要和core-site.xml中的保持一致 --> <property> <name>dfs.nameservices</name> <value>ns1</value> </property> <!-- ns1下面有两个NameNode,分别是nn1,nn2 --> <name>dfs.ha.namenodes.ns1</name> <value>nn1,nn2</value> <!-- nn1的RPC通信地址 --> <name>dfs.namenode.rpc-address.ns1.nn1</name> <value>SparkSlave01:9000</value> <!-- nn1的http通信地址 --> <name>dfs.namenode.http-address.ns1.nn1</name> <value>SparkSlave01:50070</value> 2018/11/15
Spark入门基础——(二)Spark集群安装 <!-- nn2的RPC通信地址 --> <property> <name>dfs.namenode.rpc-address.ns1.nn2</name> <value>SparkSlave02:9000</value> </property> <!-- nn2的http通信地址 --> <name>dfs.namenode.http-address.ns1.nn2</name> <value>SparkSlave02:50070</value> <!-- 指定NameNode的元数据在JournalNode上的存放位置 --> <name>dfs.namenode.shared.edits.dir</name> <value>qjournal://SparkMaster:8485;SparkSlave01:8485;SparkSlave02:8485/ns1</value> 2018/11/15
Spark入门基础——(二)Spark集群安装 <!-- 开启NameNode失败自动切换 --> <property> <name>dfs.ha.automatic-failover.enabled</name> <value>true</value> </property> <!-- 配置失败自动切换实现方式 --> <name>dfs.client.failover.proxy.provider.ns1</name> <value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value> <!-- 配置隔离机制方法,多个机制用换行分割,即每个机制暂用一行--> <name>dfs.ha.fencing.methods</name> <value> sshfence shell(/bin/true) </value> <!-- 使用sshfence隔离机制时需要ssh免登陆 --> <name>dfs.ha.fencing.ssh.private-key-files</name> <value>/home/hadoop/.ssh/id_rsa</value> <!-- 配置sshfence隔离机制超时时间 --> <name>dfs.ha.fencing.ssh.connect-timeout</name> <value>30000</value> </configuration> 2018/11/15
Spark入门基础——(二)Spark集群安装 修改mapred-site.xml <configuration> <!-- 指定mr框架为yarn方式 --> <property> <name>mapreduce.framework.name</name> <value>yarn</value> </property> </configuration> 2018/11/15
Spark入门基础——(二)Spark集群安装 修改yarn-site.xml <configuration> <!-- 开启RM高可靠 --> <property> <name>yarn.resourcemanager.ha.enabled</name> <value>true</value> </property> <!-- 指定RM的cluster id --> <name>yarn.resourcemanager.cluster-id</name> <value>SparkCluster</value> <!-- 指定RM的名字 --> <name>yarn.resourcemanager.ha.rm-ids</name> <value>rm1,rm2</value> 2018/11/15
Spark入门基础——(二)Spark集群安装 <!-- 分别指定RM的地址 --> <property> <name>yarn.resourcemanager.hostname.rm1</name> <value>SparkMaster</value> </property> <name>yarn.resourcemanager.hostname.rm2</name> <value>SparkSlave01</value> <!-- 指定zk集群地址 --> <name>yarn.resourcemanager.zk-address</name> <value>SparkMaster:2181,SparkSlave01:2181,SparkSlave02:2181</value> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle</value> </configuration> 2018/11/15
Spark入门基础——(二)Spark集群安装 配置Slaves SparkMaster SparkSlave01 SparkSlave02 将配置好的hadoop-2.4.1拷到其它服务器上 scp -r /etc/profile root@SparkSlave01:/etc/profile scp -r /hadoopLearning/hadoop-2.4.1/ root@SparkSlave01:/hadoopLearning/ scp -r /etc/profile root@SparkSlave02:/etc/profile scp -r /hadoopLearning/hadoop-2.4.1/ root@SparkSlave02:/hadoopLearning/ 2018/11/15
Spark入门基础——(二)Spark集群安装 启动journalnode hadoop-daemons.sh start journalnode #运行jps命令检验,SparkMaster、SparkSlave01、SparkSlave02上多了JournalNode进程 格式化HDFS #在SparkSlave01上执行命令: hdfs namenode -format #格式化后会在根据core-site.xml中的hadoop.tmp.dir配置生成个文件,这里我配置的是/hadoopLearning/hadoop-2.4.1/tmp,然后将/hadoopLearning/hadoop-2.4.1/tmp拷贝到SparkSlave02的/hadoopLearning/hadoop-2.4.1/下。 scp -r tmp/ sparkslave02:/hadoopLearning/hadoop-2.4.1/ 2018/11/15
Spark入门基础——(二)Spark集群安装 格式化ZK(在SparkSlave01上执行即可) hdfs zkfc -formatZK 启动HDFS(在SparkSlave01上执行) sbin/start-dfs.sh 启动YARN(#####注意#####:是在SparkMaster上执行start-yarn.sh,把namenode和resourcemanager分开是因为性能问题,因为他们都要占用大量资源,所以把他们分开了,他们分开了就要分别在不同的机器上启动) sbin/start-yarn.sh 2018/11/15
Spark入门基础——(二)Spark集群安装 打开浏览器输入: http://sparkmaster:8088可以看到以下页面: 2018/11/15
Spark入门基础——(二)Spark集群安装 输入http://sparkslave01:50070可以看到以下页面: 2018/11/15
Spark入门基础——(二)Spark集群安装 输入http://sparkslave02:50070可以看到以下页面 2018/11/15
Spark入门基础——(二)Spark集群安装 输入以下命令上传文件到hadoop hadoop fs -put /etc/profile / 在active namenode上查看上传成功的文件 2018/11/15
Spark入门基础——(三)Spark开发环境搭建 1. Intellij IDEA开发环境配置 2. Spark 应用程序开发 2018/11/15
Spark入门基础——(三)Spark开发环境搭建 1. Intellij IDEA开发环境配置 (1)创建Scala项目 File->new->Project,如下图 2018/11/15
Spark入门基础——(三)Spark开发环境搭建 2018/11/15
Spark入门基础——(三)Spark开发环境搭建 2018/11/15
Spark入门基础——(三)Spark开发环境搭建 2018/11/15
Spark入门基础——(三)Spark开发环境搭建 2018/11/15
Spark入门基础——(三)Spark开发环境搭建 2018/11/15
Spark入门基础——(三)Spark开发环境搭建 ### (2)导入Spark 1.5.0依赖包 直接F4打开Project Structure,然后选择libraries 2018/11/15
Spark入门基础——(三)Spark开发环境搭建 2018/11/15
Spark入门基础——(三)Spark开发环境搭建 2018/11/15
Spark入门基础——(三)Spark开发环境搭建 (1) 本地方式执行Spark WordCount程序 在src/main/scala源文件目录中创建一个SparkWordCount 应用程序对象,编辑内容如下: import org.apache.spark.SparkContext._ import org.apache.spark.{SparkConf, SparkContext} object SparkWordCount{ def main(args: Array[String]) { //输入文件既可以是本地linux系统文件,也可以是其它来源文件,例如HDFS if (args.length == 0) { System.err.println("Usage: SparkWordCount <inputfile>") System.exit(1) } //以本地线程方式运行,可以指定线程个数, //如.setMaster("local[2]"),两个线程执行 //下面给出的是单线程执行 val conf = new SparkConf().setAppName("SparkWordCount").setMaster("local") val sc = new SparkContext(conf) //wordcount操作,计算文件中包含Spark的行数 val count=sc.textFile(args(0)).filter(line => line.contains("Spark")).count() //打印结果 println("count="+count) sc.stop() 2018/11/15
Spark入门基础——(三)Spark开发环境搭建 编程运行参数,Run->Edit Configurations 2018/11/15
Spark入门基础——(三)Spark开发环境搭建 Main Class输入:SparkWordCount Program arguments输入:/hadoopLearning/spark-1.5.0-bin-hadoop2.4/README.md 2018/11/15
Spark入门基础——(三)Spark开发环境搭建 完成后直接Run->Run或Alt+Shift+F10运行程序,执行结果如下图: 2018/11/15
Spark入门基础——(三)Spark开发环境搭建 (2) Spark集群上执行Spark WordCount程序 将SparkWordCount打包成Jar文件 将程序内容修改如下: import org.apache.spark.SparkContext._ import org.apache.spark.{SparkConf, SparkContext} object SparkWordCount{ def main(args: Array[String]) { //输入文件既可以是本地linux系统文件,也可以是其它来源文件,例如HDFS if (args.length == 0) { System.err.println("Usage: SparkWordCount <inputfile> <outputfile>") System.exit(1) } val conf = new SparkConf().setAppName("SparkWordCount") val sc = new SparkContext(conf) //rdd2为所有包含Spark的行 val rdd2=sc.textFile(args(0)).filter(line => line.contains("Spark")) //保存内容,在例子中是保存在HDFS上 rdd2.saveAsTextFile(args(1)) sc.stop() 2018/11/15
Spark入门基础——(三)Spark开发环境搭建 点击工程SparkWordCount,然后按F4打个Project Structure并选择Artifacts,如下图 2018/11/15
Spark入门基础——(三)Spark开发环境搭建 选择Jar->form modules with dependencies,如下图 2018/11/15
Spark入门基础——(三)Spark开发环境搭建 2018/11/15
Spark入门基础——(三)Spark开发环境搭建 在main class中,选择SparkWordCount,如下图 2018/11/15
Spark入门基础——(三)Spark开发环境搭建 点击确定后得到如下界面 2018/11/15
Spark入门基础——(三)Spark开发环境搭建 因为后期提交到集群上运行,因此相关jar包都存在,为减小jar包的体积,将spark-assembly-1.5.0-hadoop2.4.0.jar等jar包删除即可,如下图 2018/11/15
Spark入门基础——(三)Spark开发环境搭建 确定后,再点击Build->Build Artifacts 2018/11/15
Spark入门基础——(三)Spark开发环境搭建 提交集群运行 ./spark-submit --master spark://sparkmaster:7077 --class SparkWordCount --executor-memory 1g /root/IdeaProjects/SparkWordCount/out/artifacts/SparkWordCount_jar/SparkWordCount.jar hdfs://ns1/README.md hdfs://ns1/SparkWordCountResult 2018/11/15
Spark入门基础——(三)Spark开发环境搭建 执行结果: 2018/11/15
Spark入门基础——(三)Spark开发环境搭建 HDFS文件已经生成了SparkWordCountResult 2018/11/15
Spark入门基础——(四)Spark源码阅读环境构建 在项目中关联源码 通过Intellij IDEA 从GitHub上加载 2018/11/15
Spark入门基础——(四)Spark源码阅读环境构建 2018/11/15
Spark入门基础——(四)Spark源码阅读环境构建 2018/11/15
Spark入门基础——(四)Spark源码阅读环境构建 2018/11/15
Spark入门基础——(四)Spark源码阅读环境构建 2018/11/15
Spark入门基础——(四)Spark源码阅读环境构建 2018/11/15
Spark入门基础——(五)Spark应用程序调试 1. 本地调试 2. 远程调试 2018/11/15
Spark入门基础——(六)Spark-Shell的使用 调用脚本顺序 spark-shell spark-submit spark-class 2018/11/15 spark-shell源码
Spark入门基础——(六)Spark-Shell的使用 调用脚本顺序 spark-shell spark-submit spark-class 2018/11/15 spark-shell源码
Spark入门基础——(六)Spark-Shell的使用 调用脚本顺序 spark-shell spark-submit spark-class 2018/11/15 spark-shell源码
Spark入门基础——(六)Spark-Shell的使用 /opt/hadoopLearning/jdk1.7.0_67/bin/java -cp /opt/hadoopLearning/spark-1.5.2-bin-hadoop2.4/conf/opt/hadoopLearning/spark-1.5.2-bin-hadoop2.4/lib spark-assembly-1.5.2-hadoop2.4.0.jar/opt/hadoopLearning/spark-1.5.2-bin-hadoop2.4/lib/datanucleus-core-3.2.10.jar/opt/hadoopLearning/spark-1.5.2-bin-hadoop2.4/lib/datanucleus-rdbms-3.2.9.jar/opt/hadoopLearning/spark-1.5.2-bin-hadoop2.4/lib/datanucleus-api-jdo-3.2.6.jar -XX:MaxPermSize=128m -Dscala.usejavacp=true -Xms512m -Xmx512m org.apache.spark.deploy.SparkSubmit --class org.apache.spark.repl.Main spark-shell 红色部分是CLASSPATH,绿色部分是JVM配置,紫色部分是最终加载的类和参数。 2018/11/15 spark-shell源码
jvisualvm跟踪调试 2018/11/15
修改spark-class脚本文件 2018/11/15
修改spark-default.conf文件 2018/11/15
Spark入门基础——(六)Spark-Shell的使用 2018/11/15
Spark零基础入门——Spark入门基础 内容回顾 (一)Spark简介 (二)Spark集群安装 (三)Spark开发环境搭建 (四)Spark源码阅读环境构建 (五)Spark应用程序调试 2018/11/15
(1)Spark 入门:https://dzone.com/refcardz/apache-spark 参考文献: (1)Spark 入门:https://dzone.com/refcardz/apache-spark (2)Spark集群安装:http://blog.csdn.net/lovehuangjiaju/article/details/46883973 (3)Spark开发环境搭建:http://blog.csdn.net/lovehuangjiaju/article/details/48577281 (4)Spark应用程序调试,http://blog.csdn.net/lovehuangjiaju/article/details/49227919 2018/11/15