一、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命令得不到返回值。
参考文章: