Redis6.0、7.0 新特性整理

一、redis6.0特性

1、众多新模块(modules)API

Redis 6中模块API开发进展非常大,因为Redis Labs为了开发复杂的功能,从一开始就用上Redis模块。Redis可以变成一个框架,利用Modules来构建不同系统,而不需要从头开始写然后还要BSD许可。Redis一开始就是一个向编写各种系统开放的平台。如:Disque作为一个Redis Module使用足以展示Redis的模块系统的强大。集群消息总线API、屏蔽和回复客户端、计时器、模块数据的AOF和RDB等。

2、更好的过期循环(expire cycle)

Redis 6重新编写了Redis活动到期周期,以更快地回收已到期的key。

3、 多线程 IO(Threaded I/O)

3.1、 Redis6.0为什么要引入多线程呢?

        Redis将所有数据放在内存中,内存的响应时长大约为100纳秒,对于小数据包,Redis服务器可以处理80,000到100,000 QPS,这也是Redis处理的极限了,对于80%的公司来说,单线程的Redis已经足够使用了。

  • 使用redis时,几乎不存在CPU成为瓶颈的情况,redis主要受限于内存和网络
  • 在一个普通的linux系统上,redis通过使用pipelining每秒可以处理100万个请求,所以如果应用程序主要使用Q ( N ) Q(N)Q(N)或者O ( l o g ( N ) ) O(log(N))O(log(N))的命令,它几乎不会占用太多CPU

随着硬件性能提升,redis的性能瓶颈可能出现网络IO的读写,也就是:单个线程处理网络读写的速度跟不上底层网络硬件的速度。

读写网络的read/write系统调用占用了redis执行期间大部分CPU时间,瓶颈主要在于网络的IO消耗,优化主要有两个方向

  • 提高网络 IO 性能,典型的实现比如使用 DPDK来替代内核网络栈的方式、零拷贝技术。
  • 使用多线程充分利用多核,提高网络请求读写的并行度,典型的实现比如 Memcached。

注意的是,redis多IO线程模型只用来处理网络读写请求,对于redis的读写命令,依然是单线程处理。

开启多线程配置:

io-threads-do-reads yes

注:Redis6.0的多线程默认是禁用的,只使用主线程。

开启多线程后,还需要设置线程数,否则是不生效的。修改redis.conf配置文件:

io-threads 

线程数设置建议:核的机器建议设置为2或3个线程,8核的建议设置为6个线程,线程数一定要小于机器核数。还需要注意的是,线程数并不是越大越好,官方认为超过了8个基本就没什么意义了。

如果开启多线程,至少要4核的机器,且Redis实例已经占用相当大的CPU耗时的时候才建议采用,否则使用多线程没有意义。

3.2 Redis6.0多线程实现机制

架构图如下:

  • 主线程负责接收建立连接过程,获取socket放入全局等待读处理队列
  • 主线程通过轮询将可读socket分配给IO线程
  • 主线程阻塞等待IO线程读取socket完成
  • 主线程执行IO线程读取和解析出来的redis请求命令
  • 主线程阻塞等待 IO 线程将指令执行结果回写socket完毕;
  • 主线程清空全局队列,等待客户端后继的请求

该设计有如下特点:

  • IO 线程要么同时在读 Socket,要么同时在写,不会同时读或写。
  • IO 线程只负责读写 Socket 解析命令,不负责命令处理。

开启多线程后,是否会存在线程并发安全问题? 

不存在。

从实现机制可以看出,redis的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程顺序执行

所以我们不需要去考虑控制 Key、Lua、事务,LPUSH/LPOP 等等的并发及线程安全问题。

3.3Redis线程中经常提到IO多路复用,如何理解? 

这是IO模型的一种,即经典的Reactor设计模式,有时也称为异步阻塞IO。

多路指的是多个socket连接,复用指的是复用一个线程。

      多路复用主要有三种技术:select,poll,epoll。epoll是最新的也是目前最好的多路复用技术。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗),且Redis在内存中操作数据的速度非常快(内存内的操作不会成为这里的性能瓶颈),主要以上两点造就了Redis具有很高的吞吐量。

4、 ACL支持

当有了ACL之后,你就可以控制比如:这个连接只允许使用RPOP,LPUSH这些命令,其他命令都无法调用。

      Redis提供了一个新的命令ACL来维护Redis的访问控制信息,详情见: https://redis.io/topics/acl

  Redis 6开始支持ACL,该功能通过限制对命令和key的访问来提高安全性。ACL的工作方式是在连接之后,要求客户端进行身份验证(用户名和有效密码);如果身份验证阶段成功,则连接与指定用户关联,并且该用户具有限制。
  在默认配置中,Redis 6的工作方式与Redis的旧版本完全相同,每个新连接都能够调用每个可能的命令并访问每个键,因此ACL功能与旧版本向后兼容。客户和应用程序。依旧使用requirepass配置密码的,但现在只是为默认用户设置密码。

5、哨兵和副本的ACL规则

如果不想为Redis副本和Redis Sentinel实例提供对Redis实例的完全访问权限,则以下是一组命令,为了使一切正常工作,必须允许这些命令。
  对于Sentinel,允许用户在主实例和副本实例中访问以下命令:
  Sentinel不需要访问数据库中的任何密钥,因此ACL规则如下(注意:不需要AUTH,因为始终允许使用AUTH):

ACL setuser sentinel-user >somepassword +client +subscribe +publish +ping +info +multi +slaveof +config +client +exec on

 Redis副本需要在主实例上将以下命令列入白名单:

PSYNC,REPLCONF,PING
  不需要访问任何密钥,因此这转化为以下规则:

ACL setuser replica-user >somepassword +psync +replconf +ping on
  无需将副本配置为允许主服务器能够执行任何命令集:从副本的角度来看,主服务器始终被认证为root用户。

6、 使用外部ACL文件

         Redis6因为引入了权限机制,会有不同分工的用户;所以又引入了额外的配置项以及配置文件,通过ACL LOAD/ACL SAVE报错和加载acl文件。

[root@zijie ~]# cat /etc/Redis6.conf  | grep acl
aclfile /usr/local/Redis/users.acl
acllog-max-len 128
#Redis6开始密码加密存储
[root@zijie ~]# cat /usr/local/Redis/users.acl
user default on #ce306e0ee195cc817620c86d7b74126d0d66c077b66f66c10f1728cf34a214d3 ~* +@all
user zijie off -@all
user zijie2 on #a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3 ~* -@all +get
user zijie3 off ~zijie* ~name* -@all
#获取安全密码
127.0.0.1:6379> ACL GENPASS
"c550b646ef8f7f91908628db0d983b4ca061fcf36433baa770ea9ce09da64ef4"

7 、ACL LOG

 记录拒绝的命令,密钥访问和身份验证。

127.0.0.1:6379> acl log
 1)  1) "count"
     2) (integer) 1
     3) "reason"
     4) "auth"
     5) "context"
     6) "toplevel"
     7) "object"
     8) "auth"
     9) "username"
    10) "default"
    11) "age-seconds"
    12) "7.3860000000000001"
    13) "client-info"
    14) "id=18 addr=127.0.0.1:51882 fd=8 name= age=2 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=32742 obl=0 oll=0 omem=0 events=r cmd=auth user=default"

8、RESP3 协议

RESP(Redis Serialization Protocol)是 Redis 服务端与客户端之间通信的协议。
  RESP3 是 RESP version 2 的更新版本。RESP v2 大致从 Redis 2.0 开始支持(其实 1.2 就支持了,只不过 Redis 2.0 是第一个仅支持此协议的版本)。

 127.0.0.1:6379> hello 2
   1) "server"
   2) "Redis"
   3) "version"
   4) "6.0.5"
   5) "proto"
   6) (integer) 2
   7) "id"
   8) (integer) 7
   9) "mode"
    10) "standalone"
    11) "role"
    12) "master"
    13) "modules"
    14) (empty array)
    127.0.0.1:6379> hello 3
    1# "server" => "Redis" // 服务名称
    2# "version" => "6.0.5" // 版本号
    3# "proto" => (integer) 3 // 支持的最高协议
    4# "id" => (integer) 7 // 客户端连接 ID
    5# "mode" => "standalone" //  模式:"standalone", "sentinel", "cluster"
    6# "role" => "master" //  "master" 或 "replica"
    7# "modules" => (empty array) // 加载的模块列表

