1. Redis数据类型概述
Redis 常见的数据类型有10 种,其中包括 5 种基本数据类型:Strings(字符串)
、Lists(列表)
、Sets(集合)
、Hashes(散列)
、Sorted sets(Zset有序集合)
。除了基本数据类型外,还有 5 种常用数据类型:Bitmaps
、HyperLogLog
、Geospatial indexes
、Bitfields
、Streams
。
除了上述 10 种外,还有一部分扩展的数据类型 :例如 JSON
、time series
、Probabilistic
等
2. 五种基本数据类型
5 种基本数据类型是直接提供给用户使用的,其底层实现主要依赖这 8 种数据结构:简单动态字符串(SDS)、LinkedList(双向链表)、Hash Table(哈希表)、SkipList(跳跃表)、Intset(整数集合)、ZipList(压缩列表)、QuickList(快速列表)。
Redis 3.2 之前,List 底层实现是 LinkedList 或者 ZipList。 Redis 3.2 之后,引入了 LinkedList 和 ZipList 的结合 QuickList,List 的底层实现变为 QuickList。
类型 | 数据结构 | 常用命令 | 应用场景 |
---|---|---|---|
Strings(字符串) | SDS | SET、SETNX、GET、MSET、INCR | 需要存储常规数据的场景、需要计数的场景、分布式锁 |
Lists(列表) | LinkedList/ZipList/QuickList | RPUSH、LPUSH、LSET 、LPOP、RPOP、LLEN、LRANGE | 最新文章、最新动态、消息队列 |
Hashes(散列) | Hash Table、ZipList | HSET、HSETNX、HMSET、HGET、HGETALL 、HINCRBY | 对象数据存储场景:用户信息、商品信息、文章信息、购物车信息。 |
Sets(集合) | ZipList、Intset | SADD 、SMEMBERS、SCARD、SISMEMBER、SINTER | 需要存放的数据不能重复的:文章点赞、动态点赞等场景 |
Sorted sets(Zset有序集合) | ZipList、SkipList | ZADD 、ZCARD、ZRANGE、ZREVRANK | 排行榜 |
3. 其他五种常用类型
类型 | 底层实现 | 常用命令 | 应用场景 | 发布版本 |
---|---|---|---|---|
Bitmaps (位图) | String | SETBIT、GETBIT、BITCOUNT、BITOP | 需要保存状态信息的场景:用户签到情况、活跃用户情况、用户行为统计 布隆过滤器 | 2.2.0 |
HyperLogLog (基数统计) | String HyperLogLog算法 | PFADD 、PFCOUNT、PFMERGE | 热门网站每日/每周/每月访问 ip 数统计、热门帖子 uv 统计 | 2.8.9 |
Geospatial indexes (地理空间) | Sorted Set | GEOADD、GEOPOS、GEODIST 、GEORADIUS 、GEORADIUSBYMEMBER | 需要管理使用地理空间数据的场景:附近的人、附近的餐馆 | 3.2.0 |
Bitfields (位域) | String | BITFIELD kkk SET u32 #0 1000 、BITFIELD kkk INCRBY u32 #0 -999 | 游戏记录打怪数量、金币数量 | 3.2.0 |
Streams (流) | 日志追加(append-only log)的数据结构 | XADD 、XACK 、XGROUP 、XREADGROUP 、XDEL | 实时数据处理、消息队列和发布订阅 | 5.0.0 |
4. 基本使用
4.1 Strings(字符串)
Strings 是一种二进制安全的数据结构,可以用来存储任何类型的数据比如字符串、整数、浮点数、图片(图片的 base64 编码或者解码或者图片的路径)、序列化后的对象。 Redis 并没有使用 C 的字符串表示,而是自己构建了一种 简单动态字符串(Simple Dynamic String,SDS)。相比于 C 的原生字符串,Redis 的 SDS 不光可以保存文本数据还可以保存二进制数据,并且获取字符串长度复杂度为 O(1)(C 字符串为 O(N)),除此之外,Redis 的 SDS API 是安全的,不会造成缓冲区溢出。
4.1.1 常用命令
命令 | 介绍 | 命令行使用 | RedisTemplate使用 |
---|---|---|---|
SET key value | 存储指定 key 的值 | set aaa 123 | redisTemplate.opsForValue().set(“aaa”, “123”); |
SETNX key value | 只有在 key 不存在时设置 key 的值 | setnx aaa 456 | redisTemplate.opsForValue().setIfAbsent(“aaa”, “456”); |
GET key | 获取指定 key 的值 | get aaa | redisTemplate.opsForValue().get(“aaa”); |
MSET key1 value1 key2 value2… | 设置一个或多个指定 key 的值 | mset aaa 111 bbb 222 | redisTemplate.opsForValue().multiSet(); |
MGET key1 key2 … | 获取一个或多个指定 key 的值 | mget aaa bbb | redisTemplate.opsForValue().multiGet(); |
STRLEN key | 返回 key 所储存的字符串值的长度 | strlen aaa | redisTemplate.opsForValue().size(“aaa”); |
INCR key | 将 key 中储存的数字值增 1 | incr aaa | redisTemplate.opsForValue().increment(“aaa”); |
DECR key | 将 key 中储存的数字值减 1 | decr aaa | redisTemplate.opsForValue().decrement(“aaa”); |
EXISTS key | 判断指定 key 是否存在 | exists aaa | redisTemplate.hasKey(“aaa”); |
DEL key(通用) | 删除指定的 key | del aaa | redisTemplate.delete(“aaa”); |
EXPIRE key seconds(通用) | 给指定 key 设置过期时间 | expire aaa 20 | redisTemplate.expire(“aaa”, 200L, TimeUnit.SECONDS); |
4.1.2 应用场景
- 需要存储常规数据的场景:缓存 session、token、图片地址、序列化后的对象(相比较于 Hash 存储更节省内存)。
- 需要计数的场景:用户单位时间的请求数(简单限流可以用到)、页面单位时间的访问数。
- 分布式锁:利用 SETNX key value 命令可以实现一个最简易的分布式锁
4.2 List(列表)
Redis 中的 List 其实就是链表数据结构的实现。许多高级编程语言都内置了链表的实现比如 Java 中的 LinkedList,但是 C 语言并没有实现链表,所以 Redis 实现了自己的链表数据结构。Redis 的 List 的实现为一个 双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。
4.2.1 常用命令
命令 | 介绍 | 命令行使用 | RedisTemplate使用 |
---|---|---|---|
RPUSH key value1 value2 … | 在指定列表的尾部(右边)添加一个或多个元素 | rpush myList a b c | redisTemplate.opsForList().rightPushAll(“myList”, “a”, “b”, “c”); |
LPUSH key value1 value2 … | 在指定列表的头部(左边)添加一个或多个元素 | lpush myList 1 2 3 | redisTemplate.opsForList().leftPushAll(“myList”, “1”, “2”, “3”); |
LSET key index value | 将指定列表索引 index 位置的值设置为 value | lset myList 2 hh | redisTemplate.opsForList().set(“myList”, 2, “hh”); |
LPOP key | 移除并获取指定列表的第一个元素(最左边) | lpop myList | redisTemplate.opsForList().leftPop(“myList”); |
RPOP key | 移除并获取指定列表的最后一个元素(最右边) | rpop myList | redisTemplate.opsForList().rightPop(“myList”); |
LLEN key | 获取列表元素数量 | llen myList | redisTemplate.opsForList().size(“myList”); |
LRANGE key start end | 获取列表 start 和 end 之间 的元素 | lrange myList 0 -1 | redisTemplate.opsForList().range(“myList”, 0, -1); |
4.2.2 应用场景
- 信息流展示:最新文章、最新动态。
4.3 Hashes(散列)
Redis 中的 Hash 是一个 String 类型的 field-value(键值对) 的映射表,特别适合用于存储对象,后续操作的时候,可以直接修改这个对象中的某些字段的值。Hash 类似于 JDK1.8 前的 HashMap,内部实现也差不多(数组 + 链表)。不过,Redis 的 Hash 做了更多优化。
4.3.1 常用命令
命令 | 介绍 | 命令行使用 | RedisTemplate使用 |
---|---|---|---|
HSET key field value | 设置指定哈希表中指定字段的值(现在同HMSET) | hset myUser name aaa tel 199 | HashMap<String, String> myUserMap = new HashMap<>(); myUserMap.put(“name”, “aaa”);myUserMap.put(“tel”, “199”);redisTemplate.opsForHash().putAll(“myUser”, myUserMap); |
HSETNX key field value | 只有指定字段不存在时设置指定字段的值 | hsetnx myUser age 20 | redisTemplate.opsForHash().putIfAbsent(“myUser”, “age”, 20); |
HGET key field | 获取指定哈希表中指定字段的值 | hget myUser name | redisTemplate.opsForHash().get(“myUser”, “name”); |
HMGET key field1 field2 | 获取指定哈希表中一个或者多个指定字段的值 | hmget myUser age name | redisTemplate.opsForHash().multiGet(“myUser”, Arrays.asList(“name”, “age”)); |
HGETALL key | 获取指定哈希表中所有的键值对 | hgetall myUser | redisTemplate.opsForHash().entries(“myUser”); |
HEXISTS key field | 查看指定哈希表中指定的字段是否存在 | HEXISTS myUser name | redisTemplate.opsForHash().hasKey(“myUser”,“name”); |
HDEL key field1 field2 | 删除一个或多个哈希表字段 | hdel myUser age | redisTemplate.opsForHash().delete(“myUser”,“name”); |
HLEN key | 获取指定哈希表中字段的数量 | hlen myUser | redisTemplate.opsForHash().size(“myUser”); |
HINCRBY key field increment | 对指定哈希中的指定字段做运算操作正数为加,负数为减 | hincrby myUser tel -10 | redisTemplate.opsForHash().increment(“myUser”, “tel”, -10L); |
4.3.2 应用场景
对象数据存储场景:用户信息、商品信息、文章信息、购物车信息。
4.4 Sets(集合)
Redis 中的 Set 类型是一种无序集合,集合中的元素没有先后顺序但都唯一,有点类似于 Java 中的 HashSet 。当你需要存储一个列表数据,又不希望出现重复数据时,Set 是一个很好的选择,并且 Set 提供了判断某个元素是否在一个 Set 集合内的重要接口,这个也是 List 所不能提供的。
可以基于 Set 轻易实现交集、并集、差集的操作,比如你可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。这样的话,Set 可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程。
4.4.1 常用命令
命令 | 介绍 | 命令行使用 | RedisTemplate使用 |
---|---|---|---|
SADD key member1 member2 | 向指定集合添加一个或多个元素 | sadd mySet a b c | redisTemplate.opsForSet().add(“mySet”, “a”, “b”, “c”); |
SMEMBERS key | 获取指定集合中的所有元素 | smembers mySet | redisTemplate.opsForSet().members(“mySet”); |
SCARD key | 获取指定集合的元素数量 | scard mySet | redisTemplate.opsForSet().size(“mySet”); |
SISMEMBER key member | 判断指定元素是否在指定集合中 | sismember mySet a | redisTemplate.opsForSet().isMember(“mySet”,“a”); |
SINTER key1 key2 | 获取给定所有集合的交集 | sinter mySet mySet2 | redisTemplate.opsForSet().intersect(“mySet”, “mySet2”); |
SINTERSTORE destination key1 key2 | 将给定所有集合的交集存储在 destination 中 | sinterstore inSet mySet mySet2 | redisTemplate.opsForSet().intersectAndStore(“mySet”, “mySet2”, “inSet”); |
SUNION key1 key2 | 获取给定所有集合的并集 | sunion mySet mySet2 | redisTemplate.opsForSet().union(“mySet”, “mySet2”); |
SUNIONSTORE destination key1 key2 | 将给定所有集合的并集存储在 destination 中 | sunionstore unionSet mySet mySet2 | redisTemplate.opsForSet().unionAndStore(“mySet”, “mySet2”,“unionSet”); |
SDIFF key1 key2 | 获取给定所有集合的差集key1-key2 | sdiff mySet mySet2 | redisTemplate.opsForSet().difference(“mySet”,“mySet2”); |
SDIFFSTORE destination key1 key2 | 将给定所有集合的差集存储在 destination 中 | sdiffstore diffSet mySet mySet2 | redisTemplate.opsForSet().differenceAndStore(“mySet”, “mySet2”, “diffSet”); |
SPOP key count | 随机移除并获取指定集合中一个或多个元素 | spop mySet2 2 | redisTemplate.opsForSet().pop(“mySet2”,2); |
SRANDMEMBER key count | 随机获取指定集合中指定数量的元素 | srandmember mySet 2 | redisTemplate.opsForSet().randomMembers(“mySet”,2); |
4.4.2 应用场景
- 需要存放的数据不能重复的场景:文章点赞、动态点赞
- 需要获取多个数据源交集、并集和差集的场景:共同好友(交集)、共同粉丝(交集)、共同关注(交集)、好友推荐(差集)、音乐推荐(差集)、订阅号推荐(差集+交集)
- 需要随机获取数据源中的元素的场景:抽奖系统、随机点名
4.5 Sorted Sets(有序集合)
Sorted Set 又称为Zset 类似于 Set,但和 Set 相比,Sorted Set 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,还可以通过 score 的范围来获取元素的列表。有点像是 Java 中 HashMap 和 TreeSet 的结合体。
4.5.1 常用命令
命令 | 介绍 | 命令行使用 | RedisTemplate使用 |
---|---|---|---|
ZADD key score1 member1 score2 member2 | 向指定有序集合添加一个或多个元素 | zadd myZSet 100 a 88 b 56 c | Set<ZSetOperations.TypedTuple> tuples = new HashSet<>();tuples.add(new DefaultTypedTuple<>(“a”, 100.0)); tuples.add(new DefaultTypedTuple<>(“b”, 88.0));tuples.add(new DefaultTypedTuple<>(“c”, 56.0)); redisTemplate.opsForZSet().add(“myZSet”, tuples); |
ZCARD KEY | 获取指定有序集合的元素数量 | zcard myZSet | redisTemplate.opsForZSet().size(“myZSet”); |
ZSCORE key member | 获取指定有序集合中指定元素的 score 值 | zscore myZSet a | redisTemplate.opsForZSet().score(“myZSet”, “a”); |
ZINTERSTORE destination numkeys key1 key2 | 将给定所有有序集合的交集存储在 destination 中,对相同元素对应的 score 值进行 SUM 聚合操作,numkeys 为集合数量 | zinterstore zong 2 yuwen shuxue | redisTemplate.opsForZSet().intersectAndStore(“yuwen”, “shuxue”, “zong”); |
ZUNIONSTORE destination numkeys key1 key2 | 求并集,其它和 ZINTERSTORE 类似 | zunionstore bing 2 yuwen shuxue | redisTemplate.opsForZSet().unionAndStore(“yuwen”, “shuxue”, “bing”); |
ZDIFFSTORE destination numkeys key1 key2 | 求差集,其它和 ZINTERSTORE 类似 | zdiffstore diff 2 yuwen shuxue | redisTemplate.opsForZSet().differenceAndStore(“yuwen”, Collections.singletonList(“shuxue”),"diff "); |
ZRANGE key start end | 获取指定有序集合 start 和 end 之间的元素(score 从低到高) | zrange myZSet 0 -1 | redisTemplate.opsForZSet().range(“myZSet”, 0, -1); |
ZREVRANGE key start end | 获取指定有序集合 start 和 end 之间的元素(score 从高到底 | zrevrange myZSet 0 -1 | redisTemplate.opsForZSet().reverseRange(“myZSet”, 0, -1); |
ZREVRANK key member | 获取指定有序集合中指定元素的排名(score 从大到小排序) | zrevrank myZSet a | redisTemplate.opsForZSet().reverseRank(“myZSet”, “a”); |
4.5.2 应用场景
- 需要随机获取数据源中的元素根据某个权重进行排序的场景:各种排行榜比如直播间送礼物的排行榜、朋友圈的微信步数排行榜、游戏中的段位排行榜、话题热度排行榜
4.6 Bitmaps(位图)
Bitmap 存储的是连续的二进制数字(0 和 1),通过 Bitmap, 只需要一个 bit 位来表示某个元素对应的值或者状态,key 就是对应元素本身 。我们知道 8 个 bit 可以组成一个 byte,所以 Bitmap 本身会极大的节省储存空间。
你可以将 Bitmap 看作是一个存储二进制数字(0 和 1)的数组,数组中每个元素的下标叫做 offset(偏移量)。
4.6.1 常用命令
命令 | 介绍 | 命令行使用 | RedisTemplate使用 |
---|---|---|---|
SETBIT key offset value | 设置指定 offset 位置的值 | setbit myBit 1000 1 | redisTemplate.opsForValue().setBit(“myBit”, 1000L, true); |
GETBIT key offset | 获取指定 offset 位置的值 | getbit myBit 1000 | redisTemplate.opsForValue().getBit(“myBit”,1000L); |
BITCOUNT key start end | 获取 start 和 end 之间值为 1 的元素个数 | bitcount myBit 3 0 | redisTemplate.execute((RedisCallback) connection -> connection.bitCount(“myBit”.getBytes(), 3L, 1L)); |
BITOP operation destkey key1 key2 | 对一个或多个 Bitmap 进行运算,可用运算符有 AND, OR, XOR 以及 NOT | bitop or dest a b | redisTemplate.execute((RedisCallback) connection -> connection.bitOp(RedisStringCommands.BitOperation.OR, new byte[10], “myBit”.getBytes())); |
4.6.2 应用场景
- 需要保存状态信息(0/1 即可表示)的场景:用户签到情况、活跃用户情况、用户行为统计(比如是否点赞过某个视频)。
4.7 HyperLogLog(基数统计)
HyperLogLog (简称HLL) 是一种有名的基数计数概率算法 ,基于 LogLog Counting(LLC)优化改进得来,并不是 Redis 特有的,Redis 只是实现了这个算法并提供了一些开箱即用的 API。Redis 提供的 HyperLogLog 占用空间非常非常小,只需要 12k 的空间就能存储接近2^64个不同元素。
Redis 对 HyperLogLog 的存储结构做了优化,采用两种方式计数:
稀疏矩阵:计数较少的时候,占用空间很小。
稠密矩阵:计数达到某个阈值的时候,占用 12k 的空间。
4.7.1 常用命令
命令 | 介绍 | 命令行使用 | RedisTemplate使用 |
---|---|---|---|
PFADD key element1 element2 | 添加一个或多个元素到 HyperLogLog 中 | pfadd myhll a b c | redisTemplate.opsForHyperLogLog().add(“myhll”, “a”, “b”, “c”); |
PFCOUNT key1 key2 | 获取一个或者多个 HyperLogLog 的唯一计数 | pfcount myhll myhll2 | redisTemplate.opsForHyperLogLog().size(“myhll”,“myhll2”); |
PFMERGE destkey sourcekey1 sourcekey2 | 将多个 HyperLogLog 合并到 destkey 中,destkey 会结合多个源,算出对应的唯一计数 | pfmerge desthll myhll myhll2 | redisTemplate.opsForHyperLogLog().union(“desthll”, “myhll”, “myhll2”); |
4.7.2 应用场景
数量量巨大(百万、千万级别以上)的计数场景:热门网站每日/每周/每月访问 ip 数统计、热门帖子 uv 统计
4.8 Geospatial indexes (地理空间)
Geospatial indexes (地理空间索引,简称 GEO) 主要用于存储地理位置信息,基于 Sorted Set 实现。通过 GEO 我们可以轻松实现两个位置距离的计算、获取指定位置附近的元素等功能。
4.8.1 常用命令
命令 | 介绍 | 命令行使用 | RedisTemplate使用 |
---|---|---|---|
GEOADD key longitude1 latitude1 member1 | 添加一个或多个元素对应的经纬度信息到 GEO 中 | geoadd myGeo 116.404844 39.923678 user1 116.479152 39.938174 user2 | Map<String, Point> map = new HashMap<>(); map.put(“user1”, new Point(new Point(116.404844, 39.923678))); map.put(“user2”, new Point(new Point(116.479152, 39.938174))); redisTemplate.opsForGeo().add(“myGeo”, map); |
GEODIST key member1 member2 M/KM/FT/MI | 返回两个给定元素之间的距离 | geodist myGeo user1 user2 km | redisTemplate.opsForGeo().distance(“myGeo”,“user1”,“user2”, Metrics.KILOMETERS); |
GEORADIUS key longitude latitude radius distance | 获取指定位置附近 distance 范围内的其他元素,支持 ASC(由近到远)、DESC(由远到近)、Count(数量) 等参数 | georadius myGeo 116.419504 39.929654 3 km | Circle circle = new Circle(new Point(116.419504, 39.929654), new Distance(3, Metrics.KILOMETERS)); redisTemplate.opsForGeo().radius(“myGeo”, circle); |
4.8.2 应用场景
需要管理使用地理空间数据的场景:附近的人
4.9 Bitfields(位域)
Bitfields 可以设置、增加和获取任意位长度的整数值。可以对从无符号1位整数到有符号63位整数的任何值进行运算。
这些值是使用二进制编码的 Redis String 存储的。位域支持原子读、写和增量操作,是管理计数器和类似数值的好选择。
4.9.1 应用场景和命令
假设英雄联盟中,需要为每个玩家维护两个关键指标:金币总量和补刀数量,这些计数器的宽度应至少为 32 位。
可以用每个玩家的一个位字段来表示这些计数器,使用偏移量 0记录金币 1记录补刀数 。
新玩家以 1000 金币开始(计数器偏移量0 )
bitfield player1 set u32 #0 1000
BitFieldSubCommands bitFieldSubCommands =BitFieldSubCommands.create();
bitFieldSubCommands .set(BitFieldSubCommands.BitFieldType.unsigned(32)).valueAt(0).to(1000);
redisTemplate.opsForValue().bitField("player1", bitFieldSubCommands);
补了一个小兵后,获得的 50 金币并让补刀数增加1(偏移量 1)。
bitfield player1 incrby u32 #0 50 incrby u32 #1 1
BitFieldSubCommands bitFieldSubCommands = BitFieldSubCommands.create();
bitFieldSubCommands.incr(BitFieldSubCommands.BitFieldType.unsigned(32)).valueAt(0).by(50);
bitFieldSubCommands.incr(BitFieldSubCommands.BitFieldType.unsigned(32)).valueAt(1).by(1);
redisTemplate.opsForValue().bitField("player1", bitFieldSubCommands);
买了一把暴风大剑 999 金币
bitfield player1 incrby u32 #0 -999
bitFieldSubCommands.incr(BitFieldSubCommands.BitFieldType.unsigned(32)).valueAt(0).by(-999);
redisTemplate.opsForValue().bitField("player1", bitFieldSubCommands);
读取玩家的统计数据
bitfield player1 get u32 #0 get u32 #1
bitFieldSubCommands.get(BitFieldSubCommands.BitFieldType.unsigned(32)).valueAt(0)
.get(BitFieldSubCommands.BitFieldType.unsigned(32)).valueAt(1);
redisTemplate.opsForValue().bitField("player1", bitFieldSubCommands);
4.10 Streams(流)
Stream 是 Redis 5.0 版本中新增的一种数据结构,它是一个高性能、持久化的消息队列,可以用于实现消息的发布和订阅。Stream 可以看作是一个有序的消息队列,每个消息都有一个唯一的 ID,可以根据 ID 进行消息的查找、删除和确认。在 Stream 中,消息以键值对的形式存储,可以存储任意类型的数据。Stream 还支持多个消费者组,每个消费者组可以独立消费消息,避免消息重复消费。Stream 的引入使得 Redis 在消息队列领域更具竞争力,同时也为开发者提供了一种高效、可靠的消息处理方式。
4.10.1 常用命令
消息队列相关命令
命令 | 说明 |
---|---|
XADD | 添加消息至队列末尾 |
XTRIM | 对流进行修剪,限制长度 |
XDEL | 删除消息 |
XLEN | 获取流包含的元素数量,即消息长度 |
XRANGE | 获取消息列表,会自动过滤已经删除的消息 |
XREAD | 以阻塞或非阻塞方式获取消息列表 |
消费者组相关命令
命令 | 说明 |
---|---|
XGROUP CREATE | 创建消费者组 |
XREADGROUP GROUP | 读取消费者组中的消息 |
XACK | 将消息标记为"已处理" |
XGROUP SETID | 为消费者组设置新的最后递送消息ID |
XGROUP DELCONSUMER | 删除消费者 |
XGROUP DESTROY | 删除消费者组 |
XPENDING | 显示待处理消息的相关信息 |
XCLAIM | 转移消息的归属权 |
XINFO | 查看流和消费者组的相关信息 |
XINFO GROUPS | 打印消费者组的信息 |
XINFO STREAM | 打印流信息 |
Streams 懒得继续写了,以后应用到再补充吧