背景介绍
在项目中,SDK会上报包含用户经纬度信息的一系列数据,我们需要根据经纬度信息定位出此条数据上报时用户所在的位置(包括国家、省、市、区),并和其他信息写入宽表中。
旧方案
旧方案中,主要使用GeoSpark对数据进行定位,考虑到同一个经纬度下会有多条数据,所以我们先对数据做分组,同一个用户同一个会话下相同经纬度的数据分为一组,从每组数据中抽取第一条生成一张临时表,再在临时表上调用GeoSpark算出district_id,city_id,province_id,country_id,之后将临时表与原表关联,用临时表的四个ID填充相同经纬度的其他数据的ID。
在测试过程中,我们发现有很小一部分数据只有district_id或city_id等细粒度数据,却没有与之相对的province_id、country_id等数据,这是老大所不能接受的(在哪个城市都知道了,国家你给我个null??? =。=),所以在计算四个ID之后,会有一个反推的步骤,即:判断是否有下层ID不为空上层ID却为空的情况,如果有,通过下层ID进行反推,得到上层ID,并填充。
中间还有一些其他的过滤排序逻辑不做具体介绍,最后当整个定位逻辑完成后,需要做6~7次的shuffle,我们发现其性能远低于预期,在我们每天将近40亿的数据量下,较大的拖慢了整个流程的运行速度,影响了数据产出,因此需要对这一部分进行优化。经过调研后,本菜鸡决定采用GeoHash的方式进行优化。
什么是Geohash
简单介绍下GeoHash,我们可以用一个经纬度的点(例如点A: 37.788422,-122.391907 )计算出一个GeoHash字符串(9q8yyzh),这个字符串代表一个矩形面,点A以及点A附近的点B(37.787933,-122.392887)虽然经纬度不同,但通过经纬度计算出的GeoHash字符串相同,也就是说AB两个点都在(9q8yyzh)这个面内。这样就将二维的经纬度坐标转换成了一维的字符串表示。
但A附近的多少点会跟A共享相同的字符串呢?也就是这个面的大小是怎么确定的呢?这就取决于GeoHash字符串的长度了,GeoHash的字符串长度越长,意味着这个面也就越小,会有更少的点跟A共享同样的GeoHash值。