redis 5 新特性

稳定版 (5.0)
Redis 5.0 是第一个加入流数据类型(stream data type )的版本,sorted sets blocking pop operations, LFU/LRU info in RDB, Cluster manager inside redis-cli, active defragmentation V2, HyperLogLogs improvements and many other improvements. Redis 5 was release as GA in October 2018.



http://www.redis.cn/download.html

Redis 5主要专注于几个重要功能。相比之下Redis 4非常非常专注于操作类型,Redis 5的变化大多是面向用户的。即在现有的基础上增加新的数据类型和操作类型。



1.新的Stream数据类型。[1]



2.新的Redis模块API:Timers and Cluster API。




  1. RDB现在存储LFU和LRU信息。



4.集群管理器从Ruby(redis-trib.rb)移植到C代码。可以在redis-cli中。查看redis-cli —cluster help了解更多信息。



5.新sorted set命令:ZPOPMIN / MAX和阻塞变量。



6.主动碎片整理V2。



7.增强HyperLogLog实现。



8.更好的内存统计报告。



9.许多带有子命令的命令现在都有一个HELP子命令。



10.客户经常连接和断开连接时性能更好。



11.错误修复和改进。




  1. Jemalloc升级到5.1版



13.引入 CLIENT UNBLOCK 和 CLIENT ID
14.新增 LOLWUT 命令 http://antirez.com/news/123
15.在不存在需要保持向后兼容性的地方,弃用 “slave” 术语
16.网络层中的差异优化
17.Lua 相关的改进
18.引入动态的 HZ(Dynamic HZ) 以平衡空闲 CPU 使用率和响应性
19.对 Redis 核心代码进行了重构并在许多方面进行了改进



Redis Stream
Redis stream本质上是个时序数据结构,具有如下特点:



每条记录是结构化、可扩展的对
每条记录在日志中有唯一标识,标识中包含了时间戳信息,单调递增
可以根据需要自动清理历史记录
保存在内存中,支持持久化
底层是修改版的radix tree,每个node存储了一个listpack。listpack是一块连续的内存block,用于序列化msg entry及相关元信息,如msg ID,使用了多种编码,用于节省内存,是ziplist的升级版。如果XADD每次添加的对中的field是一样的,那么field不会重复存储



Zpop
Sorted Sets 增加了类似List的pop命令:
ZPOPMAX 命令用于移除并弹出有序集合中分值最大的 count 个元素
ZPOPMIN 命令用于移除并弹出有序集合中分值最小的 count 个元素
BZPOPMAX 和 BZPOPMIN 是上述两个命令的阻塞变种.



CLIENT:
Client id返回当前连接的ID,每个ID符合如下约束:



永不重复,可以判断当前链接是否断链过
单调递增,可以判断不同链接的接入顺序
Client unblock:
当客户端因为执行具有阻塞功能的命令(如BRPOP、XREAD或者WAIT)被阻塞时,该命令可以通过其他连接解除客户端的阻塞



CLIENT:
Client id返回当前连接的ID,每个ID符合如下约束:



永不重复,可以判断当前链接是否断链过
单调递增,可以判断不同链接的接入顺序
Client unblock:
当客户端因为执行具有阻塞功能的命令(如BRPOP、XREAD或者WAIT)被阻塞时,该命令可以通过其他连接解除客户端的阻塞



Redis 6 会给大家提供的新功能,包括:



一、对用户使用有直接影响的功能



ACL用户权限控制功能
RESP3:新的 Redis 通信协议
Cluster 管理工具
SSL 支持
二、Redis 内部的优化



IO多线程支持
新的Module API
新的 Expire 算法
三、外部工具



Redis Cluster Proxy
Disque



ACL
目前的 Redis(5及以下版本),没有用户权限管理这个概念,只有一个AUTH密码验证功能,基本上能够接入的用户就是root用户。
ACL 就是为了避免接入的用户进行危险命令的操作开发的功能,这类命令如 FLUSHALL,DEBUG等。还记得我之前分享过的一篇文章《从清档到二级索引》
多年来 Redis 管理员通过RENAME命令来进行限制。另一方面,开发人员有时候不清楚一些Redis 驱动的内部实现,可能无意中触发一些危险命令,所以也需要进行限制。
Redis 6 中加入ACL的功能,能够对接入的用户进行三个层面的权限控制:
(1)接入权限:用户名和密码;
(2)可以执行的命令;
(3)可以操作的 KEY。
下面我们实际代码中看看效果,下面展示我创建一个用户aaron,设置他的密码,允许执行所有命令,针对所有KEY。



