导读:
利用spark sql函数,将多行数据转化为一行,并且将group by后的某些字段拼接成为json字符串。
最终将数据存入redis中。
假设hive表原始数据如下:
day area playNum
20200808 "南京" 888
20200808 "苏州" 999
20200809 "常州" 999
读取原始数据,声明临时表,编写sql,按day字段,将多行转为一行。
val ss: SparkSession = SparkSession.builder().config(new SparkConf()).enableHiveSupport().getOrCreate()
ss.read.load("/Users/xxxx/tmp/areaTable").createTempView("tmp")
val df=ss.sql("select concat('areaDistribution_',day) as key,cast(collect_list(concat('{\"area\"',':\"',area ,'\",','\"playNum\"',':',playNum,'}')) as string) as value from tmp group by day")
sql代码中,通过"group by day",将同一日的数据进行汇总,然后使用concat函数做json字符的拼接操作,使用collect_list函数将多个子json字符串合并成为字符串json的数组,最后通过cast函数将数组转化为string,满足最终要求。
df的数据类型如下:
root
|-- key: string (nullable = true)
|-- value: string (nullable = true)
df的内容如下:
+-------------------------+--------------------------------------------------------------+
|key |value |
+-------------------------+--------------------------------------------------------------+
|areaDistribution_20200808|[{"area":"南京","playNum":888}, {"area":"苏州","playNum":999}]|
|areaDistribution_20200809|[{"area":"南京","playNum":999}] |
+-------------------------+--------------------------------------------------------------+
此时,value字段是一个json格式的字符串。
通过在原始dataframe的每一个partition中声明redis的连接,注意不是每条记录声明一个连接。
//默认200个partition,按现在程序的数据量太多,没有必要初始化200个redis连接,故而调整下
val partitionNum=if(df.rdd.getNumPartitions>6) 6 else df.rdd.getNumPartitions
df.repartition(partitionNum).foreachPartition(p=>{
var redis:Jedis=null
try {
redis = getRedis()
var count=0
val arrayBuffer : ArrayBuffer[String] = new ArrayBuffer[String]()
p.foreach(e => {
count=count+1
val key= e.getAs[String]("key")
val value= e.getAs[String]("value")
//println("value:"+value)
arrayBuffer.+=:(value.toString)
arrayBuffer.+=:(key)
if(count%1000==0){
redis.mset(arrayBuffer: _*)
arrayBuffer.clear()
}
})
//清理末尾
if(arrayBuffer.size>0){
//println(arrayBuffer.toString())
redis.mset(arrayBuffer: _*)
}
}catch {
case e:Exception=>println(e.toString)
}finally {
redis.close()
}
})
一个简单函数,用于返回redis连接给dataframe的每一个partition调用:
//获得redis资源
def getRedis(): Jedis = {
val host = "10.10.01.01"
val port = "6379".toInt
val db = "4".toInt
println("--------redisInfo:",host,port,db)
val config: JedisPoolConfig = new JedisPoolConfig()
val metadataPool: JedisPool = new JedisPool(config, host, port, 100 * Protocol.DEFAULT_TIMEOUT, null, db)
val redis = metadataPool.getResource
redis
}