用户画像系统中遇到的比较难的问题是什么?问题一: 我们在选择如何存储用户标签时,遇到了问题(标签查询速度慢,并且构建不够灵活,标签更新和删除比较麻烦),比如之前用HDFS或者ES存储,后来切换为ClikcHouse,并用BitMap存储。
原因如下:
针对标签的表示形式,存储方式有很多,结构为`宽表,BitMap` 都可以,存储选择`HDFS,ES,ClickHouse 等` 也都可以,需要衡量的有两点`1.标签构建的灵活性和构建速度 2.标签的查询效率 `
`HDFS [Presot,Impala]:` 标签的增加,删除,更新不友好, 一个小变动,要重写整个`Parquet`, 写放大问题。 查询效率还可以,但是不够优秀。 支持查询并发较小。
`ES:`标签的构建的写入速度一般, 新增和修改标签需要对ES文档结构更新,ES的DSL语法不友好,有一定学习成本。查询效率还算优秀,同时支持高并发。 ES资源占用高,需要较好的硬件配置。
`ClickHouse[BitMap]` 标签可以并行构建,查询效率优秀,标签的增加非常方便,标签的更新和删除可以实现,但是并不高效,并发查询支持比Presto,Impala要好,但同样不支持高并发,能够满足大部分场景需求。注意两点`1. BitMap存储的是用户ID 2. BitMap使用了RoaringBitMap, 解决BitMap空间占用问题,不然1亿这一个数也要占用11.9M空间`
用户画像系统中遇到的比较难的问题是什么?问题二:如何构建用户的稠密向量的问题
如果我们直接将用户的标签转换为稀疏向量来存储,对于类别标签使用`one-hot`编码,但这样会出现维度爆炸的问题,向量过于稀疏,向量之间的余弦相似度计算结果基本没有意义,根本无法实现用户相似度的计算。所以就开始思考如何将用户表示为转换为稠密向量,经过调研发现,Word2Vec可以将词转换为稠密向量,同时借助Word2Vec思想,也可以将物品转换为向量Item2Vec,比如将一个Session内,用户购买的物品或者点击的物品列表,看成是一句话,每个物品看成是一个单词,就可以借助Word2Vec的思想将物品转换为稠密向量表示。(这里注意如果是文章,可以使用分词,然后抽取关键词,将词通过Word2Vec转换为向量的方式) ,我们再将用户点击或者购买的物品列表中物品向量加和求平均,就可以得到用户的稠密向量。后来发现通过ALS模型`矩阵分解`的方式也可以得到用户的稠密向量,两者`表达的用户向量含义`是不同的,一个是有浓重的物品属性特征的,一个是有协同特征的向量。但是都可以作为用户的向量表示方式。
HBase得二级索引的设计(或者Phoenix 二级索引-说说原理)
HBase二级索引的设计方案一般有如下几种
1. 协处理器coprocessor方案。 原理就是自定义协处理器,实现`双写`,就是写主表的时候,同时写索引表[这里这个索引表是根据业务对查询的需求建立的]。 比如我们要查询的主表是A, 里面有RowKey,还有一列ColumnA. 如果想对ColumnA这一列建立索引,就自定义一个协处理器(观察者模式),当我们写入A表中一条数据,比如 行键rowkey(123),cloumnA列值:abc,这时协处理在索引表(自己建立,比如A_INDEX)中插入一条记录 行键为刚才列A的值abc,列值为主表的rowkey(123). 查询的时候,先查索引表得到rowkey,然后根据rowkey在主表中查。
2. ES 方案,将想要构建的二级索引的字段值存储到ES中,查询时先去ES根据条件查到rowkey,然后根据rowkey再去hbase查数据。
3. Phoenix 方案。 Phoenix构建构建索引的方式,本质也在HBase中建立索引表。只不建表的过程,索引维护的过程,Phoenix自己内部实现,暴露给用户的只是SQL接口。
# 其实在HBase构建二级索引,万变不离其宗,最终的方向都是构建索引字段与行键的映射关系,先更加索引表查行键,在根据行键,查最终数据。
怎么提高Flink的执行性能(代码方面)
• 通用的优化方式
1. 尽早fliter掉一些不需要的数据以及避免一些不必要的序列化。
2. 避免使用深层嵌套数据类型。
3. 对于数据倾斜使用调整并行度或者双层聚合的方式。
4. 一些基数较少的并且本身较长维度可以采用数据字典的方式减少网络传输及内存占用、gc开销。
• 数据类型和序列化
Flink支持java、scala基本数据类型,以及java Tuples、scala Case Class、Flink Value,对于这些数据类型,flink会采用自身的序列化反序列化器去做序列化操作,对于其他数据类型,flink会采用kyro方式序列化,kyro序列化方式效率会比flink自带的方式低很多。因此在数据序列化方面我们可以做如下工作
1. 尝试使用transient修饰不需要序列化的变量,或者修饰你可以在下游通过其他方式获取到变量,这个可以减少序列化流程和网络传输(但可能带来更多的内存占用用和gc消耗)
2. 对于一些特殊的数据你可以尝试重写writeObject() 和 readObject() 来自己控制一些序列化方式,如果更高效的话
3. 如果使用了lambda或者泛型的话,显式的指定类型信息让flink类型提取系统识别到以提升性能。
• 多组相同keyby可使用DataStreamUtils
在多组keyby的场景可以采用DataStreamUtils.reinterpretAsKeyedStream的方式避免多次shuffle操作
• 尽量减少状态的大小
1. 设置合适的state TTL, 清洗过期状态,避免状态无限增大。
2. 减少状态字段数, 比如使用aggreteFunction 做窗口聚合时,可以只将要聚合的信息放入状态,其他keyBy字段以及窗口信息,可以通过processWindowFunction的方式获取,这样就是 aggregateFunction + ProcessWindowFunction,agg函数获取聚合信息,输出的结果到processwindowFunction中取获取窗口信息。
3. checkpoint频率不宜过高,超时时间不要太长,可以异步化的地方尽量异步化
更多关于“大数据面试题”的问题,欢迎咨询千锋教育在线名师。千锋已有十余年的培训经验,课程大纲更科学更专业,有针对零基础的就业班,有针对想提升技术的好程序员班,高品质课程助理你实现java程序员梦想。