127.0.0.1:6380> ACL WHOAMI
“default”
127.0.0.1:6380> ACL setuser aaron on >mypasswd +@all ~*
OK
127.0.0.1:6380> AUTH aaron mypasswd
OK
127.0.0.1:6380> ACL WHOAMI
“aaron”
127.0.0.1:6380> GET foo
(nil)
127.0.0.1:6380> SET foo bar
OK
然后我尝试将 aaron 这个用户去掉SET命令的权限。



127.0.0.1:6380> ACL setuser aaron -SET
OK
127.0.0.1:6380> SET foo 123
(error) NOPERM this user has no permissions to run the ‘set’ command or its subcommand
我们也可以控制用户可以对哪些 KEY 进行操作,比如下面演示一个叫做 Ben 的用户,他只能创建以 ben 为前缀的 KEY。



127.0.0.1:6380> ACL setuser ben on >mypasswd +@all ~ben*
OK
127.0.0.1:6380> set foo bar
(error) NOPERM this user has no permissions to access one of the keys used as arguments
127.0.0.1:6380> set benfoo bar
OK
“default” 用户是我们默认连接入 Redis 时的用户,默认情况下这个用户有所有的权限,当然了,我们也可以像以前那样给默认用户设置权限。通过ACL list可以查看当前有哪些用户和他们的权限和密码(前提是该用户有ACL命令的权限)。



127.0.0.1:6380> ACL list
1) “user aaron on >mypasswd ~* +@all -set”
2) “user default on nopass ~* +@all”
作者提到ACL功能是基于 bitmap 实现的,对性能几乎没有影响。
关于ACL功能就介绍到这里,有兴趣的作者可以看官方文档:
https://redis.io/topics/acl



