缓存重定向这个名字,是我的杜撰,指的是一个不带缓存的接口,通过重定向,跳转到一个缓存接口来为客户端提供服务。本质上这个不带缓存的接口,只实现缓存键的计算,从而引导客户端拿到缓存的结果。

一个实际应用

查询离我最近功能需求:下拉列表展示商店,并将这些商店按照离手机当前地理位置的距离从近到远排序。

接口设计

终端节点:GET /locations
参数:query string,包含 经度、纬度

查询的后端实现

简单起见,可以认为最终从数据库查询时的 SQL 大致长这样: sql SELECT * FROM locationtable WHERE coordinates IS NOT NULL and coordinates STCONTAINS(ST_GeomFromText(:wkxString)

其中 wksString 参数使用了 wks 库将传入的经纬度进行地理位置编码。

挑战点

如果每次请求过来都从数据库查询,在请求量超过一定数量时,数据库会扛不住。

分析

由于商店数据更新特别低频,所以对于同样的请求,返回结果在长时间内都是一样的,显然可以做缓存。这样的话,只需要在返回头里指定对于同样的查询参数,给一个比较长的缓存时间就好了。
但是,再进一步优化,如果有两个人相隔很近,手机传过来的请求里的经纬度,只有小数点后几位的差别,那么结果也是一样的,但是由于请求参数不是严格一致,因此不能击中缓存从而查询链条又会走到数据库。如果要将经纬度相近的查询请求,对应到同一个缓存键,那么,可以让客户端再传参时对经纬度数字做一个四舍五入,从而击中同一个缓存键。如果客户端不受控,那么这个四舍五入的逻辑,需要放到服务器端来做。
比如第一期预计只有少量用户,因此以上接口和对应的客户端也已上线。上线后吸引了大量用户,在用户量剧增的情况下,眼看着服务器在短期内就快要扛不住了,需要将现有版本进行迭代升级,就可以采用缓存重定向方案,从而不需要改动客户端。

缓存重定向方案

经过以上分析后,得出的结论是 GET /locations 终端节点,其返回结果不设置缓存。它只负责四舍五入的逻辑,将请求重定向至 GET /locations/cached 终端节点,将同一个位置附近范围的请求,都能对应到同样的查询参数,从而实现一片区域内,只有第一个人搜索时会查询到数据,后续请求的结果都从缓存获取。

架构图大致如下

arch.png

请求响应的时序图如下

注意事项

  • 排序逻辑需要客户端实现。这样的话,在一定的范围内的不同设备,虽然拿到的响应列表是一样的,但是排序可能会有所不同。
  • 在搜索时需要在配置的搜索半径基础上增加一个额外的距离。比如配置的搜索半径为 100 公里,那么实际搜索时需要在 110 公里范围内搜索。因为在对经纬度进行四舍五入时,使得中心点变成了如下图所示的黑色正方形。也就是说,在黑色正方形区域内移动,得到的结果都是一样的,这个黑色正方形的变长是 20 公里的话,就需要以输入的经纬度为圆心,在配置好的搜索半径 100 公里外额外增加 10 公里。这样可以保证当位置移动时不会漏掉某些边缘结果。image.png

总结

当需要对某些查询做缓存,但是又要应对缓存键(URL)变化时,可以通过将原始接口逻辑改造成只计算缓存键,然后重定向到可缓存接口的方式实现。这个可以缓存的接口,往往涉及到数据库操作,无法很好地横向扩展,因此可以通过 CDN 缓存的能力来解决效率问题。而原始接口则不设置缓存,由于它是纯计算,可以横向扩展,因此也没有必要缓存了。