Redis6.0新特性之客户端缓存

一、客户端缓存(Client side caching)简介 

客户端缓存是一种用于创建高性能服务的技术。它利用应用服务器(与数据库节点相比,应用服务器通常是不同的计算机)中的可用内存,以便将数据库信息的某些子集直接存储在应用程序端。

先来回忆常见的缓存结构1、MySQL buffer   2、Redis缓存。他们都是在数据库层面(DB域)的缓存,而不是应用服务器层面的。

由于中间存在一定的网络损耗,于是Redis新增了客户端层面(应用层)的缓存功能,将部分数据集缓存到应用层,在服务访问的时候直接读取应用层缓存的数据,

访问本地服务器内存的时间相对于网络传输而言少了几个量级,可以提高性能。

 

举个例子,比如之前要获取user:1234的信息,它的流程如下图,需要去数据库获取信息。

而现在有了应用层的缓存,可以将user:1234缓存到应用层,访问的时候可以直接获取,如下图:

 

用于本地缓存的应用程序内存可能不是很大,且访问本地计算机内存所需的时间要比查询数据库之类的网络服务小几个数量级。由于经常非常频繁地访问相同百分比的数据,

这种模式可以极大地减少应用程序获取数据的延迟,同时减少数据库端的负载。此外,有许多数据集的变化非常不频繁。

例如,社交网络中大多数用户的帖子要么是不可变的,要么很少被用户编辑。再加上通常只有一小部分的帖子是非常受欢迎的,要么是因为一小部分用户有很多关注者,

要么是因为最近的帖子有更多的可见性,所以这种模式非常有用。

通常客户端缓存的两个关键优势是:

1、数据可用的延迟非常小。

2、数据库系统接收的查询变少,可以允许使用更少的节点为相同的数据集提供服务。

 

上述模式的一个问题是如何持避免应用程序向用户显示陈旧的数据。例如,在上面的应用程序本地缓存了用户:1234信息之后,Alice可能会将她的用户名更新到Flora。然而,应用程序可以继续为用户1234提供旧用户名。

根据以往的过期策略模式,可以设定客户端只会为缓存的信息使用一个固定的最大“存活时间”。一旦经过了给定的时间,这些信息将不再被认为是有效的,然而这种模式存在着实时性问题,例如更新了信息无法立即显示有效。而更复杂的模式,当使用Redis时,利用Pub/Sub系统来发送无效消息给监听的客户端。这可以实现,但从所使用的带宽的角度来看,这是很棘手且昂贵的,因为这种模式通常涉及向应用程序中的每个客户端发送无效消息,即使某些客户端可能没有任何无效数据的副本。此外,修改数据的每个应用程序查询都需要使用PUBLISH命令,这会使数据库在处理该命令时花费更多的CPU时间。

无论使用什么模式,有一个简单的事实:许多大型应用程序实现了某种形式的客户端缓存,因为这是拥有快速存储或快速缓存服务器的下一个逻辑步骤。出于这个原因,Redis 6实现了对客户端缓存的直接支持,以使这个模式更容易实现,更容易访问,更可靠和高效。

 

二、Redis客户端缓存实现模式

Redis客户端缓存有两种模式:
1、在默认模式下,服务器记录(tracking)给定客户端访问的keys,并在修改已缓存的key时发送无效消息。这将消耗服务器端的内存,但仅为客户端在内存中可能拥有的keys发送无效消息。
2、在广播模式下,服务器不会尝试记住给定客户端访问的key,因此这种模式不会在服务器端消耗任何内存。相反,客户端订阅诸如object:或user:这样的键前缀,并在每次触摸与此类前缀匹配的键时接收通知消息。

 

(2.1)默认模式

先来看默认模式的特点:
1、如果客户需要,可以启用跟踪功能。可以关闭跟踪(tracking)连接创建访问。
2、当启用跟踪时,服务器将记录每个客户端在连接生命周期中请求的key(通过发送有关这些key的读取命令)。
3、当某个客户端修改某个key时,或者因为它有关联的过期时间而驱逐它,或者因为maxmemory策略而驱逐它,那么所有启用跟踪并缓存了该键的客户端都会收到一条无效消息通知。
4、当客户端接收到无效消息时,需要更新或者删除相应的键,以避免提供陈旧的数据。

例如:

表面上看,这个模式很好,但如果考虑到连接上万个的客户端都需要存储数百万个键,那么服务器就会存储过多的信息。
出于这个原因,Redis使用了两个关键的想法来限制服务器端的内存使用量,以及处理数据结构实现该功能的CPU成本:
1、服务器会在全局表中记录所有缓存过key的客户端列表。这个表称为无效表。这个无效表可以包含最大数量的条目,如果插入了新的key,服务器可以通过假装修改了某个key(即使没有修改),
并向客户机发送一条无效消息来删除旧条目。这样服务器就可以收回用于这个key的内存,即使这将迫使拥有这个key的本地副本的客户端将其逐出。
2、在无效表中,我们并不需要在客户端断开连接时强制执行垃圾回收的存储指向客户端的指针结构,我们只需要存储客户端ID(每个Redis客户端都有一个唯一的数字ID)。
如果客户端断开连接,随着缓存槽失效,信息将被逐步垃圾收集。
3、需要设置一个单独的不以数据库编号的键名称空间。因此,如果客户端在数据库2中缓存键foo,而其他一些客户端更改了数据库3中键foo的值,仍然会发送一条无效消息。
通过这种方式,我们可以忽略数据库数量,从而减少内存使用和实现复杂性。

在默认情况下,客户端不需要告诉服务器它们正在缓存的key。服务器会跟踪只读命令上下文中提到的每个键,因为它可以被缓存。
一个好的缓存解决方案是缓存所有没有缓存且先进后出的方法:比如缓存固定数量的对象,缓存每一个新检索的数据,丢弃最古老的缓存对象。更高级的实现可能会删除最少使用的对象或类似的对象。
但是无论如何当服务器上有写入压力,缓存槽将在此期间失效。一般来说,当服务器假设我们也缓存我们得到的东西时,我们在做一个权衡:
1、当客户端倾向于使用欢迎新对象的策略缓存许多内容时,效率会更高。
2、服务器将被迫保留更多关于客户端key的数据。
3、客户端将收到关于它没有缓存的对象的无效消息。

(注:这部分工作还在进行中,还没有在Redis中实现)
在此引入一种更有效的方法:

客户端显式的指定想要实现缓存的键,来明确传达给服务器要缓存什么,如:CLIENT TRACKING on REDIRECT 1234 OPTIN
当缓存新对象这将需要更多的带宽,但同时会降低服务器需要记录的数据量和客户端接收到的失效消息的数量。

 

(2.2)广播模式
广播模式即是通过广播的方式将修改的key信息发送给客户端,这种方式的优点在于不占用服务端的内存,但是会发送更多的无效信息给客户端。这种模式的行为方式如下:
1、客户端使用BCAST选项启用客户端缓存,使用PREFIX选项指定一个或多个前缀。
例如:客户端记录重定向10BCAST前缀对象:前缀用户:。如果根本没有指定前缀,则假定前缀为空字符串,因此客户端将收到每个被修改键的无效消息。
相反,如果使用了一个或多个前缀,则在无效消息中只发送与指定前缀之一匹配的键。

这种模式下服务器不会在无效表中存储任何内容。相反,它只使用不同的前缀表,其中每个前缀都关联到一个客户机列表。
每当与任何前缀匹配的键被修改时,所有订阅了该前缀的客户端都会收到无效消息。
服务器将使用与注册前缀数量成比例的CPU。如果你只有几个需要匹配的前缀,很难看出有什么区别。使用大量前缀,CPU成本会变得非常大。
在这种模式下,服务器可以执行优化,为所有订阅了给定前缀的客户端创建单个应答,并向所有客户端发送相同的应答。这有助于降低CPU使用量。

 

三、缓存如何回收

1、如果我们失去了与用于获取无效消息的套接字的连接,我们可能会以陈旧的数据结束。为了避免这个问题,我们需要做以下几点:
2、确保如果连接丢失,则刷新本地缓存。当使用RESP2和Pub/Sub,或RESP3,定期ping无效通道。如果连接看起来中断了,并且我们不能接收回ping,在一段最大的时间后,关闭连接并刷新缓存。

 

四、Redis作者的一些使用建议

1、不缓存持续更新的key

2、不缓存很少被访问的key

3、缓存经常被请求并且以合理的速率改变的key。

4、设定合适大小的前缀匹配信息,达到资源使用和高性能业务访问的平衡。

 

五、相关参数 &统计信息

Redis-cli Info命令的clients模块新增了tracking_clients和clients_in_timeout_table信息。

在INFO命令stats模块新增了4个统计信息

 

配置参数

tracking-table-max-keys 1000000

 

六、使用方式

命令格式如下:

其中CLIENT TRACKING是固定格式,REDIRECT client-id是指定客户端连接id,省略即是指当前连接。PREFIX是设置记录前缀key,BCAST是开启广播模式。

OPINT、OPTOUT、NOLOOP是三种模式,在OPTIN模式下,读取的key是不会被记录到客户端缓存,在执行Client Caching yes后,才记录下一个读取的key。

相反的,在OPTOUT模式下,所有读取的key都会被记录到客户端缓存,在执行Client Caching no后,会跳过记录下一个读取的key。

在一些将写操作缓存到本地的客户端里,我们不希望客户端缓存再接收当前客户端更新的命令,在NOLOOP模式下,可以避免写入命令后将本地已缓存的信息删除过期。

 

posted @ 2020-11-15 21:32  洲渚皓月掩映  阅读(706)  评论(0编辑  收藏  举报