RESP3
RESP 全称 REdis Serialization Protocol,是 Redis 服务端与客户端之间通信的协议。Redis 5 使用的是 RESP2,而 Redis 6 开始在兼容 RESP2 的基础上,开始支持 RESP3。其实一开始作者是打算完全放弃 RESP2的,后来被劝退了。详情见链接(http://antirez.com/news/125)。



Cluster 管理工具
作者分享说redis-trib.rb的功能集成到redis-cli,但这个不是 Redis 5 就已经做了的事情吗?看了一圈,也并没有太大的变化,就增加了一个backup命令。



除了redis-cli,其实另一个工具的优化更让人喜闻乐见,就是redis-benchmark。



$ src/redis-benchmark –help
Usage: redis-benchmark [-h ] [-p ] [-c ] [-n ] [-k ]
(省略部分输出结果……)
--threads Enable multi-thread mode.
--cluster Enable cluster mode.
--enable-tracking Send CLIENT TRACKING on before starting benchmark.
官方benchmark工具总算支持cluster了,通过多线程的方式对多个分片进行压测。



支持 SSL 连接
Amazon 提供的一个功能,在 Redis 6 中 merge 进来。没有提及细节,不清楚对性能有多大影响。



IO多线程
Redis 终于实现多线程了!?先打住,多线程是不可能多线程的,这辈子都不可能多线程(后面这句是我加的)。



作者先简单解释了为什么不能多线程(复杂性、锁的效率等等),然后提到就是有一个事情可以做成多线程,就是针对客户端的这部分。这个操作通过系统调用写操作,将客户端的输入输出缓冲中的数据通过多线程IO与客户端交互。作者说这部分通常能够占到CPU负载的50%,将这部分通过其他线程进行处理,核心流程还是单线程,实现起来也比较简单,性价比超高,所以就做了。
这样能进一步提升单实例的性能,使用4核、8核等来分散写压力,不过再多几个核估计收益比不高了。如果真正想发挥多核性能,还是老路子——Cluster。



Modules API
新的Modules API,让Module功能可以做更多的事情,但作者没有展开讲。



新Expire过期算法
这部分因为涉及到的背景和算法比较多,所以会另外开一个 session 来分享。(视频也可以在YouTube上看了)



Proxy
针对 Cluster 的代理,这么多年了,仍然有不少人在Cluster的接入方式上挣扎,因为缺少合适的驱动而无法使用Cluster。所以开发了这个Proxy功能。作者也强调,虽然这个Proxy 让 Cluster 拥有了像单实例一样的接入方式,但是本质上还是 Cluster,不支持的命令还是不会支持,比如跨 slot 的多Key操作。
其实社区早已有过不少 Proxy 方面的尝试,而且有些做的还不错。那么这个官方的 Proxy 究竟会给我们带来什么惊喜呢?还是让我们拭目以待吧。



Disque
这个本来是作者几年前开发的一个基于 Redis 的消息队列工具,但多年来作者发现 Redis 在持续开发时,他也要持续把新的功能合并到这个Disque 项目里面,这里有大量无用的工作。因此这次他在 Redis 的基础上通过 Modules 功能实现 Disque。



如果业务并不需要保持严格消息的顺序,这个 Disque 能提供足够简单和快速的消息队列功能。



1、合理分配过期时间



不管是将Redis作为缓存,还是存储,如果不愿意看到内存被慢慢消耗殆尽,最后只能扩容或者人工介入,就给自己的key设置一个合理的过期时间。当把Redis作为缓存时,更要预估自己的数据量和数据大小,选择一个合理的过期时间。



2、多个操作使用pinepine



这是Redis使用中的一项基本原则,同时需要知道,另外如果下一个命令的input基于上一个命令的output,就不可以放到一个pipeline里面执行了。



使用时考虑pipeline中一个命令执行失败的场景,后面的命令未执行是否因为一致性带来问题。



3、使用命名空间



方便key的管理,我们开发中常用的Redis-desktop客户端能够按照命名空间对key进行展示,另外,命名空间方便需要对某一类key进行统计和管理。



如果需要通过key进行分片,命名空间可以作为分片参数。



4、选用合适的数据结构



理解每个数据结构的用途,和常用的命令,我曾经见过开发人员因为不知道scard命令可以获得set的size,而将所有的元素取出然后在程序中计算,所以需要平时多查看Redis命令文档;如果能够理解每种数据结构背后的原理,使用时会更加得心应手。



Redis命令文档:



https://redis.io/commands



不建议使用Redis缓存单个数据大小较大的对象,尤其是使用Set,Hash此类数据结构时候,考虑到Redis是单线程,过多的大对象访问增加了网络IO压力,对Redis性能有一定影响,另一方面Redis的虚拟内存page较小,如果内存碎片率较高,则分配/申请内存时在性能上有些影响。如果要缓存较大的对象,可以考虑Memcache。



5、禁用keys



很基本的Redis使用常识,可以通过rename-command来将一些类似的命令重命名,实现disable的效果。



6、选用lua



如果要保证多个操作的原子性,可以选择使用lua脚本。



7、config set parameter value



Redis2.0后提供了config set命令来动态修改一些运行参数而不必重启Redis,目前已经支持动态修改maxmemory,可以通过CONFIGGET*查看支持动态修改的参数列表。



三、最佳实践编辑本段回目录



1、key的命名



合理的命名自己的key,不能在查看数据时可读性更强,也更便于统计和管理。



2、key name的长度



预估key的存活数量,如果key的数量可能达到百万级别,就需要考虑key的名字过长而导致占用太多的存储空间,我在曾经参与过的一个消息系统中使用Redis存储消息阅读量,但是后面由于消息量过多,导致name的占用空间达到几百M,如果精简name,可以节省大量的空间,减少不必要的困扰。例如,保存用户的基本信息可以使用u:${id}。



3、不滥用Lua



由于Redis是单线程,在QPS很高的情况下,过多的lua脚本执行,特别是内部包含较多业务逻辑处理的情况下,会对Redis性能产生很大的影响。曾经参与过的直播业务的生产环境中,我们在Lua脚本中对送礼物触发的的积分和活动信息的有较多的逻辑处理(20行左右),导致Redis负载100%,所以在排查时Lua脚本有可能是负载较高的元凶之一。



4、关注内存和slowlog等统计数据



通过info memory查看内存的分配和使用大小,碎片等情况。



slowlog get N查看最近几条执行较慢的命令。



通过Redis-cli–bigkeys通过采样scan元素较多的key,不会一直阻塞Redis执行。



更多好玩的Redis-cli命令可以查看:



https://redis.io/topics/rediscli



注:monitor命令不建议生产环境使用



edis Stream狠狠地借鉴了Kafka的设计。



Redis Stream的结构如上图所示,它有一个消息链表,将所有加入的消息都串起来,每个消息都有一个唯一的ID和对应的内容。消息是持久化的,Redis重启后,内容还在。



每个Stream都有唯一的名称,它就是Redis的key,在我们首次使用xadd指令追加消息时自动创建。



每个Stream都可以挂多个消费组,每个消费组会有个游标last_delivered_id在Stream数组之上往前移动,表示当前消费组已经消费到哪条消息了。每个消费组都有一个Stream内唯一的名称,消费组不会自动创建,它需要单独的指令xgroup create进行创建,需要指定从Stream的某个消息ID开始消费,这个ID用来初始化last_delivered_id变量。



每个消费组(Consumer Group)的状态都是独立的,相互不受影响。也就是说同一份Stream内部的消息会被每个消费组都消费到。



同一个消费组(Consumer Group)可以挂接多个消费者(Consumer),这些消费者之间是竞争关系,任意一个消费者读取了消息都会使游标last_delivered_id往前移动。每个消费者者有一个组内唯一名称。



消费者(Consumer)内部会有个状态变量pending_ids,它记录了当前已经被客户端读取的消息,但是还没有ack。如果客户端没有ack,这个变量里面的消息ID会越来越多,一旦某个消息被ack,它就开始减少。这个pending_ids变量在Redis官方被称之为PEL,也就是Pending Entries List,这是一个很核心的数据结构,它用来确保客户端至少消费了消息一次,而不会在网络传输的中途丢失了没处理。



消息ID
消息ID的形式是timestampInMillis-sequence,例如1527846880572-5,它表示当前的消息在毫米时间戳1527846880572时产生,并且是该毫秒内产生的第5条消息。消息ID可以由服务器自动生成,也可以由客户端自己指定,但是形式必须是整数-整数,而且必须是后面加入的消息的ID要大于前面的消息ID。



消息内容
消息内容就是键值对,形如hash结构的键值对,这没什么特别之处。



增删改查
xadd 追加消息



xdel 删除消息,这里的删除仅仅是设置了标志位,不影响消息总长度



xrange 获取消息列表,会自动过滤已经删除的消息



xlen 消息长度



del 删除Stream



独立消费
我们可以在不定义消费组的情况下进行Stream消息的独立消费,当Stream没有新消息时,甚至可以阻塞等待。Redis设计了一个单独的消费指令xread,可以将Stream当成普通的消息队列(list)来使用。使用xread时,我们可以完全忽略消费组(Consumer Group)的存在,就好比Stream就是一个普通的列表(list)。



客户端如果想要使用xread进行顺序消费,一定要记住当前消费到哪里了,也就是返回的消息ID。下次继续调用xread时,将上次返回的最后一个消息ID作为参数传递进去,就可以继续消费后续的消息。



block 0表示永远阻塞,直到消息到来,block 1000表示阻塞1s,如果1s内没有任何消息到来,就返回nil
创建消费组
Stream通过xgroup create指令创建消费组(Consumer Group),需要传递起始消息ID参数用来初始化last_delivered_id变量。



127.0.0.1:6379> xgroup create codehole cg1 0-0  #  表示从头开始消费



消费
Stream提供了xreadgroup指令可以进行消费组的组内消费,需要提供消费组名称、消费者名称和起始消息ID。它同xread一样,也可以阻塞等待新消息。读到新消息后,对应的消息ID就会进入消费者的PEL(正在处理的消息)结构里,客户端处理完毕后使用xack指令通知服务器,本条消息已经处理完毕,该消息ID就会从PEL中移除。



Stream消息太多怎么办
读者很容易想到,要是消息积累太多,Stream的链表岂不是很长,内容会不会爆掉就是个问题了。xdel指令又不会删除消息,它只是给消息做了个标志位。



Redis自然考虑到了这一点,所以它提供了一个定长Stream功能。在xadd的指令提供一个定长长度maxlen,就可以将老的消息干掉,确保最多不超过指定长度。
消息如果忘记ACK会怎样
Stream在每个消费者结构中保存了正在处理中的消息ID列表PEL,如果消费者收到了消息处理完了但是没有回复ack,就会导致PEL列表不断增长,如果有很多消费组的话,那么这个PEL占用的内存就会放大



PEL如何避免消息丢失
在客户端消费者读取Stream消息时,Redis服务器将消息回复给客户端的过程中,客户端突然断开了连接,消息就丢失了。但是PEL里已经保存了发出去的消息ID。待客户端重新连上之后,可以再次收到PEL中的消息ID列表。不过此时xreadgroup的起始消息ID不能为参数>,而必须是任意有效的消息ID,一般将参数设为0-0,表示读取所有的PEL消息以及自last_delivered_id之后的新消息。



.新增加的Stream(流)数据类型,这样redis就有了6大数据类型,另外五种是String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sorted set有序集合)。它弥补了其它5种数据类型不能实现的功能,比如List数据类型只能先进先出,或者后进先出,不能从中间去数据,但是Stream可以实现。



  1)创建Stream



    命令:XADD



    用法:XADD key ID field string (id可以自定义)


Category storage