基本介绍

背景介绍

1.如何解决业务方在小程序、H5应用上的接入地图服务的困扰?
我们统一收口哈啰&高德服务能力,业务侧不需要每次接入LBS和高德服务都做http服务的接入工作,只用接入地图提供的组件包。我们还提供在类型声明,线接入文档,简化业务方的接入成本和工作量。

2.总是担心服务不稳定,如何缓解心智负担?
我们在前端侧优化了请求策略,加入了降级兜底能力,稳定性更有保障。

3.面对调用量增长,减轻服务压力,将如何面对?
我们从缓存方面入手,为调用量特别突出的逆地理设计缓存方案,通过缓存机制来减少调用,减轻服务压力。

SDK介绍

基于哈啰&高德地图服务的封装库

图片

能力支持:

  • 核心是哈啰和高德地图服务
  • 统一的出入参数据结构,数据差异抹平
  • 高德服务兜底策略
  • 逆地理编码缓存策略、请求缓存、结果缓存
  • 完整的Typescript参类型声明
  • 支持多环境下安装使用

Map Services

我们现在有这么多地图服务能力,需要将其整合到一起。

图片

如何快速使用

  • 我们的SDK项目有完善的TypeScript类型声明,可以让开发者在编写代码时享受到类型检查和自动补全的便利。
  • 我们使用TypeDoc将类型声明生成对应的Markdown文档文件,再使用VuePress生成静态站点,方便开发者快速接入和使用。
  • 未来,我们还打算添加在线文档接口mock的能力,可以编辑入参,发送请求,显示响应数据,让开发使用更便利。

图片

请求策略是怎么样的

为了保证地图组件的稳定性,地图组件对高德和LBS的请求策略进行了优化,在请求失败或超时的情况下,用高德服务做一次补偿(降级兜底),尝试再次获取数据。

图片

如何解决数据结构差异的困扰

我们需要完成上图 -> 下图的数据转换,从高德数据转换为哈啰数据格式。

图片

图片

为了保证地图组件的通用性和可扩展性,地图组件对哈罗LBS和高德的出入参数据结构进行了统一,统一以哈啰数据结构为基准。

这样可以提高地图组件的兼容性和灵活性,同时也可以方便开发者使用。

我们定制开发了专门用来处理数据转换的工具库 @hb/map-convert。

  • 数据字段的转换
  • 将数据转换为目标数据类型
  • 对于一些数据类型完全不一样的内容,可自定义转换函数,保障数据转换工具的兼容性和灵活性。比如:高德返回值数据中比较典型的空数组问题处理,city:[] -> city: StringType() -> city: "",类似这种字段数据我们会进行类型统一
  • 支持多层级的数据处理

图片

前端缓存优化

逆地理编码缓存如何实现

  • 选择一个合适的算法,用于生成缓存的CacheKey
  • 选择一个合适的缓存容器,用于存储逆地理编码响应数据
  • 选择合适的淘汰机制,用于数据的更新
  • 根据CacheKey命中缓存

图片

如何提升缓存命中效率

通过GeoHash算法,进行一定误差范围内的经纬度匹配。

  • GeoHash是一种地址编码方法,他能够把二维的空间经纬度数据编码成一个字符串
  • 算法思想:GeoHash表示的并不是一个点,而是一个矩形区域,编码越长,表示的范围越小,位置也越精确,GeoHash编码的前缀可以表示更大的区域。例如wx4g0ec1,它的前缀wx4g0e表示包含编码wx4g0ec1在内的更大范围

图片

GeoHash 算法原理

经纬度划分

经度范围是东经180到西经180,纬度范围是南纬90到北纬90,我们设定西经为负,南纬为负,所以地球上的经度范围就是[-180, 180],纬度范围就是[-90,90]。

如果纬度范围用二进制代表:

  • [-90°, 0°) => 0
  • (0°, 90°] => 1
  • [-180°, 0°) => 0
  • (0°, 180°] => 1

图片

编码长度

编码长度就是对方块的划分次数。

图片

  • 根据设定的编码长度对当前经纬度分别进行划分,得到两组二进制串(10101、01010)后以偶数位放经度,奇数位放纬度的方式合并成一个二进制串(1001100110)
  • 将二进制串划分每5位一组,不足5位补0(10011、00110)
  • 将各组的5位二进制串转成十进制,5bits对应着10进制的数值为0-31(19、6)
  • 用0-9、b-z(去掉a、i、l、o)这32个字母进行Base32编码,即对照下标将其转换为字符串(m、6)

编码长度 - 精度范围

在逆地理缓存中编码长度为GeoHash9(5m左右误差)。

图片

缓存淘汰机制

图片

我们采用LRUCache缓存策略,它可以根据访问频率和时间自动淘汰最不常用的数据,保证缓存的空间利用率和数据的新鲜度。

原理解析:新数据插入到链表头部;每当缓存命中(即缓存数据被访问),则将数据移到链表头部;当链表满的时候,将链表尾部的数据丢弃。

其他维度的数据更新机制:

  • 时间维度:我们限制只使用2天内的数据,如超过则淘汰数据,重新请求并缓存
  • 访问次数维度:我们限制数据使用10次后,会主动淘汰数据,重新请求并缓存

请求缓存

计算CacheKey,缓存请求Promise,下一次请求调用发起时,先尝试命中缓存,若命中则返回上一次调用缓存下来的Promise。

更新机制:缓存只在一次请求生命周期内有效,请求成功或失败后缓存都将删除。

解决问题:同一接口重复调用的场景下,解决并发调用的问题,只要上一次响应没有返回,下一次就不会重复发起相同请求。比如:进入“打车”首页时多次调用某个接口的场景下;重复点击按钮执行某个操作的场景。

图片

响应结果缓存

计算CacheKey,请求成功后,缓存响应结果,下一次请求调用发起时,先尝试命中缓存,若命中则返回缓存中的数据。

更新机制:使用LRUCache作为缓存容器,存储超过10条数据后陈旧数据将会被淘汰;缓存超过5分钟后失效;如果在小程序中,缓存随着小程序生命周期的结束而销毁。

图片

插件化设计

插件化的优点

图片

  • 提高了代码的可复用性和可维护性,因为每个插件都是独立的模块
  • 降低了代码的耦合度和复杂度,每个插件都只关注自己的功能,不需要知道其他插件的细节
  • 我们将一个网络请求的生命周期分为四个阶段:请求开始前、请求调用时、请求成功、请求失败。在每个阶段,我们可以使用不同的插件来做不同的事情

缓存插件

图片

在请求开始前,在onBefore生命周期中,我们可以使用缓存插件来检查是否有缓存数据,如果有则直接返回缓存数据,不需要发起网络请求。

在请求成功时,我们可以使用缓存插件来保存返回的数据到缓存中,方便下次使用。

埋点插件

图片

在onSuccess生命周期中,使用ubt埋点插件来记录用户请求成功状态。

在onError生命周期中,使用ubt埋点插件来上报错误信息,方便后续分析和优化。

(本文作者:任赛龙)

图片


哈啰技术
89 声望51 粉丝

哈啰官方技术号,不定期分享哈啰的相关技术产出。