Redis6引入新的RESP3协议,并以此为基础加入了客户端缓存的新特性,在此特性下,大大提高了应用程序的响应速度,并降低了数据库的压力,本篇就带大家来看一下Redis6的新特性:客户端缓存。
目录
什么是客户端缓存
什么是RESP3
客户端缓存的实现方式
默认模式
原理
应用
广播模式
原理
应用
重定向模式
OPTIN 和 OPTOUT
NOLOOP选项
失效表key上限
Redis6系列文章:
Redis系列(一)、CentOS7下安装Redis6.0.3稳定版
Redis系列(二)、数据类型之字符串String
Redis系列(三)、数据类型之哈希Hash
Redis系列(四)、数据类型之列表List
Redis系列(五)、数据类型之无序集合Set
Redis系列(六)、数据类型之有序集合ZSet(sorted_set)
Redis系列(七)、常用key命令
Redis系列(八)、常用服务器命令
Redis系列(九)、Redis的“事务”及Lua脚本操作
Redis系列(十)、详解Redis持久化方式AOF、RDB以及混合持久化
Redis系列(十一)、Redis6新特性之ACL安全策略(用户权限管理)
Redis系列(十二)、Redis6集群搭建及原理(主从、哨兵、集群)
Redis系列(十三)、pub/sub发布与订阅(对比List和Kafka)
Redis系列(十四)、Redis6新特性之RESP3与客户端缓存(Client side caching)
客户端缓存是一种用于创建高性能服务的技术,在此技术下,应用程序端将数据库中的数据缓存在应用端的内存中,当应用程序访问数据时直接从本机内存中读取,而无需连接数据库端,减少了网络IO,提升了应用程序的响应速度,同时也减少了数据库端的压力。
官网:https://redis.io/topics/client-side-caching
Why RESP3: http://antirez.com/news/125
没有客户端缓存:
应用端先查询Redis端,如果没有Redis缓存则到原数据库端查询,如果有则直接从Redis端查询数据,更新数据时直接更新MySQL端并同步到Redis内;
有客户端缓存:
应用端先查询本地缓存如Guava、Caffeine,若没有本地缓存则访问Redis缓存,如果Redis缓存中也没有则查询原数据库;
客户端缓存的优点:
1.降低了客户端的数据延迟,提升客户端的响应速度;
2.数据库端接收的查询减少,降低了数据库端的压力,因此在相同的数据集下可以使用更少的节点提供服务;
疑问:
为了实现客户端缓存,我们面临这样的问题,当进程中缓存了数据,而数据库端数据发生变更,该如何通知到进程,避免客户端显示失效的数据呢? 在Redis中可以使用上篇介绍过的发布订阅机制,向客户端发布数据失效的通知,但该模式下即使某些客户端中没有包含过期数据也会向所有客户端发送无效的消息,非常影响数据库的性能。
在之前的版本中,客户端缓存采用缓存槽(caching slot)的方式记录每个客户端内的key是否发生变化以及时同步,最新版中已弃用该方式,而是采用记录key的名称或前缀。
RESP全程RedisSerializationProtocol,是Redis服务端与客户端之间通信的协议。在Redis6之前的版本,使用的是RESP2协议,数据都是以字符串数组的形式返回给客户端,不管是list还是sorted set。因此客户端需要自行去根据类型进行解析,这样会增加了客户端实现的复杂性。
为了照顾老用户,Redis6在兼容 RESP2 的基础上,开始支持 RESP3,但未来会全面切换到RESP3之上。今天的客户端缓存在基于RESP3才能有更好的实现,可以在同一个连接中运行数据的查询和接收失效消息。而目前在RESP2上实现的客户端缓存,需要两个客户端连接以转发重定向的形式实现。
在Redis6中我们可以使用HELLO命令在RESP2和RESP3协议之间进行切换:
#使用RESP2协议
HELLO 2
#使用RESP3协议
HELLO 3
Redis客户端缓存被称为Tracking,在RESP3协议下,有两种模式:
默认模式:服务器记录客户端访问了哪些key,当其中的key发生变更时给客户端发送失效信息,消耗服务器端内存;
广播模式:客户端订阅访问过的key的前缀,当符合模式的key发生变更就会被通知(即使变更的key没有被客户端缓存),服务器端不记录客户端访问的key,因此不会消耗服务器端的内存;
服务器端会记录访问key的客户端列表并维护一个表,这个表被称为失效表(Invalidation Table),如果插入一个新的key,服务器端会给客户端发送失效信息并从客户端剔除该key,避免提供过时数据。
在失效表中不会记录key和客户端内对应指针的映射关系,只会记录key的指针和各客户端ID(每个Redis客户端都有一个唯一ID)的映射关系,当发送完失效信息后,客户端剔除key,服务端从失效表中删除key的指针和客户端ID的映射关系。
在失效表中key的命名空间只有一个,即是说,在db0~db15中相同的key名,在失效表中会记录在同一个命名空间内,即使客户端缓存的是db0内的key,如果db1内的同名key被更新,也会通知客户端剔除db0内的同名key。
客户端缓存的操作就是对key的内存地址进行操作:
1.当开启客户端缓存的客户端从Redis获取数据时,Redis服务端会调用enableTracking方法在上面的失效表中记录key和客户端ID的映射关系;
2.若key被修改,则Redis服务端会调用trackingInvalidateKey函数根据该key被缓存的客户端列表ID调用sendTrackingMessage函数向它们发送失效消息。(发送失效消息前会检查客户端的Client_Tracking和NOLOOP状态)
3.服务端发送完失效消息后会从失效表中将该key与客户端ID的映射关系删除;
4.由于客户端可能会在开启之后关闭了缓存功能,在失效表中删除key和该客户端ID之间的映射关系比较消耗性能,因此服务端采用懒删除的方式,只是将该客户端的Client_Tracking相关标志位删除;
上面提到我们可以使用HELLO命令切换RESP3协议,在此协议下我们使用tracking命令开启track追踪,此时服务端会记录客户端在连接的生命周期内的只读的key,当客户端开启track追踪后,key的数据会被缓存在客户端内存中:
#开启RESP3协议
HELLO 3
#开启tracking客户端缓存
client tracking on
#关闭tracking客户端缓存
client tracking off
为了演示失效消息的通知,这里使用telnet测试客户端缓存,然后在另一个redis-cli对key做操作:
# 使用telnet连接客户端
telnet wykd 6379
#auth命令登录服务器(如果没有密码可以忽略)
auth default wyk123456
#开启RESP3
hello 3
#开启客户端缓存tracking
client tracking on
#查询一个key 同时该key会被缓存
get name
#在另一个redis客户端中 修改/删除/过期/淘汰 该key
set name new_values
#在telnet窗口会受到key失效的消息如下:
get nam #客户端缓存key$3
wyk
>2 # 失效消息
$10
invalidate
*1
$4
name
#关闭客户端缓存tracking,关闭后不会再收到key的失效消息
client tracking off
当开启了tracking后,客户端缓存的key如果在别处被修改为与原值一样,也会收到失效消息;
当客户端缓存失效后,该key再被修改时,客户端不会再收到消息,也就是再查询该key之后才会在客户端缓存key的值;
当客户端缓存的key因过期策略或内存淘汰策略被驱逐时,服务端也会发送失效消息给开启了tracking的客户端:
当开启了tracking的客户端获取的key不存在时,如果在另一个客户端新增/修改了该key,那个tracking的客户端也会收到失效消息,可见如果key不存在也会在客户端缓存中缓存空值,这种结果因人而异,个人认为这样不太好,一是客户端会徒增大量的无用缓存,而是服务端的失效表会维护更多的key->clientID的映射关系。
另一个客户端缓存的实现方式是广播模式(broadcasting),广播不会消耗服务端的内存,而是向各客户端发送更多的失效消息。广播模式与默认模式类似,不同的是广播模式下维护的是前缀表,在前缀表中存储客户端订阅的key前缀与客户端ID之间的映射关系。
在这种模式下,有以下的主要行为:
1.客户端使用BCAST选项开启客户端缓存的广播模式,并使用PREFIX指定一个或多个前缀。如果不指定前缀则默认客户端接收所有的key的失效消息,如果指定则只会接收匹配该前缀的key的失效消息;
2.在广播模式下,服务端维护的不是失效表,而是前缀表(Prefix Table),每个前缀映射一些客户端ID;
3.每次修改跟任意前缀匹配的键时,所有订阅该前缀的客户端都将收到失效消息;
4.服务端的CPU消耗与订阅的key前缀数量成正比,订阅的key前缀数量越多服务器端压力越大;
5.服务器可以为订阅特定前缀的客户端创建单个回复,并向所有的客户端发送相同的回复来进行优化,有助于降低CPU使用率。
同样,在广播模式下也需要开启RESP3协议,这里我们仍然使用刚才的telnet会话进行演示。
使用下面的命令开启广播模式的客户端缓存,上面提到广播模式下服务端维护一个前缀表,记录key的前缀和客户端id的映射关系,因此我们也可以在客户端指定需要接收失效消息的key前缀:
#telnet访问redis客户端(略)
#开启RESP3
hello 3
#开启广播模式的客户端缓存tracking,默认会收到所有的key的失效信息
client tracking on bcast
#开启广播模式的客户端缓存tracking,只接受指定前缀'wyk'的key的失效信息
client tracking on bcast prefix wyk
广播模式下,只要符合客户端设置的key前缀的key发生新增、修改、删除、过期、淘汰等动作,即使该key没有被该客户端缓存,也会收到key的失效消息;
为了兼容RESP2协议,在Redis6中客户端缓存可以以重定向(Redirect)的方式实现,不再使用RESP3原生支持的PUSH消息,而是将消息通过Pub/Sub通知给另外一个客户端连接:
#查看客户端id
client id
#用于接收失效消息的客户端订阅频道
subscribe _redis_:invalidate
#客户端开启Tracking客户端缓存 并指定需要接收失效消息的客户端ID
client tracking on bcast redirect receive_client_id
OPTIN和OPTOUT
在默认模式或重定向模式下,我们可以有选择的对需要的key进行缓存,而由于广播模式是匹配key前缀,因此不能使用此命令。
#RESP3 默认模式
#切换RESP3协议
hello 3
#开启客户端缓存optin选项
client tracking on optin
#此命令后面第一个只读key会被缓存
client caching yes
#RESP2 重定向模式
hello 2
#开启客户端缓存optin选项,1234是接收失效消息的客户端id
client tracking on REDIRECT 1234 OPTIN
#此命令后面第一个只读key会被缓存
client caching yes
OPTIN:只有执行client caching yes之后的第一个key才会被缓存;
OPTOUT:与OPTIN相反,执行client caching on之后的第一个只读key不会被缓存;
注意:在redis6.0.3版本中optin和optout选项时灵时不灵,可能还有BUG;
我们的客户端修改自己已缓存的key的时候也会收到这个key的过期信息,事实上这个客户端是不需要收到该消息的,这造成了浪费,因此我们可以使用NOLOOP选项将该客户端设置为:本客户端修改的key不会收到相关的失效信息。
#开启客户端缓存的NOLOOP选项
client tracking on noloop
开启noloop选项的客户端,如果在该客户端上修改它已经缓存的key,自己不会收到该key的失效消息:
没开启noloop选项的客户端,如果在该客户端上修改它已经缓存的key,自己也会收到该key的失效消息:
可以使用 tracking_table_max_keys参数修改服务端失效表内记录的缓存的key的数量,当失效表内记录的缓存key达到配置的数量时会随机从失效表内移除缓存:
#查询最大缓存的数量
config get tracking-table-max-keys
#设置最大缓存数量为300
config set tracking-table-max-keys 300
参考文章:
Redis server-assisted client side caching
Why RESP3 will be the only protocol supported by Redis 6
Redis客户端缓存设计(In-Process caching)
带你100% 地了解 Redis 6.0 的客户端缓存
希望本文对你有帮助,请点个赞鼓励一下作者吧~ 谢谢!