Redis介绍
redis是一个K-V存储系统,它是内存数据库,主要是用于做数据的缓存,同时在大数据领域上,主要是做数据结果的存储,因为它是一个内存数据数据库,读写效率高,每秒吞吐量达到11W左右,支持多种数据类型(String,list,set等等),C语言开发的K-V数据库
应用场景:Java居多,用于缓存页面,大数据领域,主要是在做实时流处理统计的时候,使用Redis进行数据的存储
Nosql数据库:MongoDB、Hbase、Redis
Nosql优势:非关系数据库,K-V存储,查询效率高,数据存储容量大(Redis除外),可以按照数据的K-V结构处理当前存储的数据
注意:3.0以下版本不支持集群模式,3.0以上支持集群模式
安装Redis
Redis数据类型
String类型 字符串
Hash类型 散列
List类型 列表
Set类型 集合
Zset类型 有序集合
Redis-API连接
添加redis的java依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
资源设置和使用
序号 | 参数名 | 含义 | 默认值 | 使用建议 |
---|---|---|---|---|
1 | maxTotal | 资源池中最大连接数 | 8 | 设置建议见下节 |
2 | maxIdle | 资源池允许最大空闲的连接数 | 8 | 设置建议见下节 |
3 | minIdle | 资源池确保最少空闲的连接数 | 0 | 设置建议见下节 |
4 | blockWhenExhausted | 当资源池用尽后,调用者是否要等待。只有当为true时,下面的maxWaitMillis才会生效 | true | 建议使用默认值 |
5 | maxWaitMillis | 当资源池连接用尽后,调用者的最大等待时间(单位为毫秒) | -1:表示永不超时 | 不建议使用默认值 |
6 | testOnBorrow | 向资源池借用连接时是否做连接有效性检测(ping),无效连接会被移除 | false | 业务量很大时候建议设置为false(多一次ping的开销)。 |
7 | testOnReturn | 向资源池归还连接时是否做连接有效性检测(ping),无效连接会被移除 | false | 业务量很大时候建议设置为false(多一次ping的开销)。 |
8 | jmxEnabled | 是否开启jmx监控,可用于监控 | true | 建议开启,但应用本身也要开启 |
2.空闲资源监测
空闲Jedis对象检测,下面四个参数组合来完成,testWhileIdle是该功能的开关。
序号 | 参数名 | 含义 | 默认值 | 使用建议 |
---|---|---|---|---|
1 | testWhileIdle | 是否开启空闲资源监测 | false | true |
2 | timeBetweenEvictionRunsMillis | 空闲资源的检测周期(单位为毫秒) | -1:不检测 | 建议设置,周期自行选择,也可以默认也可以使用下面JedisPoolConfig中的配置 |
3 | minEvictableIdleTimeMillis | 资源池中资源最小空闲时间(单位为毫秒),达到此值后空闲资源将被移除 | 1000 60 30 = 30分钟 | 可根据自身业务决定,大部分默认值即可,也可以考虑使用下面JeidsPoolConfig中的配置 |
4 | numTestsPerEvictionRun | 做空闲资源检测时,每次的采样数 | 3 | 可根据自身应用连接数进行微调,如果设置为-1,就是对所有连接做空闲监测 |
Redis 持久化
RDB(Redis DataBase): 优点在于效率很高,不会频繁的刷入数据到磁盘,到达一定量的时候刷入磁盘,减少IO操作,缺点是数据安全没保障,如果某一时刻数据库挂掉了,那么没有达到持久化要求,此时的数据不会被刷入磁盘存储,所以下次启动后,无法回复未持久化的数据
AOF(Append Only File): 优点是数据安全,缺点效率低,并且频繁执行存储,IO过大,而且内部存储的是命令,不是数据,如果进行恢复,那么需要执行命令恢复数据。
总结:RDB适用于对数据安全性低的操作,而AOF恰恰相反,适用于数据安全要求性高的。
Redis集群
Redis集群,首先保证最少3个主节点,三个从节点,保证某一节点宕机半数以上节点存活,同时,每个槽位都是固定的,如果当一个节点宕机,那么从节点之间顶替主节点工作,当主节点没问题的时候,从节点负责同步数据即可
案例解析
注意redis的序列化问题
package com.qf.SparkStreaming
import org.apache.commons.pool2.impl.GenericObjectPoolConfig
import org.apache.kafka.clients.consumer.ConsumerConfig
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.streaming.{Seconds,StreamingContext}
import org.apache.spark.streaming.kafka010.{ConsumerStrategies, KafkaUtils, LocationStrategies}
import org.apache.spark.{HashPartitioner, SparkConf, SparkContext}
import redis.clients.jedis.{ JedisPool, JedisPoolConfig}
/**
* @Author potpof
* @Date 2019/11/20 16:07
* @Version 1.0
*/
class HomeWork1120 {
}
// 序列化创建redis连接
object RedisClient extends Serializable {
val redisHost = "10.36.151.79"
val redisPort = 6379
val redisTimeout = 30000
lazy val pool = new JedisPool(new GenericObjectPoolConfig(), redisHost, redisPort, redisTimeout)
lazy val hook: Thread = new Thread {
override def run(): Unit = {
println("Execute hook thread: " + this)
pool.destroy()
}
}
sys.addShutdownHook(hook.run())
}
/**
* 从kafka读数据
*/
object ScalaKafkaStreaming1 {
def main(args: Array[String]): Unit = {
// 累加功能
val func = (it: Iterator[(String, Seq[Int], Option[Int])]) => {
it.map(x => {
(x._1, x._2.sum + x._3.getOrElse(0))
})
}
// 自定义方法将ip转换成数字
def ip2Long(ip: String): Long = {
val fragments = ip.split("[.]")
var ipNum = 0L
for (i <- 0 until fragments.length) {
ipNum = fragments(i).toLong | ipNum << 8L
}
ipNum
}
//二分查找,找出ip在哪个范围,不存在则返回null
def binarySearch(arr: Array[(String, String, String)], ip: Long): String = {
var start = 0
var end = arr.length - 1
while (start <= end) {
val middle = (start + end) / 2
if ((ip >= arr(middle)._1.toLong) && (ip <= arr(middle)._2.toLong)) {
return arr(middle)._3
}
if (ip < arr(middle)._1.toLong) {
end = middle - 1
} else {
start = middle + 1
}
}
"NULL"
}
val checkpointPath = "/ssc/1120_1"
val conf = new SparkConf()
.setAppName("ScalaKafkaStream")
.setMaster("local[2]")
.set("redis.host", "10.36.151.79")
val sc = new SparkContext(conf)
sc.setLogLevel("WARN")
val ssc = new StreamingContext(sc, Seconds(3))
ssc.checkpoint(checkpointPath)
/**
* 配置kafka连接
*/
val bootstrapServers = "server:9092,client1:9092,client2:9092"
val groupId = "sz1901"
val topicName = "shop-topic"
val maxPoll = 500
val offset = "earliest"
val tr = "true"
val kafkaParams = Map(
ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG -> bootstrapServers,
ConsumerConfig.GROUP_ID_CONFIG -> groupId,
ConsumerConfig.MAX_POLL_RECORDS_CONFIG -> maxPoll.toString,
ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG -> classOf[StringDeserializer],
ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG -> classOf[StringDeserializer],
ConsumerConfig.AUTO_OFFSET_RESET_CONFIG -> offset,
ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG -> tr
)
val kafkaTopicDS = KafkaUtils.createDirectStream(ssc, LocationStrategies.PreferConsistent,
ConsumerStrategies.Subscribe[String, String](Set(topicName), kafkaParams))
kafkaTopicDS.map(_.value()).map(_.toString.split(" ")).print()
//问题1.计算出总的成交量总额(结果保存到redis中)
val input1: DStream[(String, Int)] = kafkaTopicDS.map(_.value().split(" ")(4).toInt).map(("sum", _))
val sum: DStream[(String, Int)] = input1.updateStateByKey(func, new HashPartitioner(ssc.sparkContext.defaultParallelism), rememberPartitioner = true)
sum.print()
//保存到redis,redis一定要继承序列化接口
//错误示例
// input1.foreachRDD(rdd => {
// val jedis = jedisPool.getResource
// rdd.foreachPartition(tp => {
// tp.foreach(tp1 => {
// jedis.hincrBy("sum1120", tp1._1, tp1._2)
// })
// jedis.close()
// })
// })
input1.foreachRDD(rdd => {
rdd.foreachPartition(partitionOfRecords => {
partitionOfRecords.foreach(pair => {
val sum = pair._1
val data = pair._2
val jedis = RedisClient.pool.getResource
jedis.hincrBy("sum1120", sum, data)
jedis.close()
})
})
})
//问题2.计算每个商品分类的成交量(结果保存到redis中)
val input2: DStream[(String, Int)] = kafkaTopicDS.map(_.value().split(" ")(2)).map((_, 1))
val cate: DStream[(String, Int)] = input2.updateStateByKey(func, new HashPartitioner(ssc.sparkContext.defaultParallelism), rememberPartitioner = true)
cate.print()
input2.foreachRDD(rdd => {
rdd.foreachPartition(partitionOfRecords => {
partitionOfRecords.foreach(pair => {
val cate = pair._1
val data = pair._2
val jedis = RedisClient.pool.getResource
jedis.hincrBy("cate1120", cate, data)
jedis.close()
})
})
})
// 问题3.计算每个省份的成交总额(结果保存到redis)
val ipDict = sc.textFile("/ip.txt")
val dict = ipDict.map(x => {
val strings = x.split("\\|")
(strings(2), strings(3), strings(6))
}).collect()
val area: DStream[(String, Int)] = kafkaTopicDS.map(_.value().split(" ")(1)).map(x => {
val longIp = ip2Long(x)
val arrInfo: Array[(String, String, String)] = dict
val i: String = binarySearch(arrInfo, longIp)
(i, 1)
})
val area1: DStream[(String, Int)] = area.updateStateByKey(func, new HashPartitioner(ssc.sparkContext.defaultParallelism), rememberPartitioner = true)
area1.print()
area.foreachRDD(rdd => {
rdd.foreachPartition(partitionOfRecords => {
partitionOfRecords.foreach(pair => {
val area = pair._1
val data = pair._2
val jedis = RedisClient.pool.getResource
jedis.hincrBy("area1120", area, data)
jedis.close()
})
})
})
ssc.start()
ssc.awaitTermination()
}
}
//验证
object test1120 extends App {
// 设置连接池的配置
val config = new JedisPoolConfig
config.setMaxTotal(20)
config.setMaxIdle(10)
config.setMaxWaitMillis(10)
val pool = new JedisPool(config, "10.36.151.79", 6379)
val jedis = pool.getResource
println(jedis.hgetAll("sum1120"))
println(jedis.hgetAll("cate1120"))
println(jedis.hgetAll("area1120"))
}