对外服缓存
本篇内容是对外服相关的扩展。
本篇文档不是必读内容,但介绍了一种性能提升的技巧,建议在时间充裕时阅读。
介绍
我们对业务数据做缓存时,一般的做法是通过 Caffeine、 cache2k、 ehcache、 JetCache 等专业的缓存库,将业务数据缓存在逻辑服中,以实现对业务数据的缓存。
而对外服缓存,可以将一些热点的业务数据缓存在对外服中。 当用户每次访问相关路由时,会直接从对外服内存中取数据。 这样既能避免反复请求逻辑服,又能减少序列化(编码)业务数据,从而做到性能的超级提升。
当我们把对外服缓存与专业的缓存库做结合时,可以发挥更大的性能效果。 因为我们可以将热点数据缓存在对外服中,之后其他用户访问热点数据时, 就不需要去逻辑服中取数据,而是直接在对外服这一环节中就能得到数据了。
对外服缓存对性能有着巨大的提升,主要体现在几个方面
- 当用户访问缓存数据时,响应更快了,因为请求链更少了。
- 直接在对外服中取数据,无需将请求传递到逻辑服中,无需对业务数据做序列化操作。
- 避免请求传递到逻辑服中,节省系统资源。
特点
- 零学习成本
- 可快速响应用户请求。
- 简化了缓存的使用,即使没有在逻辑服中对这些业务数据做缓存, 只要在对外服配置好相关的路由缓存,就能达到缓存的效果。
- 减少请求传递,同时对外服缓存还可以减少请求的传递, 使得业务数据在对外服就能处理,而不需要经过逻辑服。
- 避免序列化操作,由于路由对应的业务数据是以 byte[] 类型缓存在对外服的, 所以从缓存中取得的业务数据,将不再需要序列化(编码)操作了。 简单点说,就是不需要将业务对象转换成 byte[] 类型了。
- 支持条件缓存,同一 action 支持不同的请求参数。
- 支持路由范围缓存配置
对比各解决方案
为了让开发者更好的理解对外服缓存的优势,这里先引入两个开发者常用的第三方解决方案来做对比。
与 Hazelcast、Redis 等第三方解决方案相比较
- Redis 有一些额外的开销,比如序列化和反序列化数据、对数据进行压缩和解压缩、网络IO、网络波动等方面。 使用对外服缓存特性可以有效避免这些问题,并且性能是 Redis 的数百倍。
- Hazelcast 也有一些额外的开销,每个节点占用的内存几乎相同, 无论这些内存数据是否对你有用,都会强行占用你的内存。 如果其中一个节点占用了 1G 内存,那么其他节点也将占用 1G 内存。 每次数据发生变动时,都需要经过N个节点的序列化、N个节点的网络IO等操作。 对于不熟悉 Hazelcast 的开发者来说,安装、配置和调试 Hazelcast 集群可能会比较复杂,需要花费一定的时间和精力。
使用 Hazelcast、Redis 等第三方解决方案,缺点
- 第三方解决方案需要你进行额外的安装(安装成本、机器资源占用成本)。
- 在项目中引入第三方后,需要学习相关 API 的使用(学习成本)。
- 需要你进行很多额外的工作,例如数据的存储、更新等(使用成本、维护成本)。
对比小结
最重要的是,开发者使用对外服缓存时,是无感知的(零学习成本)。
| 对外服缓存 | Hazelcast | Redis | 描述 | |
|---|---|---|---|---|
| 高性能 | ✅ | ✅ | ❌ | 两者比 Redis 快数百倍 |
| 零安装成本 | ✅ | ❌ | ❌ | |
| 零学习成本 | ✅ | ❌ | ❌ | |
| 零使用成本 | ✅ | ❌ | ❌ | |
| 零维护成本 | ✅ | ❌ | ❌ | |
| 内存占用 | 按需增加 | 节点均等 | 内存加载所有 | |
| 序列化次数 | 0 | 0 | N | 每次访问缓存时 |
| 网络 io 次数 | 0 | 0 | N | 每次访问缓存时 |
使用场景
使用场景举例
- 如一些配置数据信息 : 装备的配置、道具的配置... 等。
- 排行榜,如果及时性要求不高的,比如能接受 1、5、10、N 分钟同步一次的这种延迟排行榜,且数据量不多的(百来条或几百条)。
- 更多举例后续补充...
对外服缓存的适用场景
- 热点数据
- 请求较为频繁的,数据不常变动的。
你可以将对外服看成一台另类的 "Nginx、OpenResty" 等服务器, 可为整体架构做一些前置抵挡、功能扩展等职责。
对外服缓存并不是用于取代 Hazelcast、Redis 之类的,只是在某些业务场景下会具备更大的优势。
缓存处理流程图
缓存处理流程图说明
- Player : 用户。
- ExternalServer : 对外服。
- request : 用户发送请求。
- response : 对外服的响应数据。
- cacheData : 缓存数据。检测到该请求有对应的缓存,将缓存结果发送给用户,否则到逻辑服中获取。
如何使用
对外服缓存的使用与路由访问权限控制类似, 如果你之前了解过这部分的内容,那么花几分钟就能上手了。
- code 3,配置缓存 3-1
PresentKit.ifPresent(ExternalGlobalConfig.externalCmdCache, externalCmdCache -> {
// cache config
externalCmdCache.addCmd(3, 1);
});
当用户请求 3-1 时,如果在对外服中找到了缓存数据,就直接在对外服环节将数据响应给用户。
如果没有找到缓存数据,请求将被传递到逻辑服,由逻辑服的操作进行处理。 在操作完成后,逻辑服将返回结果给对外服,并将该结果保存到缓存中。 下次其他用户请求 3-1 时,就可以从缓存中获取结果,从而实现快速响应。
更多配置
CmdCacheOption
CmdCacheOption 是对外服的缓存配置对象,缓存配置对象可以让我们在对外服缓存上做更精细的控制。
- code 3,缓存过期时间 1 小时。
- code 4,缓存过期检测时间间隔 5 分钟。
- code 5, 同一个 action 的缓存数量上限设置为 256 条。
CmdCacheOption createCmdCacheOption() {
return CmdCacheOption.builder()
.setExpireTime(Duration.ofHours(1))
.setExpireCheckTime(Duration.ofMinutes(5))
.setCacheLimit(256)
.build();
}
即使不设置,框架默认也是这个配置,这里只是展示如何创建的缓存配置选项。
配置不同的 CmdCacheOption
- code 4~5,创建并置默认的缓存配置项,之后添加的路由缓存都将使用这个缓存配置。
- code 6,添加路由缓存 22-1,将使用默认的缓存配置。
- code 8~14,创建一个新的缓存配置对象,并设置到 22-2 、22-3 的路由中。 (不会使用默认的配置,而是使用 optionCustom 这个缓存配置对象。)
...
void extractedExternalCache() {
PresentKit.ifPresent(ExternalGlobalConfig.externalCmdCache, externalCmdCache -> {
var defaultOption = createCmdCacheOption()
externalCmdCache.setCmdCacheOption(defaultOption);
externalCmdCache.addCmd(22, 1);
var optionCustom = CmdCacheOption.builder()
.setExpireTime(Duration.ofSeconds(30))
.setExpireCheckTime(Duration.ofSeconds(5))
.build();
externalCmdCache.addCmd(22, 2, optionCustom);
externalCmdCache.addCmd(22, 3, optionCustom);
});
}
CmdCacheOption createCmdCacheOption() {
return CmdCacheOption.builder()
.setExpireTime(Duration.ofHours(2))
.setExpireCheckTime(Duration.ofMinutes(10))
.setCacheLimit(128)
.build();
}
路由范围缓存配置
这里,我们添加了主路由为 2 的值,对外服会对主路由为 2 下所有子路由的数据进行缓存。 通过路由范围缓存,我们可以避免为每个路由做单独的配置。
举个例子,对于 2-1、2-2、2-N 等子路由,即使你没有为这些子路由配置相关的缓存,缓存仍然会生效。 子路由使用的缓存配置与主路由使用的缓存配置相同。
PresentKit.ifPresent(ExternalGlobalConfig.externalCmdCache, externalCmdCache -> {
externalCmdCache.addCmd(2);
});
Example Source Code
该功能为企业级功能