基本介绍
背景介绍
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埋点插件来上报错误信息,方便后续分析和优化。
(本文作者:任赛龙)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。