使用本地缓存优化微服务

cache in microservice

Posted by ypingcn on January 30, 2022 Lastest updated on August 9, 2022

一 背景

微服务相较于整体式架构而言具有易拓展、敏捷开发、在各个小型独立团队之间职责明确的特点。相关接口在日常维护过程中,根据业务发展情况进行优化是一项必要且成效明显的工作。

由于业务逻辑中会经常进行信息查询(例如查询用户信息/获取活动配置等),这部分信息的变动并不是很频繁,如果每次都需要走 RPC 调用查询的话,就会多额外的网络请求和资源消耗。特别是在有突发流量的情况下,上游接口调用的暴增会向下传递给下游服务,用户信息等基础信息的查询量会因此而暴增。如果用户信息服务出现瓶颈,上游服务都会不同程度地受到影响,从而降低用户体验甚至导致事故。使用网络调用代替本地调用在查询这种相对稳定不变化的结果无疑是一种资源的浪费。

优化可以从这种额外的网络请求和资源消耗做起,使用本地内存缓存取代远程查询进行优化,这种缓存对服务的优化效果是十分显著的。

二 优化方式

2.1 在用户接入侧进行缓存

在实际业务中,有部分请求都是在获取配置等公用信息。这种对一致性容忍度比较高的接口结果可以在用户接入点侧进行缓存,起到类似于动态 CDN 的效果。缓存需要一个唯一 key 来对不同用户进行区分从而进行缓存,这种唯一 key 可以从多个接口参数值获取,根据一定算法得出。例如地区维度的接口可以将地区代码设置成唯一 key ,相同地区的请求获取相同的配置。这种做法能够得到 20%-80% 的缓存命中率,优化削峰填谷效果明显。

有一个额外的问题是,从接口参数获取唯一 key 的做法无法兼容到需要服务端 ABTest 的场景。 这种情况需要将缓存逻辑继续后置给业务服务处理,而不是在用户接入侧缓存。

2.2 缓存其他微服务接口的结果

可以在内存中缓存相关的接口结果。例如用户信息这种极少变化的接口结果就可以用 LRU 内存缓存下来,如果业务场景可以接受的话,缓存有效期时间可以设置 5 分钟甚至更长时间。而排行榜这种对有效性要求比较高的场景下,设置秒级的有效期更能符合业务需求。

还可以在这种缓存的逻辑下增加降级熔断逻辑,避免业务异常流量继续向下游传递。

三 实现细节

3.1 缓存雪崩、击穿的问题

本地内存缓存与下游微服务结果之间,跟数据库(如 MySQL )与分布式缓存工具(如 Redis )搭配使用一样,都会有缓存过期带来的雪崩、击穿问题。

雪崩问题可以在实现细节里增加一个额外的随机过期时间,将数据过期的时间尽可能地错开避免集中向下游服务请求导致雪崩。

而击穿问题则是因为热点数据的过期导致的,这种可以使用singleFlight合并查询等方法,将并发而来的多个同类型的请求合并成一个,只向下游发送一次请求来复用结果。

3.2 一致性问题

可以使用内存进行缓存的时候,只能是对一致性要求不高、数据变动少的场景。因为微服务架构下,多节点之间的缓存同步与刷新是一种事倍功半的工作,只能追求最终一致性。

应用场景简化了一致性的要求,所以只需要做到 淘汰缓存 + 先查询再异步更新缓存 的方法。