9、客户端缓存

        Redis 客户端缓存在某些方面进行了重新设计,特别是放弃了缓存槽(caching slot)方法而只使用 key 的名称。在分析了备选方案之后,在其他 Redis 核心团队成员的帮助下,这种方法最终看起来更好。
  客户端缓存重新设计中引入了广播模式(broadcasting mode)。在使用广播模式时,服务器不再尝试记住每个客户端请求的 key。 取而代之的是,客户订阅 key 的前缀:每次修改匹配前缀的 key 时,这些订阅的客户端都会收到通知。这意味着会产生更多的消息(仅适用于匹配的前缀),但服务器端无需进行任何内存操作。

         客户端缓存的功能是该版本的全新特性,服务端能够支持让客户端缓存values,Redis作为一个本身作为一个缓存数据库,自身的性能是非常出色的,但是如果可以在Redis客户端再增加一层缓存结果,那么性能会更加的出色。Redis实现的是一个服务端协助的客户端缓存,叫做tracking。客户端缓存的命令是:

CLIENT TRACKING ON|OFF [REDIRECT client-id] [PREFIX prefix] [BCAST] [OPTIN] [OPTOUT] [NOLOOP]

    当tracking开启时, Redis会"记住"每个客户端请求的key,当key的值发现变化时会发送失效信息给客户端。失效信息可以通过 RESP3 协议发送给请求的客户端,或者转发给一个不同的连接(支持RESP2+ Pub/Sub)。

客户端缓存:

      redis 6 提供了服务端追踪key的变化,客户端缓存数据的特性,这需要客户端实现

目前只有lettuce对其进行了支持:

<dependency>
   <groupId>io.lettuce</groupId>
   <artifactId>lettuce-core</artifactId>
   <version>6.0.0.RELEASE</version>
</dependency>
 
public static void main(String[] args) throws InterruptedException {
    RedisClient redisClient = RedisClient.create("redis://192.168.109.200");
 
    Map<String, String> clientCache = new ConcurrentHashMap<>();
 
    StatefulRedisConnection<String, String> myself = redisClient.connect();
 
    CacheFrontend<String, String> frontend =
            ClientSideCaching.enable(CacheAccessor.forMap(clientCache),
            myself,
            TrackingArgs.Builder.enabled().noloop());
 
    String key="csk";
    int count = 0;
    while (true){
 
        System.out.println(frontend.get(key));
        TimeUnit.SECONDS.sleep(3);
        if (count++ == Integer.MAX_VALUE){
            myself.close();
            redisClient.shutdown();
        }
    }
}

10、无盘复制&PSYNC2

现在,Redis用于复制的 RDB 文件如果不再有用,将立即被删除。不过,在某些环境中,最好不要将数据放在磁盘上,而只放在内存中。

repl-diskless-sync no
repl-diskless-sync-delay 5
repl-diskless-load disabled

复制协议 PSYNC2 现在得到了改进。Redis 将能够更频繁地部分重新同步,因为它能够修整协议中的最终 PING,从而使副本和主副本更有可能找到一个公共的偏移量。

        Redis 6.0,RDB 文件的加载速度比之前变得更快了。根据文件的实际组成(较大或较小的值),大概可以获得 20-30% 的改进。除此之外,INFO 也变得更快了,当有许多客户端连接时,这会消耗很多时间,不过现在终于消失了。

Redis 7

1、Function

Function是Redis脚本方案的全新实现,在Redis 7.0之前用户只能使用EVAL命令族来执行Lua脚本,但是Redis对Lua脚本的持久化和主从复制一直是undefined状态,在各个大版本甚至release版本中也都有不同的表现。因此社区也直接要求用户在使用Lua脚本时必须在本地保存一份(这也是最为安全的方式),以防止实例重启、主从切换时可能造成的Lua脚本丢失,维护Redis中的Lua脚本一直是广大用户的痛点。

Function的出现很好的对Lua脚本进行了补充,它允许用户向Redis加载自定义的函数库,一方面相对于EVALSHA的调用方式用户自定义的函数名可以有更为清晰的语义,另一方面Function加载的函数库明确会进行主从复制和持久化存储,彻底解决了过去Lua脚本在持久化上含糊不清的问题。

 2、Multi-part AOF

AOF是Redis数据持久化的核心解决方案,其本质是不断追加数据修改操作的redo log,那么既然是不断追加就需要做回收也即compaction,在Redis中称为AOF rewrite。

然而AOF rewrite期间的增量数据如何处理一直是个问题,在过去rewrite期间的增量数据需要在内存中保留,rewrite结束后再把这部分增量数据写入新的AOF文件中以保证数据完整性。可以看出来AOF rewrite会额外消耗内存和磁盘IO,这也是Redis AOF rewrite的痛点,虽然之前也进行过多次改进但是资源消耗的本质问题一直没有解决。

