更快的 Redis:客户端库支持客户端侧缓存
1. 原创声明
原文地址:https://redis.io/blog/faster-redis-client-library-support-for-client-side-caching/
2. 概述
比 Redis 更快不容易,但现在可以读取访问最频繁的数据,减少延迟,并且更有效地利用资源。官方 Redis 客户端库已支持客户端侧缓存。
- 用于 Java 的 Jedis
- 用于 Python 的 Redis-py
Redis 社区版从版本 6 开始支持客户端侧缓存。但到目前为止,需要商用的第三方客户端库才能使用,因为 Redis 未在官方客户端库中提供支持。现在,可以直接使用 Redis 官方的开源客户端库使用近缓存,仅需几行代码,即可在建立连接时启用缓存。
下面回顾什么是近缓存以及使用近缓存的动机,看看客户端侧缓存如何加快数据访问。
Redis 的客户端-服务端模型意味着性能受网络限制。每个操作都需要往返通信,网络可能影响性能,特别是在高吞吐系统中或存在网络延迟问题时。
减少网络开销,同时提高应用程序性能的一种有效方法是通过客户端侧缓存,也被称为近缓存(near cache)。该技术支持从运行(Redis)客户端的应用服务上的缓存直接服务频繁的读取操作。启用客户端侧缓存后,客户端应用程序将使用后端应用程序缓存的数据,从而减少网络流量和延迟。
以下是其作用:
- 本地缓存非常适合大量读取的场景、低带宽环境中的部署以及管理热分片/键(用于读取)。
- 减少到服务端的网络往返,有助于降低带宽消耗和成本 - 尤其是在云中。
- 最小化服务端负载,减少计算开销,节省基础设施成本。
3. 工作原理
下面通过示例说明客户端侧缓存的工作方式。
- SET foo bar
- 数据存储在服务端
- GET foo
- 命令被发送到服务端
- 客户端在本地内存中缓存响应
- GET foo
- 客户端从本地内存获取 bar
当任意客户端修改被追踪的数据时,服务端将向当前追踪该数据的所有客户端发布失效消息。
- SET foo qux
- 客户端 A 修改数据
- 失效消息及缓存移除
- 客户端 B 在最初用于请求该数据的同一连接上收到实效消息
- 客户端 B 从缓存中移除数据
- GET foo
- 客户端 B 再次请求数据
- 客户端 B 在本地内存缓存响应
- GET foo
- 客户端 B 从本地内存获取 qux
4. 测试客户端侧缓存
客户端侧缓存需要 RESP3 协议,因为需要推送通知。在设置连接时,需要确保选择正确的协议版本。
为简化所有 Redis 产品的开发体验,需要 Redis CE 7.4,Redis Stack 7.4 或更新版本。也可以测试最新的 Redis 8 M01 里程碑版本。客户端侧缓存与 Redis Software、Redis Cloud 和 Azure 的 Redis Enterprise 完全兼容,该功能目前处于预览阶段,将很快推出。
如前所述只需几行代码即可很容易地设置客户端侧缓存,下面就是证明。下面的示例不会复杂化客户端设置。仅需在建立连接时启用缓存,如下面的 Python 示例所示。
r = redis.Redis(
protocol=3,
cache_config=CacheConfig(),
decode_responses=True
)
r.set("city", "New York")
cityNameAttempt1 = r.get("city") # Retrieved from Redis server and cached
cityNameAttempt2 = r.get("city") # Retrieved from cache
第一次检索键“city”时,将从数据库读取,并且将其缓存在调用进程的内存中。后续读取将透明地由本地缓存提供,因此具有最小的延迟,并且不会给数据库带来额外的负载。
如果使用 Java,那么由 Jedis 完成所有繁重工作。可以像下面这样创建连接,启用客户端侧缓存。
HostAndPort endpoint = new HostAndPort("localhost", 6379);
DefaultJedisClientConfig config = DefaultJedisClientConfig.builder().protocol(RedisProtocol.RESP3).build();
CacheConfig cacheConfig = CacheConfig.builder().maxSize(1000).build();
UnifiedJedis client = new UnifiedJedis(endpoint, config, cacheConfig);
Map<String, String> usr = new HashMap<>();
usr.put("id", "Johnny");
usr.put("first", "John");
usr.put("last", "Doe");
client.hset("session:e788eeb2", usr);
// Retrieved from Redis server and cached
client.hgetAll("session:e788eeb2");
// Retrieved from cache
client.hgetAll("session:e788eeb2");
// Peek into the cacheCache cache = client.getCache();
System.out.println(cache.getSize());
System.out.println(cache.getCacheEntries());
System.out.println(cache.getStats());
5. 问答
5.1. 客户端库支持连接池吗?
可以为独立连接和连接池启用客户端侧缓存。也支持 Cluster 和 Sentinel API。
5.2. 应该何时启用缓存?
每当缓存键的值被修改时,Redis 将向缓存该键的所有客户端推送失效消息。告诉客户端刷新该键的无效本地缓存值。该行为意味着需要在本地缓存命中和失效消息之间进行权衡:本地缓存命中率大于失效消息率的键是本地追踪和缓存的最佳候选。
比如表示为字符串或嵌入到 hash、排行榜等的计数器 - 可以在标准连接上读取这样的键,以防止过量的失效消息。
如果不希望缓存某些键,该如何做?
为控制缓存的键,实例化未启用客户端侧缓存的标准连接。
5.3. 缓存哪些数据?
客户端缓存发送到数据库的命令的规范化版本以及返回结果。缓存除以下命令外的所有只读命令:
- 用于概率和时序数据结构的命令
- 搜索和查询命令
- 非确定性命令(比如 HSCAN、ZRANDMEMBER 等)
5.4. 如何预估本地缓存的内存消耗?
缓存条目的大小是可变的,因此本地缓存的内存消耗与工作负载和存储的数据大小有关。客户端支持检查缓存,因此关于本地存储数据的相关统计信息支持基准测试。
5.5. 如何验证缓存是否适用于命令?
如何希望知道底层发生什么,那么在 Redis Insight 中启动分析器,查看发送的命令,或者从终端在 redis-cli 会话中使用 MONITOR 命令。
6. 使用客户端库
阅读 https://redis.io/docs/latest/develop/connect/clients/client-side-caching/,获取有关客户端侧缓存的更多信息,使用 GA 版本的客户端库。官方承诺将很快为其他语言的客户端库添加客户端侧缓存。
- Jedis v5.2.0.。查看 Jedis 专用文档
- redis-py v5.1.1。查看 redis-py 专用文档