实时业务需要计算新增用户,从 Hive 加载用户表到内存存储,但是几千万数据放到内存占用资源,查询效率也低。使用 Redis 位图进行存储,可以有效减少内存占用,提高查询效率。这里使用 Spark 查询, Redis 位图存储。
这里是 Scala + Spark local模式 + Redis 单节点测试。
测试了几种 Hash 算法,单一 Hash 算法效果或者效率都不是很满意,在万级到几十万级时,出现了多个碰撞。经过几种算法多次Hash,结果仍然不理想,在千万数据时,仍会有大量碰撞。
测试了 Redissom,碰撞率接近的情况下,Redisson 效率会低很多(千万数据/小时级)。
测试了多个质数组合:(61),(7,61),(7,11,13,61),(5,7,11,13,61),(61,83,113,151,211),(61,83,113,151,211,379)...
最后选择 murmurhash,经过 5 次 Hash(37, 41, 61, 79, 83), 使用 256m 存储 4kw 数据, 31 个碰撞,可以接受。
主类
import java.util.Properties
import com.sm.bloom.Bloom
import com.sm.constants.Constants
import com.sm.utils.{DateUtils, RedisUtils}
import org.apache.log4j.{Level, Logger}
import org.apache.spark.sql.SparkSession
import org.slf4j.LoggerFactory
import redis.clients.jedis.Jedis
/**
* Spark查询用户表,导入Redis缓存
*
* create by LiuJinHe 2020/3/3
*/
object ImportDataToRedis {
private val warehouseLocation = Constants.WAREHOUSE
private val logger = LoggerFactory.getLogger(this.getClass)
private var prop: Properties = new Properties
private var jedis: Jedis = _
private var startDate: String = _
private var endDate: String = _
private var count: Long = 0L
def main(args: Array[String]): Unit = {
Logger.getLogger("org.apache.hadoop").setLevel(Level.WARN)
Logger.getLogger("org.apache.spark").setLevel(Level.INFO)
Logger.getLogger("org.spark_project.jetty").setLevel(Level.WARN)
// hash 质数
val seeds = Array(37, 41, 61, 79, 83)
if (args.length == 2) {
startDate = args(0)
endDate = args(1)
}
val spark = initSparkSession
logger.info(s"========== 全量导入新增用户表到 Redis ==========")
val start = System.currentTimeMillis()
// 加载 redis 配置,初始化 jedis
val redisProp = loadProp(Constants.REDIS_PROP)
val host = redisProp.getProperty("redis.host")
val port = redisProp.getProperty("redis.port").toInt
val timeout = redisProp.getProperty("redis.timeout").toInt
jedis = RedisUtils.init(host, port, timeout)
// 用户位图
// 位图存储用户记录,key:用户表名, value:bitmap (offset,即 hash(core_account + game_id + package_id))
val userBit = redisProp.getProperty("redis.user.bit