Apache Geode with Spark
在一些特定场景,例如streamingRDD需要和历史数据进行join从而获得一些profile信息,此时形成较小的新数据RDD和很大的历史RDD的join。
Spark中直接join实际上效率不高:
- RDD没有索引,join操作实际上是相互join的RDD进行hash然后shuffle到一起;
实际上,如果历史数据的RDD有索引,我们可以循环遍历streaming中的每一条数据,并向历史数据发送point query,即loop + indexed get。Streaming的数据是小数据,这样坐的性能会高很多。(这种小数据和大量历史数据的join模式在物联网/互联网场景下很常见)
另外,
- spark中的RDD是只读的,增量信息无法直接更新到历史RDD中
虽然我们可以使用streaming的窗口操作来缓存一定量的历史数据,但这会增加业务逻辑的复杂度。
IndexedRDD能够解决上述的两个问题,即对RDD内存数据建立索引,并且可以更新RDD。但是IndexRDD不支持事务,如果需要对同一个key做更新就存在数据更新冲突,导致数据不一致。另外,IndexRDD单纯是RDD的数据结构和接口的增强,不支持Spark之外的组件对其的访问。
本文将介绍基于Apache Geode和Spark相结合:
- 基于Geode的RDD借助Geode的内存数据存储和数据索引,其join操作是loop + indexed get方式,可以提高流数据和历史数据相join的效率;
- Geode 是目前性能和生产可用性最高的IMDG之一,基本满足ACID;
- Spark 中通过GeodeRDD的写操作实际上是将数据写入Geode,我们还可以通过JDBC等方式访问数据,甚至进行OLAP操作。
Geode和spark版本选择
- geode
https://github.com/apache/geode/blob/rel/v1.1.1/
这个版本包含了spark-connector。 - spark
选择1.6,如果选择2.0则需要手动编译基于scala2.10的包。
Geode-Spark connector编译
需要手动编译spark-connector,参照GitHub上的流程操作即可。
https://github.com/apache/geode/blob/rel/v1.1.1/geode-spark-connector/doc/1_building.md
最终会编译三个文件:
The following jar files will be created:
geode-spark-connector/target/scala-2.10/geode-spark-connector_2.10-0.5.0.jar
geode-functions/target/scala-2.10/geode-functions_2.10-0.5.0.jar
geode-spark-demos/target/scala-2.10/geode-spark-demos_2.10-0.5.0.jar
启动geode并创建region
Start Geode cluster with 1 locator and 2 servers:
gfsh
gfsh>start locator --name=locator1 --port=55221
gfsh>start server --name=server1 --locators=localhost[55221] --server-port=0
gfsh>start server --name=server2 --locators=localhost[55221] --server-port=0
Then create two demo regions:
gfsh>create region --name=str_str_region --type=PARTITION --key-constraint=java.lang.String --value-constraint=java.lang.String
gfsh>create region --name=int_str_region --type=PARTITION --key-constraint=java.lang.Integer --value-constraint=java.lang.String
Deploy Spark Geode Connector's geode-function jar (geode-functions_2.10-0.5.0.jar
):
gfsh>deploy --jar=<path to connector project>/geode-functions/target/scala-2.10/geode-functions_2.10-0.5.0.jar
Spark 启动
官网下载spark1.6.0-bin-hadoop2.6。解压后运行./sbin/start-all。
进入spark-shell并引入Geode包
export GEDE=<path to geode>/apache-geode-1.1.1/
spark-shell --master spark://Dings-MacBook-Pro.local:7077 --jars /Users/dingbingbing/hon/geode/geode/geode-spark-connector/geode-spark-connector/target/scala-2.10/geode-spark-connector_2.10-0.5.0.jar,/Users/dingbingbing/hon/geode/geode/geode-spark-connector/geode-functions/target/scala-2.10/geode-functions_2.10-0.5.0.jar,$GEDE/lib/activation-1.1.jar,$GEDE/lib/antlr-2.7.7.jar,$GEDE/lib/commons-beanutils-1.8.3.jar,$GEDE/lib/commons-io-2.4.jar,$GEDE/lib/commons-lang-2.5.jar,$GEDE/lib/commons-logging-1.2.jar,$GEDE/lib/commons-modeler-2.0.jar,$GEDE/lib/fastutil-7.0.2.jar,$GEDE/lib/findbugs-annotations-1.3.9-1.jar,$GEDE/lib/geode-common-1.1.1.jar,$GEDE/lib/geode-core-1.1.1.jar,$GEDE/lib/geode-cq-1.1.1.jar,$GEDE/lib/geode-dependencies.jar,$GEDE/lib/geode-json-1.1.1.jar,$GEDE/lib/geode-lucene-1.1.1.jar,$GEDE/lib/geode-old-client-support-1.1.1.jar,$GEDE/lib/geode-rebalancer-1.1.1.jar,$GEDE/lib/geode-wan-1.1.1.jar,$GEDE/lib/geode-web-1.1.1.jar,$GEDE/lib/gfsh-dependencies.jar,$GEDE/lib/jackson-annotations-2.8.0.jar,$GEDE/lib/jackson-core-2.8.2.jar,$GEDE/lib/jackson-databind-2.8.2.jar,$GEDE/lib/jansi-1.8.jar,$GEDE/lib/javax.mail-api-1.4.5.jar,$GEDE/lib/javax.resource-api-1.7.jar,$GEDE/lib/javax.servlet-api-3.1.0.jar,$GEDE/lib/javax.transaction-api-1.2.jar,$GEDE/lib/jetty-http-9.3.6.v20151106.jar,$GEDE/lib/jetty-io-9.3.6.v20151106.jar,$GEDE/lib/jetty-security-9.3.6.v20151106.jar,$GEDE/lib/jetty-server-9.3.6.v20151106.jar,$GEDE/lib/jetty-servlet-9.3.6.v20151106.jar,$GEDE/lib/jetty-util-9.3.6.v20151106.jar,$GEDE/lib/jetty-webapp-9.3.6.v20151106.jar,$GEDE/lib/jetty-xml-9.3.6.v20151106.jar,$GEDE/lib/jgroups-3.6.10.Final.jar,$GEDE/lib/jline-2.12.jar,$GEDE/lib/jna-4.0.0.jar,$GEDE/lib/jopt-simple-5.0.1.jar,$GEDE/lib/log4j-api-2.6.1.jar,$GEDE/lib/log4j-core-2.6.1.jar,$GEDE/lib/log4j-jcl-2.6.1.jar,$GEDE/lib/log4j-jul-2.6.1.jar,$GEDE/lib/log4j-slf4j-impl-2.6.1.jar,$GEDE/lib/lucene-analyzers-common-6.0.0.jar,$GEDE/lib/lucene-core-6.0.0.jar,$GEDE/lib/lucene-queries-6.0.0.jar,$GEDE/lib/lucene-queryparser-6.0.0.jar,$GEDE/lib/mx4j-3.0.1.jar,$GEDE/lib/mx4j-remote-3.0.1.jar,$GEDE/lib/mx4j-tools-3.0.1.jar,$GEDE/lib/netty-all-4.0.4.Final.jar,$GEDE/lib/ra.jar,$GEDE/lib/shiro-core-1.3.1.jar,$GEDE/lib/slf4j-api-1.7.21.jar,$GEDE/lib/snappy-0.4.jar,$GEDE/lib/spring-aop-4.3.2.RELEASE.jar,$GEDE/lib/spring-beans-4.3.2.RELEASE.jar,$GEDE/lib/spring-context-4.3.2.RELEASE.jar,$GEDE/lib/spring-core-4.3.2.RELEASE.jar,$GEDE/lib/spring-expression-4.3.2.RELEASE.jar,$GEDE/lib/spring-shell-1.2.0.RELEASE.jar,$GEDE/lib/spring-web-4.3.2.RELEASE.jar --conf spark.geode.locators=localhost[55221]
Check Geode locator property in the Spark shell:
scala> sc.getConf.get("spark.geode.locators")
res0: String = localhost[55221]
测试代码及原理简介
Geode可以认为是类似hdfs/hbase的数据集,不同的是:
- 基于Geode数据形成的RDD可以被修改;
- 普通的RDD可以和Geode Region数据快速join;
使用Geode Spark Connector的代码中首先import一下org.apache.geode.spark.connector._
。引入所有的implicit函数。
scala> import org.apache.geode.spark.connector._
import org.apache.geode.spark.connector._
Save Pair RDD to Geode
In the Spark shell, create a simple pair RDD and save it to Geode:
scala> val data = Array(("1", "one"), ("2", "two"), ("3", "three"))
data: Array[(String, String)] = Array((1,one), (2,two), (3,three))
scala> val distData = sc.parallelize(data)
distData: org.apache.spark.rdd.RDD[(String, String)] = ParallelCollectionRDD[0] at parallelize at <console>:14
scala> distData.saveToGemfire("str_str_region")
15/02/17 07:11:54 INFO DAGScheduler: Job 0 finished: runJob at GemFireRDDFunctions.scala:29, took 0.341288 s
此时Geode中相应region就有了刚才save的数据了gfsh
:
gfsh>query --query="select key,value from /str_str_region.entries"
Result : true
startCount : 0
endCount : 20
Rows : 3
key | value
--- | -----
1 | one
3 | three
2 | two
NEXT_STEP_NAME : END
Save Non-Pair RDD to Geode
Saving non-pair RDD to Geode requires an extra function that converts each
element of RDD to a key-value pair. Here's sample session in Spark shell:
scala> val data2 = Array("a","ab","abc")
data2: Array[String] = Array(a, ab, abc)
scala> val distData2 = sc.parallelize(data2)
distData2: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[0] at parallelize at <console>:17
scala> distData2.saveToGemfire("int_str_region", e => (e.length, e))
[info 2015/02/17 12:43:21.174 PST <main> tid=0x1]
...
15/02/17 12:43:21 INFO DAGScheduler: Job 0 finished: runJob at GemFireRDDFunctions.scala:52, took 0.251194 s
Verify the result with gfsh
:
gfsh>query --query="select key,value from /int_str_region.entrySet"
Result : true
startCount : 0
endCount : 20
Rows : 3
key | value
--- | -----
2 | ab
3 | abc
1 | a
NEXT_STEP_NAME : END
Expose Geode Region As RDD
The same API is used to expose both replicated and partitioned region as RDDs.
scala> val rdd = sc.geodeRegion[String, String]("str_str_region")
rdd: org.apache.geode.spark.connector.rdd.GemFireRDD[String,String] = GemFireRDD[2] at RDD at GemFireRDD.scala:19
scala> rdd.foreach(println)
(1,one)
(3,three)
(2,two)
scala> val rdd2 = sc.geodeRegion[Int, String]("int_str_region")
rdd2: org.apache.geode.spark.connector.rdd.GemFireRDD[Int,String] = GemFireRDD[3] at RDD at GemFireRDD.scala:19
scala> rdd2.foreach(println)
(2,ab)
(1,a)
(3,abc)
Join性能测试(极简单案例)
// 10万条数据
val device_id = sc.parallelize((1 to 100000).map(i => ("device_"+i, "device_id = "+ i + ", value="+(new scala.util.Random().nextInt()))))
// save to Geode
device_id.saveToGeode("str_str_region")
// 1000条数据作为新增数据
val new_rdd = sc.parallelize((4000 to 5000).map(i => ("device_"+i, "device_id = "+ i + ", value="+(new scala.util.Random().nextInt()))))
// 新数据和Geode中十万条join
new_rdd.joinGeodeRegion("str_str_region", p => p._1).count()
// 新增数据和十万条数据的RDD join
new_rdd.join(device_id).count()
10万条数据的性能差别有将近10倍。
具体来说,RDD跟Geode Regioin的join是循环+get操作,类似于map-only 的join。具体代码参见GeodeJoinRdd.scala
private def computeWithoutFunc(split: Partition, context: TaskContext, region: Region[K, V]): Iterator[(T, V)] = {
val leftPairs = left.iterator(split, context).toList.asInstanceOf[List[(K, _)]]
val leftKeys = leftPairs.map { case (k, v) => k}.toSet
// Note: get all will return (key, null) for non-exist entry, so remove those entries
val rightPairs = region.getAll(leftKeys).filter { case (k, v) => v != null}
leftPairs.filter{case (k, v) => rightPairs.contains(k)}
.map {case (k, v) => ((k, v).asInstanceOf[T], rightPairs.get(k).get)}.toIterator
}
而RDD跟RDD的普通join操作需要数据的shuffle,会带来很多额外的开销。如下图所示。
可以推断一下,在一些特定场景,例如streamingRDD需要和历史数据进行join从而获得一些profile信息,此时形成较小的新数据RDD和很大的历史RDD的join。此时loop + index get的性能会高很多。这种小数据和大量历史数据的join模式在物联网/互联网场景下很常见。
此外IndexedRdd也可以作为一个备选方案。但是IndexedRdd无法向Geode这样能够被Spark世界之外访问,只能作为提高spark计算的一种方案.
Apache Geode with Spark的更多相关文章
- apache geode 试用
使用docker 运行,文档参考的官方的5 分钟学习文档 拉取镜像 docker pull apachegeode/geode 启动 docker run -it -p 10334:10334 -p ...
- Apache Hudi集成Spark SQL抢先体验
Apache Hudi集成Spark SQL抢先体验 1. 摘要 社区小伙伴一直期待的Hudi整合Spark SQL的PR正在积极Review中并已经快接近尾声,Hudi集成Spark SQL预计会在 ...
- 一文读懂Apache Geode缓存中间件
目录 一.对缓存中间件的诉求 1.1 我们为什么需要缓存中间件 1.2 缓存的分类 1.1.1 弱势缓存 1.1.2 强势缓存 二.什么是Apache Geode 2.1 Apache Geode的架 ...
- Apache Storm 与 Spark:对实时处理数据,如何选择【翻译】
原文地址 实时商务智能这一构想早已算不得什么新生事物(早在2006年维基百科中就出现了关于这一概念的页面).然而尽管人们多年来一直在对此类方案进行探讨,我却发现很多企业实际上尚未就此规划出明确发展思路 ...
- [翻译]Apache Spark入门简介
原文地址:http://blog.jobbole.com/?p=89446 我是在2013年底第一次听说Spark,当时我对Scala很感兴趣,而Spark就是使用Scala编写的.一段时间之后,我做 ...
- 【转载】Apache Spark Jobs 性能调优(一)
当你开始编写 Apache Spark 代码或者浏览公开的 API 的时候,你会遇到各种各样术语,比如 transformation,action,RDD 等等. 了解到这些是编写 Spark 代码的 ...
- Apache Spark Jobs 性能调优
当你开始编写 Apache Spark 代码或者浏览公开的 API 的时候,你会遇到各种各样术语,比如transformation,action,RDD(resilient distributed d ...
- 基于cdh5.10.x hadoop版本的apache源码编译安装spark
参考文档:http://spark.apache.org/docs/1.6.0/building-spark.html spark安装需要选择源码编译方式进行安装部署,cdh5.10.0提供默认的二进 ...
- How-to: Tune Your Apache Spark Jobs (Part 1)
Learn techniques for tuning your Apache Spark jobs for optimal efficiency. When you write Apache Spa ...
随机推荐
- ubuntu下搭建testlink
环境配置: 1. 安装mysql 教程网上找 2. 安装apache sudo apt-get install apache2 重启apache服务 sudo /etc/init.d/apache2 ...
- Marriage Match II(二分+并查集+最大流,好题)
Marriage Match II http://acm.hdu.edu.cn/showproblem.php?pid=3081 Time Limit: 2000/1000 MS (Java/Othe ...
- DOS 命令集锦——最常用命令
一. 常用命令: cd 改变当前目录 sys 制作DOS系统盘 (电脑入门到精通网 www.58116.cn) copy 拷贝文件 del 删除文件 deltree 删除目录树 dir 列 ...
- Golang之时间、日期类型
孤身只影的一直小地鼠,艰难的走在路上 package main import ( "fmt" "time" ) //获取时间的格式 func testTime( ...
- 深入php内核,从底层c语言剖析php实现原理
深入php内核,从底层c语言剖析php实现原理 非常好的电子书:http://www.cunmou.com/phpbook/preface.md 这是它的目录: PHP的生命周期 让我们从SAPI ...
- JS如何获取PHP循环中的ID
JS如何获取PHP循环中的ID kaalrz 二路公交车 结帖率:83.33% 首先抱歉,因为昨天那帖图片几次都不能用,修改到不能再次修改,今天早上回帖又提示没有这个帖,只好重发一次. 如 ...
- 神奇的照片修复术,这才是 PS 的正确打开方式!
蒲公英种子从远处飘回 聚成伞的模样 太阳从西边升起 落向东方 运动员回到起跑线上 轰鸣的火车退回家乡 雪花纷飞 飘向天际 我沉入梦乡 你还在我身旁 ——公益广告 大概只有时光倒流,我们才能回到那些每天 ...
- php调java接口
1.下载二进制的 JavaBridge.jar包.java/Java.inc库文件,下载地址:http://php-java-bridge.sourceforge.net/pjb/download.p ...
- Asp.Net 用户验证(自定义IPrincipal和IIdentity)
http://www.cnblogs.com/JimmyZhang/archive/2008/12/07/1349457.html
- tomcat6.0 配置不同端口访问不同项目
<?xml version='1.0' encoding='utf-8'?> <!-- Licensed to the Apache Software Foundation (ASF ...