一、概述
因为实时计算的特殊性,需要针对多维度的实时显示累计和分时的UV、PV数。目前有22+N个维度(后面简称dimName),N是用户会加的维度,每个dimName都会有维度值(后面简称dimValue),每个dimName都有不同数量的dimValue,在用户选择n(目前n<=5)个dimName时要输出这n个dimName对应的dimValue的组合数,也就是n个中取5个dimName,然后这些dimName下所有dimValue不重复的组合结果。但是每个dimValue有可能有m个dimName(m>0)。
因为数据量太大,最好将乘法转换成加法,这样所有dimName的dimValue总和也不会很大了,按照正常的思考是拿着dimName去group by方式查,但是需要把所有数据都保存下来,数据量巨大就会产生资源和请求性能的问题。
我的方案是使用倒排索引做加法,所有dimValue都是索引的字段,uv就是uid为唯一id(唯一id就是MySQL的自增主键),pv就是一个跟之前所有数据id不同的唯一id,这样就可以检索需要的字段了,检索内容就是dimValue。
二、解决方案
针对多维度实时计算的需求用倒排索引来计算结果效率应该是最高的,倒排索引的简单数据结构是
DimValue |
Uid |
|||||
中国 |
1 |
2 |
3 |
4 |
5 |
|
City:北京 |
3 |
4 |
7 |
|
|
|
City:上海 |
2 |
8 |
9 |
2 |
3 |
|
OS:IOS |
3 |
7 |
8 |
|
|
|
OS:Android |
8 |
9 |
3 |
2 |
|
|
这样在计算n个dimName的dimValue时(例子:city和OS是dimName,后面的北京,IOS是dimValue)只需要搜索{[北京,IOS],[上海,IOS],[北京,Android],[上海,Android]}这几个关键字就可以了。
使用elasticsearch作为数据库存储,使用es的custom analysis建立特殊的分词器,分词规则是把字符串按照特殊字符(例如:&)分割成多个子串来建立索引
建Mapping是在代码里添加,因为需要每天创建新的index,并根据过期时间删除index
2、UV处理
需求是需要每个小时的和今天当当前小时的uv,pv。
建索引需要维护两种类型index,一个是当前小时的,一个是今天到当前小时的,每天创建48个index,mapping都是一样的,过期的index按照时间直接删除:
建立索引逻辑:
- 创建24个index保存当前小时的数据,创建24个index保存累计数据。
- 使用uid作为唯一id保存,先用uid查出数据,需要查一个分时和一个累计,累计只需要查当前小时的index就可以,因为当前小时就包含了前面所有的数据了。
- 数据合并
1)将所有字段的dimValue合并,如果没有查出数据就不做合并,合并方式是用特殊字符(&)作为分隔符将字符串连接
2)将每个字段的dimValue保存到redis中的set集合中,为了后面查询用。
4.把合并后的数据重新insert覆盖原来的,分时只需要insert当前小时,累计需要insert时间(T>=当前小时)所有的index,因为后面时间都要包含前面的uv。
检索逻辑:
- 先用dimName从redis取对应的dimValue,并生成所有组合
- 分时(累计)从每个小时的index中用每个组合中的值检索数据。
- 返回的结果中就有每个组合的小时UV
3、PV处理
Pv的index跟uv一样也是需要48个index分别保存分时和累计,但是pv的index中不需要保存source内容,只需要能查出totalNum就可以了,insert数据时使用自动生成的唯一id,查询时按照dimValue的组合查询,每个dimValue就是一个关键词。
- 创建index同uv
- 保存dimValue到redis中,从redis中查询首访,添加首访字段(这个是跟uv复用的功能)
- insert到对应的index中
检索逻辑:
- 生成组合同uv
- 检索数据同uv
- 返回结果的totalNum就是pv。
4、数据优化
- 数据的存储优化主要是dimValue,因为dimValue内容可能有很多汉子和长的字符串,比如城市,省等,这些固定字符串可以使用一个映射表,把值换成数字可以压缩存储空间,比如用1代表北京,2代表上海等。
- 建索引优化可以es的bluk方式,虽然可能会有一点延迟,但是这个延迟是可以控制的
- 检索优化可以加缓存,因为维度确定后数据返回结果变化就不大了,只有当前小时的才会发生变化。
方案还有需要完善的地方,这只是一个想法还没有应用到实践中,在真正的uv,pv计算中大部分会用MySQL或者hive这种关系型数据库来实现,但是面对大数据量并且实时性要求很高的时候这两个就没法满足要求了。
但是还有一个结果存在误差的解决方案,就是使用hyperloglog放在redis里计算,这个算法还是很高效的应该可以满足亿级的数据量了,但是误差会有4%-0.1%。