阿里云的Redis企业版在最初也遇到了这个问题,在内部经过多次迭代开发,实现了Multi-part AOF机制来解决,同时也贡献给了社区并随此次7.0发布。具体方法是采用base(全量数据)+inc(增量数据)独立文件存储的方式,彻底解决内存和IO资源的浪费,同时也支持对历史AOF文件的保存管理,结合AOF文件中的时间信息还可以实现PITR按时间点恢复(阿里云企业版Tair已支持),这进一步增强了Redis的数据可靠性,满足用户数据回档等需求。

3、Sharded-pubsub

Redis自2.0开始便支持发布订阅机制,使用pubsub命令族用户可以很方便地建立消息通知订阅系统,但是在cluster集群模式下Redis的pubsub存在一些问题,最为显著的就是在大规模集群中带来的广播风暴。

Redis的pubsub是按channel频道进行发布订阅,然而在集群模式下channel不被当做数据处理,也即不会参与到hash值计算无法按slot分发,所以在集群模式下Redis对用户发布的消息采用的是在集群中广播的方式。

那么问题显而易见,假如一个集群有100个节点,用户在节点1对某个channel进行publish发布消息,该节点就需要把消息广播给集群中其他99个节点,如果其他节点中只有少数节点订阅了该频道,那么绝大部分消息都是无效的,这对网络、CPU等资源造成了极大的浪费。

Sharded-pubsub便是用来解决这个问题,意如其名,sharded-pubsub会把channel按分片来进行分发,一个分片节点只负责处理属于自己的channel而不会进行广播,以很简单的方法避免了资源的浪费。

4、Client-eviction

Redis支持内存规格配置,maxmemory和maxmemory-policy大家已经都很熟悉了,但是在这里我还是想再解释一下:maxmemory控制的是Redis的整体运行内存而非数据内存,例如client buffer、lua cache、fucntion cache、db metadata等这些都会算在运行内存中,如果运行内存超过了maxmemory就会触发evict删除数据,这也是用户在使用Redis时的一大痛点。

在这些非数据内存使用当中,又以client buffer消耗最大,在大流量场景下client需要缓存很多用户读写数据(想象一下keys的结果需要先缓存在client output buffer中再发送给用户),由于网络流量的内存消耗导致触发eviction删除数据的情况非常之多。虽然Redis很早就支持对client-output-buffer-limit配置项,但其限制的也只是单个连接维度的output buffer,没有全局统计client使用内存和限制。为了解决这个问题,7.0新增了maxmemory-clients配置项,来对所有client使用的内存做限制,如果超过了这个限制就会挑选内存消耗最大的client释放,用来缓解内存使用的消耗。

Client-eviction并不是终点,还有很多元数据的内存使用会对用户造成困扰,Redis是基于内存的数据库,我们需要对各个模块的内存进行更精确的统计和控制,让用户能够对数据存储有更为清晰的理解和规划。

5、重要变化

  • 将AOF文件的存储方式改为在一个文件夹下存储多个文件。
  • 将持久化文件RDB的版本升级为10,与之前的RDB文件版本不再兼容。
  • 在读取老的RDB文件格式的时候将ziplist转换为listpack,这种转换发生于两种情况之下:从磁盘读取文件或者从一个主节点进行复制文件的时候。
  • 在redis.conf配置文件中,protected-mode 默认更改为yes,只有当你希望你的客户端在没有授权的情况下可以连接到Redis server的时候可以将protected-mode设置为no。
  • 在ACL中,pub/sub channel默认是被阻塞的。
  • 在从节点中,TTL的时间标识的是绝对时间,不再是相对时间,从而保证了过期数据被及时删除。
  • 不再支持 gopher协议。
  • 当在配置文件中设置replica-serve-stale-data=no, 当主节点不再提供服务时,PING命令得不到返回值。

参考文章:

Redis4.0、5.0、6.0、7.0特性整理(持续更新)_zzhongcy的博客-CSDN博客_redis5.0新特性

从Redis7.0发布看Redis的过去与未来

Redis 6.0 新特性详解-阿里云开发者社区

redis面试:redis 6.0 多线程的实现机制_OceanStar的学习笔记的博客-CSDN博客_redis6.0多线程原理

  • 6
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值