有创意的个人网站,做空eth网站,outlook企业邮箱注册,石排镇网站建设大家好#xff0c;我是网管#xff0c;今天分享一下Redis的数据类型都能用来实现哪些产品功能。 我们都知道 Redis 提供了丰富的数据类型#xff0c;常见的有五种#xff1a;String#xff08;字符串#xff09;#xff0c;Hash#xff08;哈希#xff09;#xff0c… 大家好我是网管今天分享一下Redis的数据类型都能用来实现哪些产品功能。 我们都知道 Redis 提供了丰富的数据类型常见的有五种String字符串Hash哈希List列表Set集合、Zset有序集合。 随着 Redis 版本的更新后面又支持了四种数据类型BitMap2.2 版新增、HyperLogLog2.8 版新增、GEO3.2 版新增、Stream5.0 版新增。 每种数据对象都各自的应用场景你能说出它们各自的应用场景吗 面试过程中这个问题也很常被问到又比如会举例一个应用场景来问你让你说使用哪种 Redis 数据类型来实现。 所以这次我们就来学习 Redis 数据类型的使用以及应用场景。篇幅比较长大家收藏慢慢看。 String 介绍 String 是最基本的 key-value 结构key 是唯一标识value 是具体的值value其实不仅是字符串 也可以是数字整数或浮点数value 最多可以容纳的数据长度是 512M。 内部实现 String 类型的底层的数据结构实现主要是 int 和 SDS简单动态字符串。 SDS 和我们认识的 C 字符串不太一样之所以没有使用 C 语言的字符串表示因为 SDS 相比于 C 的原生字符串 SDS 不仅可以保存文本数据还可以保存二进制数据。因为 SDS 使用 len 属性的值而不是空字符来判断字符串是否结束并且 SDS 的所有 API 都会以处理二进制的方式来处理 SDS 存放在 buf[] 数组里的数据。所以 SDS 不光能存放文本数据而且能保存图片、音频、视频、压缩文件这样的二进制数据。**SDS 获取字符串长度的时间复杂度是 O(1)**。因为 C 语言的字符串并不记录自身长度所以获取长度的复杂度为 O(n)而 SDS 结构里用 len 属性记录了字符串长度所以复杂度为 O(1)。Redis 的 SDS API 是安全的拼接字符串不会造成缓冲区溢出。因为 SDS 在拼接字符串之前会检查 SDS 空间是否满足要求如果空间不够会自动扩容所以不会导致缓冲区溢出的问题。 字符串对象的内部编码encoding有 3 种 int、raw和 embstr。 如果一个字符串对象保存的是整数值并且这个整数值可以用long类型来表示那么字符串对象会将整数值保存在字符串对象结构的ptr属性里面将void*转换成 long并将字符串对象的编码设置为int。 如果字符串对象保存的是一个字符串并且这个字符申的长度小于等于 32 字节那么字符串对象将使用一个简单动态字符串SDS来保存这个字符串并将对象的编码设置为embstr embstr编码是专门用于保存短字符串的一种优化编码方式 如果字符串对象保存的是一个字符串并且这个字符串的长度大于 32 字节那么字符串对象将使用一个简单动态字符串SDS来保存这个字符串并将对象的编码设置为raw 可以看到embstr和raw编码都会使用SDS来保存值但不同之处在于embstr会通过一次内存分配函数来分配一块连续的内存空间来保存redisObject和SDS而raw编码会通过调用两次内存分配函数来分别分配两块空间来保存redisObject和SDS。Redis这样做会有很多好处 embstr编码将创建字符串对象所需的内存分配次数从 raw 编码的两次降低为一次释放 embstr编码的字符串对象同样只需要调用一次内存释放函数因为embstr编码的字符串对象的所有数据都保存在一块连续的内存里面可以更好的利用 CPU 缓存提升性能。 但是 embstr 也有缺点的 如果字符串的长度增加需要重新分配内存时整个redisObject和sds都需要重新分配空间所以embstr编码的字符串对象实际上是只读的redis没有为embstr编码的字符串对象编写任何相应的修改程序。当我们对embstr编码的字符串对象执行任何修改命令例如append时程序会先将对象的编码从embstr转换成raw然后再执行修改命令。 常用指令 普通字符串的基本操作 # 设置 key-value 类型的值SET name lin
OK
# 根据 key 获得对应的 valueGET name
lin
# 判断某个 key 是否存在EXISTS name
(integer) 1
# 返回 key 所储存的字符串值的长度STRLEN name
(integer) 3
# 删除某个 key 对应的值DEL name
(integer) 1 批量设置 : # 批量设置 key-value 类型的值MSET key1 value1 key2 value2
OK
# 批量获取多个 key 对应的 valueMGET key1 key2
1) value1
2) value2 计数器字符串的内容为整数的时候可以使用 # 设置 key-value 类型的值SET number 0
OK
# 将 key 中储存的数字值增一INCR number
(integer) 1
# 将key中存储的数字值加 10INCRBY number 10
(integer) 11
# 将 key 中储存的数字值减一DECR number
(integer) 10
# 将key中存储的数字值键 10DECRBY number 10
(integer) 0 过期默认为永不过期 # 设置 key 在 60 秒后过期该方法是针对已经存在的key设置过期时间EXPIRE name 60
(integer) 1
# 查看数据还有多久过期TTL name
(integer) 51#设置 key-value 类型的值并设置该key的过期时间为 60 秒SET key value EX 60
OKSETEX key 60 value
OK 不存在就插入 # 不存在就插入not exists
SETNX key value
(integer) 1 应用场景 缓存对象 使用 String 来缓存对象有两种方式 直接缓存整个对象的 JSON命令例子SET user:1 {name:xiaolin, age:18}。采用将 key 进行分离为 user:ID:属性采用 MSET 存储用 MGET 获取各属性值命令例子MSET user:1:name xiaolin user:1:age 18 user:2:name xiaomei user:2:age 20。 常规计数 因为 Redis 处理命令是单线程所以执行命令的过程是原子的。因此 String 数据类型适合计数场景比如计算访问次数、点赞、转发、库存数量等等。 比如计算文章的阅读量 # 初始化文章的阅读量SET aritcle:readcount:1001 0
OK
#阅读量1INCR aritcle:readcount:1001
(integer) 1
#阅读量1INCR aritcle:readcount:1001
(integer) 2
#阅读量1INCR aritcle:readcount:1001
(integer) 3
# 获取对应文章的阅读量GET aritcle:readcount:1001
3 分布式锁 SET 命令有个 NX 参数可以实现「key不存在才插入」可以用它来实现分布式锁 如果 key 不存在则显示插入成功可以用来表示加锁成功如果 key 存在则会显示插入失败可以用来表示加锁失败。 一般而言还会对分布式锁加上过期时间分布式锁的命令如下 SET lock_key unique_value NX PX 10000 lock_key 就是 key 键unique_value 是客户端生成的唯一的标识NX 代表只在 lock_key 不存在时才对 lock_key 进行设置操作PX 10000 表示设置 lock_key 的过期时间为 10s这是为了避免客户端发生异常而无法释放锁。 而解锁的过程就是将 lock_key 键删除但不能乱删要保证执行操作的客户端就是加锁的客户端。所以解锁的时候我们要先判断锁的 unique_value 是否为加锁客户端是的话才将 lock_key 键删除。 可以看到解锁是有两个操作这时就需要 Lua 脚本来保证解锁的原子性因为 Redis 在执行 Lua 脚本时可以以原子性的方式执行保证了锁释放操作的原子性。 // 释放锁时先比较 unique_value 是否相等避免锁的误释放
if redis.call(get,KEYS[1]) ARGV[1] thenreturn redis.call(del,KEYS[1])
elsereturn 0
end 这样一来就通过使用 SET 命令和 Lua 脚本在 Redis 单节点上完成了分布式锁的加锁和解锁。 List 介绍 List 列表是简单的字符串列表按照插入顺序排序可以从头部或尾部向 List 列表添加元素。 列表的最大长度为 2^32 - 1也即每个列表支持超过 40 亿个元素。 内部实现 List 类型的底层数据结构是由双向链表或压缩列表实现的 如果列表的元素个数小于 512 个默认值可由 list-max-ziplist-entries 配置列表每个元素的值都小于 64 字节默认值可由 list-max-ziplist-value 配置Redis 会使用压缩列表作为 List 类型的底层数据结构如果列表的元素不满足上面的条件Redis 会使用双向链表作为 List 类型的底层数据结构 但是在 Redis 3.2 版本之后List 数据类型底层数据结构就只由 quicklist 实现了替代了双向链表和压缩列表。 常用命令 # 将一个或多个值value插入到key列表的表头(最左边)最后的值在最前面
LPUSH key value [value ...]
# 将一个或多个值value插入到key列表的表尾(最右边)
RPUSH key value [value ...]
# 移除并返回key列表的头元素
LPOP key
# 移除并返回key列表的尾元素
RPOP key # 返回列表key中指定区间内的元素区间以偏移量start和stop指定从0开始
LRANGE key start stop# 从key列表表头弹出一个元素没有就阻塞timeout秒如果timeout0则一直阻塞
BLPOP key [key ...] timeout
# 从key列表表尾弹出一个元素没有就阻塞timeout秒如果timeout0则一直阻塞
BRPOP key [key ...] timeout 应用场景 消息队列 消息队列在存取消息时必须要满足三个需求分别是消息保序、处理重复的消息和保证消息可靠性。 Redis 的 List 和 Stream 两种数据类型就可以满足消息队列的这三个需求。我们先来了解下基于 List 的消息队列实现方法后面在介绍 Stream 数据类型时候在详细说说 Stream。 1、如何满足消息保序需求 List 本身就是按先进先出的顺序对数据进行存取的所以如果使用 List 作为消息队列保存消息的话就已经能满足消息保序的需求了。 List 可以使用 LPUSH RPOP 或者反过来RPUSHLPOP命令实现消息队列。 生产者使用 LPUSH key value[value...] 将消息插入到队列的头部如果 key 不存在则会创建一个空的队列再插入消息。消费者使用 RPOP key 依次读取队列的消息先进先出。 不过在消费者读取数据时有一个潜在的性能风险点。 在生产者往 List 中写入数据时List 并不会主动地通知消费者有新消息写入如果消费者想要及时处理消息就需要在程序中不停地调用 RPOP 命令比如使用一个while(1)循环。如果有新消息写入RPOP命令就会返回结果否则RPOP命令返回空值再继续循环。 所以即使没有新消息写入List消费者也要不停地调用 RPOP 命令这就会导致消费者程序的 CPU 一直消耗在执行 RPOP 命令上带来不必要的性能损失。 为了解决这个问题Redis提供了 BRPOP 命令。BRPOP命令也称为阻塞式读取客户端在没有读到队列数据时自动阻塞直到有新的数据写入队列再开始读取新数据。和消费者程序自己不停地调用RPOP命令相比这种方式能节省CPU开销。 2、如何处理重复的消息 消费者要实现重复消息的判断需要 2 个方面的要求 每个消息都有一个全局的 ID。消费者要记录已经处理过的消息的 ID。当收到一条消息后消费者程序就可以对比收到的消息 ID 和记录的已处理过的消息 ID来判断当前收到的消息有没有经过处理。如果已经处理过那么消费者程序就不再进行处理了。 但是 List 并不会为每个消息生成 ID 号所以我们需要自行为每个消息生成一个全局唯一ID生成之后我们在用 LPUSH 命令把消息插入 List 时需要在消息中包含这个全局唯一 ID。 例如我们执行以下命令就把一条全局 ID 为 111000102、库存量为 99 的消息插入了消息队列 LPUSH mq 111000102:stock:99
(integer) 1 3、如何保证消息可靠性 当消费者程序从 List 中读取一条消息后List 就不会再留存这条消息了。所以如果消费者程序在处理消息的过程出现了故障或宕机就会导致消息没有处理完成那么消费者程序再次启动后就没法再次从 List 中读取消息了。 为了留存消息List 类型提供了 BRPOPLPUSH 命令这个命令的作用是让消费者程序从一个 List 中读取消息同时Redis 会把这个消息再插入到另一个 List可以叫作备份 List留存。 这样一来如果消费者程序读了消息但没能正常处理等它重启后就可以从备份 List 中重新读取消息并进行处理了。 好了到这里可以知道基于 List 类型的消息队列满足消息队列的三大需求消息保序、处理重复的消息和保证消息可靠性。 消息保序使用 LPUSH RPOP阻塞读取使用 BRPOP重复消息处理生产者自行实现全局唯一 ID消息的可靠性使用 BRPOPLPUSH 但是在用 List 做消息队列时如果生产者消息发送很快而消费者处理消息的速度比较慢这就导致 List 中的消息越积越多给 Redis 的内存带来很大压力。 要解决这个问题就要启动多个消费者程序组成一个消费组一起分担处理 List 中的消息。但是List 类型并不支持消费组的实现。 这就要说起 Redis 从 5.0 版本开始提供的 Stream 数据类型了Stream 同样能够满足消息队列的三大需求而且它还支持「消费组」形式的消息读取。 Hash 介绍 Hash 是一个键值对key - value集合其中 value 的形式入value[{field1value1}...{fieldNvalueN}]。Hash 特别适合用于存储对象。 Hash 与 String 对象的区别如下图所示: 内部实现 Hash 类型的底层数据结构是由压缩列表或哈希表实现的 如果哈希类型元素个数小于 512 个默认值可由 hash-max-ziplist-entries 配置所有值小于 64 字节默认值可由 hash-max-ziplist-value 配置的话Redis 会使用压缩列表作为 Hash 类型的底层数据结构如果哈希类型元素不满足上面条件Redis 会使用哈希表作为 Hash 类型的 底层数据结构。 在 Redis 7.0 中压缩列表数据结构已经废弃了交由 listpack 数据结构来实现了。 常用命令 # 存储一个哈希表key的键值
HSET key field value
# 获取哈希表key对应的field键值
HGET key field# 在一个哈希表key中存储多个键值对
HMSET key field value [field value...]
# 批量获取哈希表key中多个field键值
HMGET key field [field ...]
# 删除哈希表key中的field键值
HDEL key field [field ...] # 返回哈希表key中field的数量
HLEN key
# 返回哈希表key中所有的键值
HGETALL key # 为哈希表key中field键的值加上增量n
HINCRBY key field n 应用场景 缓存对象 Hash 类型的 keyfield value 的结构与对象的对象id 属性 值的结构相似也可以用来存储对象。 我们以用户信息为例它在关系型数据库中的结构是这样的 我们可以使用如下命令将用户对象的信息存储到 Hash 类型 # 存储一个哈希表uid:1的键值HSET uid:1 name Tom age 15
2
# 存储一个哈希表uid:2的键值HSET uid:2 name Jerry age 13
2
# 获取哈希表用户id为1中所有的键值HGETALL uid:1
1) name
2) Tom
3) age
4) 15 Redis Hash 存储其结构如下图 在介绍 String 类型的应用场景时有所介绍String Json也是存储对象的一种方式那么存储对象时到底用 String json 还是用 Hash 呢 一般对象用 String Json 存储对象中某些频繁变化的属性可以考虑抽出来用 Hash 类型存储。 购物车 以用户 id 为 key商品 id 为 field商品数量为 value恰好构成了购物车的3个要素如下图所示。 涉及的命令如下 添加商品HSET cart:{用户id} {商品id} 1添加数量HINCRBY cart:{用户id} {商品id} 1商品总数HLEN cart:{用户id}删除商品HDEL cart:{用户id} {商品id}获取购物车所有商品HGETALL cart:{用户id} 当前仅仅是将商品ID存储到了Redis 中在回显商品具体信息的时候还需要拿着商品 id 查询一次数据库获取完整的商品的信息。 Set 介绍 Set 类型是一个无序并唯一的键值集合它的存储顺序不会按照插入的先后顺序进行存储。 一个集合最多可以存储 2^32-1 个元素。概念和数学中个的集合基本类似可以交集并集差集等等所以 Set 类型除了支持集合内的增删改查同时还支持多个集合取交集、并集、差集。 Set 类型和 List 类型的区别如下 List 可以存储重复元素Set 只能存储非重复元素List 是按照元素的先后顺序存储元素的而 Set 则是无序方式存储元素的。 内部实现 Set 类型的底层数据结构是由哈希表或整数集合实现的 如果集合中的元素都是整数且元素个数小于 512 默认值set-maxintset-entries配置个Redis 会使用整数集合作为 Set 类型的底层数据结构如果集合中的元素不满足上面条件则 Redis 使用哈希表作为 Set 类型的底层数据结构。 常用命令 Set常用操作 # 往集合key中存入元素元素存在则忽略若key不存在则新建
SADD key member [member ...]
# 从集合key中删除元素
SREM key member [member ...]
# 获取集合key中所有元素
SMEMBERS key
# 获取集合key中的元素个数
SCARD key# 判断member元素是否存在于集合key中
SISMEMBER key member# 从集合key中随机选出count个元素元素不从key中删除
SRANDMEMBER key [count]
# 从集合key中随机选出count个元素元素从key中删除
SPOP key [count] Set运算操作 # 交集运算
SINTER key [key ...]
# 将交集结果存入新集合destination中
SINTERSTORE destination key [key ...]# 并集运算
SUNION key [key ...]
# 将并集结果存入新集合destination中
SUNIONSTORE destination key [key ...]# 差集运算
SDIFF key [key ...]
# 将差集结果存入新集合destination中
SDIFFSTORE destination key [key ...] 应用场景 集合的主要几个特性无序、不可重复、支持并交差等操作。 因此 Set 类型比较适合用来数据去重和保障数据的唯一性还可以用来统计多个集合的交集、错集和并集等当我们存储的数据是无序并且需要去重的情况下比较适合使用集合类型进行存储。 但是要提醒你一下这里有一个潜在的风险。Set 的差集、并集和交集的计算复杂度较高在数据量较大的情况下如果直接执行这些计算会导致 Redis 实例阻塞。 在主从集群中为了避免主库因为 Set 做聚合计算交集、差集、并集时导致主库被阻塞我们可以选择一个从库完成聚合统计或者把数据返回给客户端由客户端来完成聚合统计。 点赞 Set 类型可以保证一个用户只能点一个赞这里举例子一个场景key 是文章idvalue 是用户id。 uid:1 、uid:2、uid:3 三个用户分别对 article:1 文章点赞了。 # uid:1 用户对文章 article:1 点赞SADD article:1 uid:1
(integer) 1
# uid:2 用户对文章 article:1 点赞SADD article:1 uid:2
(integer) 1
# uid:3 用户对文章 article:1 点赞SADD article:1 uid:3
(integer) 1 uid:1 取消了对 article:1 文章点赞。 SREM article:1 uid:1
(integer) 1 获取 article:1 文章所有点赞用户 : SMEMBERS article:1
1) uid:3
2) uid:2 获取 article:1 文章的点赞用户数量 SCARD article:1
(integer) 2 判断用户 uid:1 是否对文章 article:1 点赞了 SISMEMBER article:1 uid:1
(integer) 0 # 返回0说明没点赞返回1则说明点赞了 共同关注 Set 类型支持交集运算所以可以用来计算共同关注的好友、公众号等。 key 可以是用户idvalue 则是已关注的公众号的id。 uid:1 用户关注公众号 id 为 5、6、7、8、9uid:2 用户关注公众号 id 为 7、8、9、10、11。 # uid:1 用户关注公众号 id 为 5、6、7、8、9SADD uid:1 5 6 7 8 9
(integer) 5
# uid:2 用户关注公众号 id 为 7、8、9、10、11SADD uid:2 7 8 9 10 11
(integer) 5 uid:1 和 uid:2 共同关注的公众号 # 获取共同关注SINTER uid:1 uid:2
1) 7
2) 8
3) 9 给 uid:2 推荐 uid:1 关注的公众号 SDIFF uid:1 uid:2
1) 5
2) 6 验证某个公众号是否同时被 uid:1 或 uid:2 关注: SISMEMBER uid:1 5
(integer) 1 # 返回0说明关注了SISMEMBER uid:2 5
(integer) 0 # 返回0说明没关注 抽奖活动 存储某活动中中奖的用户名 Set 类型因为有去重功能可以保证同一个用户不会中奖两次。 key为抽奖活动名value为员工名称把所有员工名称放入抽奖箱 SADD lucky Tom Jerry John Sean Marry Lindy Sary Mark
(integer) 5 如果允许重复中奖可以使用 SRANDMEMBER 命令。 # 抽取 1 个一等奖SRANDMEMBER lucky 1
1) Tom
# 抽取 2 个二等奖SRANDMEMBER lucky 2
1) Mark
2) Jerry
# 抽取 3 个三等奖SRANDMEMBER lucky 3
1) Sary
2) Tom
3) Jerry 如果不允许重复中奖可以使用 SPOP 命令。 # 抽取一等奖1个SPOP lucky 1
1) Sary
# 抽取二等奖2个SPOP lucky 2
1) Jerry
2) Mark
# 抽取三等奖3个SPOP lucky 3
1) John
2) Sean
3) Lindy Zset 介绍 Zset 类型有序集合类型相比于 Set 类型多了一个排序属性 score分值对于有序集合 ZSet 来说每个存储元素相当于有两个值组成的一个是有序结合的元素值一个是排序值。 有序集合保留了集合不能有重复成员的特性分值可以重复但不同的是有序集合中的元素可以排序。 内部实现 Zset 类型的底层数据结构是由压缩列表或跳表实现的 如果有序集合的元素个数小于 128 个并且每个元素的值小于 64 字节时Redis 会使用压缩列表作为 Zset 类型的底层数据结构如果有序集合的元素不满足上面的条件Redis 会使用跳表作为 Zset 类型的底层数据结构 在 Redis 7.0 中压缩列表数据结构已经废弃了交由 listpack 数据结构来实现了。 常用命令 Zset 常用操作 # 往有序集合key中加入带分值元素
ZADD key score member [[score member]...]
# 往有序集合key中删除元素
ZREM key member [member...]
# 返回有序集合key中元素member的分值
ZSCORE key member
# 返回有序集合key中元素个数
ZCARD key # 为有序集合key中元素member的分值加上increment
ZINCRBY key increment member # 正序获取有序集合key从start下标到stop下标的元素
ZRANGE key start stop [WITHSCORES]
# 倒序获取有序集合key从start下标到stop下标的元素
ZREVRANGE key start stop [WITHSCORES]# 返回有序集合中指定分数区间内的成员分数由低到高排序。
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]# 返回指定成员区间内的成员按字典正序排列, 分数必须相同。
ZRANGEBYLEX key min max [LIMIT offset count]
# 返回指定成员区间内的成员按字典倒序排列, 分数必须相同
ZREVRANGEBYLEX key max min [LIMIT offset count] Zset 运算操作相比于 Set 类型ZSet 类型没有支持差集运算 # 并集计算(相同元素分值相加)numberkeys一共多少个keyWEIGHTS每个key对应的分值乘积
ZUNIONSTORE destkey numberkeys key [key...]
# 交集计算(相同元素分值相加)numberkeys一共多少个keyWEIGHTS每个key对应的分值乘积
ZINTERSTORE destkey numberkeys key [key...] 应用场景 Zset 类型Sorted Set有序集合 可以根据元素的权重来排序我们可以自己来决定每个元素的权重值。比如说我们可以根据元素插入 Sorted Set 的时间确定权重值先插入的元素权重小后插入的元素权重大。 在面对需要展示最新列表、排行榜等场景时如果数据更新频繁或者需要分页显示可以优先考虑使用 Sorted Set。 排行榜 有序集合比较典型的使用场景就是排行榜。例如学生成绩的排名榜、游戏积分排行榜、视频播放排名、电商系统中商品的销量排名等。 我们以博文点赞排名为例小林发表了五篇博文分别获得赞为 200、40、100、50、150。 # arcticle:1 文章获得了200个赞ZADD user:xiaolin:ranking 200 arcticle:1
(integer) 1
# arcticle:2 文章获得了40个赞ZADD user:xiaolin:ranking 40 arcticle:2
(integer) 1
# arcticle:3 文章获得了100个赞ZADD user:xiaolin:ranking 100 arcticle:3
(integer) 1
# arcticle:4 文章获得了50个赞ZADD user:xiaolin:ranking 50 arcticle:4
(integer) 1
# arcticle:5 文章获得了150个赞ZADD user:xiaolin:ranking 150 arcticle:5
(integer) 1 文章 arcticle:4 新增一个赞可以使用 ZINCRBY 命令为有序集合key中元素member的分值加上increment ZINCRBY user:xiaolin:ranking 1 arcticle:4
51 查看某篇文章的赞数可以使用 ZSCORE 命令返回有序集合key中元素个数 ZSCORE user:xiaolin:ranking arcticle:4
50 获取小林文章赞数最多的 3 篇文章可以使用 ZREVRANGE 命令倒序获取有序集合 key 从start下标到stop下标的元素 # WITHSCORES 表示把 score 也显示出来ZREVRANGE user:xiaolin:ranking 0 2 WITHSCORES
1) arcticle:1
2) 200
3) arcticle:5
4) 150
5) arcticle:3
6) 100 获取小林 100 赞到 200 赞的文章可以使用 ZRANGEBYSCORE 命令返回有序集合中指定分数区间内的成员分数由低到高排序 ZRANGEBYSCORE user:xiaolin:ranking 100 200 WITHSCORES
1) arcticle:3
2) 100
3) arcticle:5
4) 150
5) arcticle:1
6) 200 电话、姓名排序 使用有序集合的 ZRANGEBYLEX 或 ZREVRANGEBYLEX 可以帮助我们实现电话号码或姓名的排序我们以 ZRANGEBYLEX 返回指定成员区间内的成员按 key 正序排列分数必须相同为例。 注意不要在分数不一致的 SortSet 集合中去使用 ZRANGEBYLEX和 ZREVRANGEBYLEX 指令因为获取的结果会不准确。 1、电话排序 我们可以将电话号码存储到 SortSet 中然后根据需要来获取号段 ZADD phone 0 13100111100 0 13110114300 0 13132110901
(integer) 3ZADD phone 0 13200111100 0 13210414300 0 13252110901
(integer) 3ZADD phone 0 13300111100 0 13310414300 0 13352110901
(integer) 3 获取所有号码: ZRANGEBYLEX phone -
1) 13100111100
2) 13110114300
3) 13132110901
4) 13200111100
5) 13210414300
6) 13252110901
7) 13300111100
8) 13310414300
9) 13352110901 获取 132 号段的号码 ZRANGEBYLEX phone [132 (133
1) 13200111100
2) 13210414300
3) 13252110901 获取132、133号段的号码 ZRANGEBYLEX phone [132 (134
1) 13200111100
2) 13210414300
3) 13252110901
4) 13300111100
5) 13310414300
6) 13352110901 2、姓名排序 zadd names 0 Toumas 0 Jake 0 Bluetuo 0 Gaodeng 0 Aimini 0 Aidehua
(integer) 6 获取所有人的名字: ZRANGEBYLEX names -
1) Aidehua
2) Aimini
3) Bluetuo
4) Gaodeng
5) Jake
6) Toumas 获取名字中大写字母A开头的所有人 ZRANGEBYLEX names [A (B
1) Aidehua
2) Aimini 获取名字中大写字母 C 到 Z 的所有人 ZRANGEBYLEX names [C [Z
1) Gaodeng
2) Jake
3) Toumas BitMap 介绍 Bitmap即位图是一串连续的二进制数组0和1可以通过偏移量offset定位元素。BitMap通过最小的单位bit来进行0|1的设置表示某个元素的值或者状态时间复杂度为O(1)。 由于 bit 是计算机中最小的单位使用它进行储存将非常节省空间特别适合一些数据量大且使用二值统计的场景。 内部实现 Bitmap 本身是用 String 类型作为底层数据结构实现的一种统计二值状态的数据类型。 String 类型是会保存为二进制的字节数组所以Redis 就把字节数组的每个 bit 位利用起来用来表示一个元素的二值状态你可以把 Bitmap 看作是一个 bit 数组。 常用命令 bitmap 基本操作 # 设置值其中value只能是 0 和 1
SETBIT key offset value# 获取值
GETBIT key offset# 获取指定范围内值为 1 的个数
# start 和 end 以字节为单位
BITCOUNT key start end bitmap 运算操作 # BitMap间的运算
# operations 位移操作符枚举值AND 与运算 OR 或运算 |XOR 异或 ^NOT 取反 ~
# result 计算的结果会存储在该key中
# key1 … keyn 参与运算的key可以有多个空格分割not运算只能一个key
# 当 BITOP 处理不同长度的字符串时较短的那个字符串所缺少的部分会被看作 0。返回值是保存到 destkey 的字符串的长度以字节byte为单位和输入 key 中最长的字符串长度相等。
BITOP [operations] [result] [key1] [keyn…]# 返回指定key中第一次出现指定value(0/1)的位置
BITPOS [key] [value] 应用场景 Bitmap 类型非常适合二值状态统计的场景这里的二值状态就是指集合元素的取值就只有 0 和 1 两种在记录海量数据时Bitmap 能够有效地节省内存空间。 签到统计 在签到打卡的场景中我们只用记录签到1或未签到0所以它就是非常典型的二值状态。 签到统计时每个用户一天的签到用 1 个 bit 位就能表示一个月假设是 31 天的签到情况用 31 个 bit 位就可以而一年的签到也只需要用 365 个 bit 位根本不用太复杂的集合类型。 假设我们要统计 ID 100 的用户在 2022 年 6 月份的签到情况就可以按照下面的步骤进行操作。 第一步执行下面的命令记录该用户 6 月 3 号已签到。 SETBIT uid:sign:100:202206 2 1 第二步检查该用户 6 月 3 日是否签到。 GETBIT uid:sign:100:202206 2 第三步统计该用户在 6 月份的签到次数。 BITCOUNT uid:sign:100:202206 这样我们就知道该用户在 6 月份的签到情况了。 如何统计这个月首次打卡时间呢 Redis 提供了 BITPOS key bitValue [start] [end]指令返回数据表示 Bitmap 中第一个值为 bitValue 的 offset 位置。 在默认情况下 命令将检测整个位图 用户可以通过可选的 start 参数和 end 参数指定要检测的范围。所以我们可以通过执行这条命令来获取 userID 100 在 2022 年 6 月份首次打卡日期 BITPOS uid:sign:100:202206 1 需要注意的是因为 offset 从 0 开始的所以我们需要将返回的 value 1 。 判断用户登陆态 Bitmap 提供了 GETBIT、SETBIT 操作通过一个偏移值 offset 对 bit 数组的 offset 位置的 bit 位进行读写操作需要注意的是 offset 从 0 开始。 只需要一个 key login_status 表示存储用户登陆状态集合数据 将用户 ID 作为 offset在线就设置为 1下线设置 0。通过 GETBIT判断对应的用户是否在线。50000 万 用户只需要 6 MB 的空间。 假如我们要判断 ID 10086 的用户的登陆情况 第一步执行以下指令表示用户已登录。 SETBIT login_status 10086 1 第二步检查该用户是否登陆返回值 1 表示已登录。 GETBIT login_status 10086 第三步登出将 offset 对应的 value 设置成 0。 SETBIT login_status 10086 0 连续签到用户总数 如何统计出这连续 7 天连续打卡用户总数呢 我们把每天的日期作为 Bitmap 的 keyuserId 作为 offset若是打卡则将 offset 位置的 bit 设置成 1。 key 对应的集合的每个 bit 位的数据则是一个用户在该日期的打卡记录。 一共有 7 个这样的 Bitmap如果我们能对这 7 个 Bitmap 的对应的 bit 位做 『与』运算。同样的 UserID offset 都是一样的当一个 userID 在 7 个 Bitmap 对应对应的 offset 位置的 bit 1 就说明该用户 7 天连续打卡。 结果保存到一个新 Bitmap 中我们再通过 BITCOUNT 统计 bit 1 的个数便得到了连续打卡 3 天的用户总数了。 Redis 提供了 BITOP operation destkey key [key ...]这个指令用于对一个或者多个 key 的 Bitmap 进行位元操作。 opration 可以是 and、OR、NOT、XOR。当 BITOP 处理不同长度的字符串时较短的那个字符串所缺少的部分会被看作 0 。空的 key 也被看作是包含 0 的字符串序列。 举个例子比如将三个 bitmap 进行 AND 操作并将结果保存到 destmap 中接着对 destmap 执行 BITCOUNT 统计。 # 与操作
BITOP AND destmap bitmap:01 bitmap:02 bitmap:03
# 统计 bit 位 1 的个数
BITCOUNT destmap 即使一天产生一个亿的数据Bitmap 占用的内存也不大大约占 12 MB 的内存10^8/8/1024/10247 天的 Bitmap 的内存开销约为 84 MB。同时我们最好给 Bitmap 设置过期时间让 Redis 删除过期的打卡数据节省内存。 HyperLogLog 介绍 Redis HyperLogLog 是 Redis 2.8.9 版本新增的数据类型是一种用于「统计基数」的数据集合类型基数统计就是指统计一个集合中不重复的元素个数。但要注意HyperLogLog 是统计规则是基于概率完成的不是非常准确标准误算率是 0.81%。 所以简单来说 HyperLogLog 提供不精确的去重计数。 HyperLogLog 的优点是在输入元素的数量或者体积非常非常大时计算基数所需的内存空间总是固定的、并且是很小的。 在 Redis 里面每个 HyperLogLog 键只需要花费 12 KB 内存就可以计算接近 2^64 个不同元素的基数和元素越多就越耗费内存的 Set 和 Hash 类型相比HyperLogLog 就非常节省空间。 这什么概念举个例子给大家对比一下。 用 Java 语言来说一般 long 类型占用 8 字节而 1 字节有 8 位即1 byte 8 bit即 long 数据类型最大可以表示的数是2^63-1。对应上面的2^64个数假设此时有2^63-1这么多个数从 0 ~ 2^63-1按照long以及1k 1024 字节的规则来计算内存总数就是((2^63-1) * 8/1024)K这是很庞大的一个数存储空间远远超过12K而 HyperLogLog 却可以用 12K 就能统计完。 内部实现 HyperLogLog 的实现涉及到很多数学问题太费脑子了我也没有搞懂。 常见命令 HyperLogLog 命令很少就三个。 # 添加指定元素到 HyperLogLog 中
PFADD key element [element ...]# 返回给定 HyperLogLog 的基数估算值。
PFCOUNT key [key ...]# 将多个 HyperLogLog 合并为一个 HyperLogLog
PFMERGE destkey sourcekey [sourcekey ...] 应用场景 百万级网页 UV 计数 Redis HyperLogLog 优势在于只需要花费 12 KB 内存就可以计算接近 2^64 个元素的基数和元素越多就越耗费内存的 Set 和 Hash 类型相比HyperLogLog 就非常节省空间。 所以非常适合统计百万级以上的网页 UV 的场景。 在统计 UV 时你可以用 PFADD 命令用于向 HyperLogLog 中添加新元素把访问页面的每个用户都添加到 HyperLogLog 中。 PFADD page1:uv user1 user2 user3 user4 user5 接下来就可以用 PFCOUNT 命令直接获得 page1 的 UV 值了这个命令的作用就是返回 HyperLogLog 的统计结果。 PFCOUNT page1:uv 不过有一点需要你注意一下HyperLogLog 的统计规则是基于概率完成的所以它给出的统计结果是有一定误差的标准误算率是 0.81%。 这也就意味着你使用 HyperLogLog 统计的 UV 是 100 万但实际的 UV 可能是 101 万。虽然误差率不算大但是如果你需要精确统计结果的话最好还是继续用 Set 或 Hash 类型。 GEO Redis GEO 是 Redis 3.2 版本新增的数据类型主要用于存储地理位置信息并对存储的信息进行操作。 在日常生活中我们越来越依赖搜索“附近的餐馆”、在打车软件上叫车这些都离不开基于位置信息服务Location-Based ServiceLBS的应用。LBS 应用访问的数据是和人或物关联的一组经纬度信息而且要能查询相邻的经纬度范围GEO 就非常适合应用在 LBS 服务的场景中。 内部实现 GEO 本身并没有设计新的底层数据结构而是直接使用了 Sorted Set 集合类型。 GEO 类型使用 GeoHash 编码方法实现了经纬度到 Sorted Set 中元素权重分数的转换这其中的两个关键机制就是「对二维地图做区间划分」和「对区间进行编码」。一组经纬度落在某个区间后就用区间的编码值来表示并把编码值作为 Sorted Set 元素的权重分数。 这样一来我们就可以把经纬度保存到 Sorted Set 中利用 Sorted Set 提供的“按权重进行有序范围查找”的特性实现 LBS 服务中频繁使用的“搜索附近”的需求。 常用命令 # 存储指定的地理空间位置可以将一个或多个经度(longitude)、纬度(latitude)、位置名称(member)添加到指定的 key 中。
GEOADD key longitude latitude member [longitude latitude member ...]# 从给定的 key 里返回所有指定名称(member)的位置经度和纬度不存在的返回 nil。
GEOPOS key member [member ...]# 返回两个给定位置之间的距离。
GEODIST key member1 member2 [m|km|ft|mi]# 根据用户给定的经纬度坐标来获取指定范围内的地理位置集合。
GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key] 应用场景 滴滴叫车 这里以滴滴叫车的场景为例介绍下具体如何使用 GEO 命令GEOADD 和 GEORADIUS 这两个命令。 假设车辆 ID 是 33经纬度位置是116.03457939.030452我们可以用一个 GEO 集合保存所有车辆的经纬度集合 key 是 cars:locations。 执行下面的这个命令就可以把 ID 号为 33 的车辆的当前经纬度位置存入 GEO 集合中 GEOADD cars:locations 116.034579 39.030452 33 当用户想要寻找自己附近的网约车时LBS 应用就可以使用 GEORADIUS 命令。 例如LBS 应用执行下面的命令时Redis 会根据输入的用户的经纬度信息116.05457939.030452 查找以这个经纬度为中心的 5 公里内的车辆信息并返回给 LBS 应用。 GEORADIUS cars:locations 116.054579 39.030452 5 km ASC COUNT 10 Stream 介绍 Redis Stream 是 Redis 5.0 版本新增加的数据类型Redis 专门为消息队列设计的数据类型。 在前面介绍 List 类型实现的消息队列有两个问题1. 生产者需要自行实现全局唯一 ID2. 不能以消费组形式消费数据。 基于 Stream 类型的消息队列就解决上面的问题它不仅支持自动生成全局唯一 ID而且支持以消费组形式消费数据。 常见命令 Stream 消息队列操作命令 XADD插入消息保证有序可以自动生成全局唯一 IDXREAD用于读取消息可以按 ID 读取数据XREADGROUP按消费组形式读取消息XPENDING 和 XACK XPENDING 命令可以用来查询每个消费组内所有消费者已读取但尚未确认的消息而 XACK 命令用于向消息队列确认消息处理已完成。 应用场景 消息队列 生产者通过 XADD 命令插入一条消息 # * 表示让 Redis 为插入的数据自动生成一个全局唯一的 ID
# 往名称为 mymq 的消息队列中插入一条消息消息的键是 name值是 xiaolinXADD mymq * name xiaolin
1654254953808-0 插入成功后会返回全局唯一的 ID1654254953808-0。消息的全局唯一 ID 由两部分组成 第一部分“1654254953808”是数据插入时以毫秒为单位计算的当前服务器时间第二部分表示插入消息在当前毫秒内的消息序号这是从 0 开始编号的。例如“1654254953808-0”就表示在“1654254953808”毫秒内的第 1 条消息。 消费者通过 XREAD 命令从消息队列中读取消息时可以指定一个消息 ID并从这个消息 ID 的下一条消息开始进行读取注意是输入消息 ID 的下一条信息开始读取不是查询输入ID的消息。 # 从 ID 号为 1654254953807-0 的消息开始读取后续的所有消息示例中一共 1 条。XREAD Stream mymq 1654254953807-0
1) 1) mymq2) 1) 1) 1654254953808-02) 1) name2) xiaolin 如果想要实现阻塞读当没有数据时阻塞住可以调用 XRAED 时设定 block 配置项实现类似于 BRPOP 的阻塞读取操作。 比如下面这命令设置了 block 10000 的配置项10000 的单位是毫秒表明 XREAD 在读取最新消息时如果没有消息到来XREAD 将阻塞 10000 毫秒即 10 秒然后再返回。 # 命令最后的“$”符号表示读取最新的消息XREAD block 10000 Stream mymq $
(nil)
(10.00s) 前面介绍的这些操作 List 也支持的接下来看看 Stream 特有的功能。 Stream 可以以使用 XGROUP 创建消费组创建消费组之后Stream 可以使用 XREADGROUP 命令让消费组内的消费者读取消息。 创建一个名为 group1 的消费组这个消费组消费的消息队列是 mymq # 创建一个名为 group1 的消费组XGROUP create mymq group1 0
OK 消费组 group1 内的消费者 consumer1 从 mymq 消息队列中读取所有消息的命令如下 # 命令最后的参数“”表示从第一条尚未被消费的消息开始读取。XREADGROUP group group1 consumer1 Stream mymq
1) 1) mymq2) 1) 1) 1654254953808-02) 1) name2) xiaolin 消息队列中的消息一旦被消费组里的一个消费者读取了就不能再被该消费组内的其他消费者读取了。 比如说我们执行完刚才的 XREADGROUP 命令后再执行一次同样的命令此时读到的就是空值了 XREADGROUP group group1 consumer1 Stream mymq
(nil) 使用消费组的目的是让组内的多个消费者共同分担读取消息所以我们通常会让每个消费者读取部分消息从而实现消息读取负载在多个消费者间是均衡分布的。 例如我们执行下列命令让 group2 中的 consumer1、2、3 各自读取一条消息。 # 让 group2 中的 consumer1 从 mymq 消息队列中消费一条消息XREADGROUP group group2 consumer1 count 1 Stream mymq
1) 1) mymq2) 1) 1) 1654254953808-02) 1) name2) xiaolin
# 让 group2 中的 consumer2 从 mymq 消息队列中消费一条消息XREADGROUP group group2 consumer2 count 1 Stream mymq
1) 1) mymq2) 1) 1) 1654256265584-02) 1) name2) xiaolincoding
# 让 group2 中的 consumer3 从 mymq 消息队列中消费一条消息XREADGROUP group group2 consumer3 count 1 Stream mymq
1) 1) mymq2) 1) 1) 1654256271337-02) 1) name2) Tom 基于 Stream 实现的消息队列如何保证消费者在发生故障或宕机再次重启后仍然可以读取未处理完的消息 Streams 会自动使用内部队列也称为 PENDING List留存消费组里每个消费者读取的消息直到消费者使用 XACK 命令通知 Streams“消息已经处理完成”。 如果消费者没有成功处理消息它就不会给 Streams 发送 XACK 命令消息仍然会留存。此时消费者可以在重启后用 XPENDING 命令查看已读取、但尚未确认处理完成的消息。 例如我们来查看一下 group2 中各个消费者已读取、但尚未确认的消息个数命令如下 127.0.0.1:6379 XPENDING mymq group2
1) (integer) 3
2) 1654254953808-0 # 表示 group2 中所有消费者读取的消息最小 ID
3) 1654256271337-0 # 表示 group2 中所有消费者读取的消息最大 ID
4) 1) 1) consumer12) 12) 1) consumer22) 13) 1) consumer32) 1 如果想查看某个消费者具体读取了哪些数据可以执行下面的命令 # 查看 group2 里 consumer2 已从 mymq 消息队列中读取了哪些消息XPENDING mymq group2 - 10 consumer2
1) 1) 1654256265584-02) consumer23) (integer) 4107004) (integer) 1 可以看到consumer2 已读取的消息的 ID 是 1654256265584-0。 一旦消息 1654256265584-0 被 consumer2 处理了consumer2 就可以使用 XACK 命令通知 Streams然后这条消息就会被删除。 XACK mymq group2 1654256265584-0
(integer) 1 当我们再使用 XPENDING 命令查看时就可以看到consumer2 已经没有已读取、但尚未确认处理的消息了。 XPENDING mymq group2 - 10 consumer2
(empty array) 好了基于 Stream 实现的消息队列就说到这里了小结一下 消息保序XADD/XREAD阻塞读取XREAD block重复消息处理Stream 在使用 XADD 命令会自动生成全局唯一 ID消息可靠性内部使用 PENDING List 自动保存消息使用 XPENDING 命令查看消费组已经读取但是未被确认的消息消费者使用 XACK 确认消息支持消费组形式消费数据 Redis 基于 Stream 消息队列与专业的消息队列有哪些差距 一个专业的消息队列必须要做到两大块 消息不丢。消息可堆积。 1、Redis Stream 消息会丢失吗 使用一个消息队列其实就分为三大块生产者、队列中间件、消费者所以要保证消息就是保证三个环节都不能丢失数据。 Redis Stream 消息队列能不能保证三个环节都不丢失数据 Redis 生产者会不会丢消息生产者会不会丢消息取决于生产者对于异常情况的处理是否合理。从消息被生产出来然后提交给 MQ 的过程中只要能正常收到 MQ 中间件 的 ack 确认响应就表示发送成功所以只要处理好返回值和异常如果返回异常则进行消息重发那么这个阶段是不会出现消息丢失的。Redis 消费者会不会丢消息不会因为 Stream MQ 中间件会自动使用内部队列也称为 PENDING List留存消费组里每个消费者读取的消息但是未被确认的消息。消费者可以在重启后用 XPENDING 命令查看已读取、但尚未确认处理完成的消息。等到消费者执行完业务逻辑后再发送消费确认 XACK 命令也能保证消息的不丢失。Redis 队列中间件会不会丢消息会Redis 在以下 2 个场景下都会导致数据丢失 AOF 持久化配置为每秒写盘但这个写盘过程是异步的Redis 宕机时会存在数据丢失的可能主从复制也是异步的主从切换时也存在丢失数据的可能。 可以看到Redis 在队列中间件环节无法保证消息不丢。像 RabbitMQ 或 Kafka 这类专业的队列中间件在使用时是部署一个集群生产者在发布消息时队列中间件通常会写「多个节点」也就是有多个副本这样一来即便其中一个节点挂了也能保证集群的数据不丢失。 2、Redis Stream 消息可堆积吗 Redis 的数据都存储在内存中这就意味着一旦发生消息积压则会导致 Redis 的内存持续增长如果超过机器内存上限就会面临被 OOM 的风险。所以 Redis 的 Stream 提供了可以指定队列最大长度的功能就是为了避免这种情况发生。 但 Kafka、RabbitMQ 专业的消息队列它们的数据都是存储在磁盘上当消息积压时无非就是多占用一些磁盘空间。 因此把 Redis 当作队列来使用时会面临的 2 个问题 Redis 本身可能会丢数据面对消息挤压内存资源会紧张 所以能不能将 Redis 作为消息队列来使用关键看你的业务场景 如果你的业务场景足够简单对于数据丢失不敏感而且消息积压概率比较小的情况下把 Redis 当作队列是完全可以的。如果你的业务有海量消息消息积压的概率比较大并且不能接受数据丢失那么还是用专业的消息队列中间件吧。 参考资料 《Redis 核心技术与实战》https://www.cnblogs.com/hunternet/p/12742390.htmlhttps://www.cnblogs.com/qdhxhz/p/15669348.htmlhttps://www.cnblogs.com/bbgs-xc/p/14376109.htmlhttp://kaito-kidd.com/2021/04/19/can-redis-be-used-as-a-queue/ 总结 Redis 常见的五种数据类型**String字符串Hash哈希List列表Set集合及 Zset(sorted set有序集合)**。 这五种数据类型都由多种数据结构实现的主要是出于时间和空间的考虑当数据量小的时候使用更简单的数据结构有利于节省内存提高性能。 这五种数据类型与底层数据结构对应关系图如下左边是 Redis 3.0版本的也就是《Redis 设计与实现》这本书讲解的版本现在看还是有点过时了右边是现在 Github 最新的 Redis 代码的。 可以看到Redis 数据类型的底层数据结构随着版本的更新也有所不同比如 在 Redis 3.0 版本中 List 对象的底层数据结构由「双向链表」或「压缩表列表」实现但是在 3.2 版本之后List 数据类型底层数据结构是由 quicklist 实现的在最新的 Redis 代码中压缩列表数据结构已经废弃了交由 listpack 数据结构来实现了。 Redis 五种数据类型的应用场景 String 类型的应用场景缓存对象、常规计数、分布式锁等。List 类型的应用场景消息队列有两个问题1. 生产者需要自行实现全局唯一 ID2. 不能以消费组形式消费数据等。Hash 类型缓存对象、购物车等。Set 类型聚合计算并集、交集、差集场景比如点赞、共同关注、抽奖活动等。Zset 类型排序场景比如排行榜、电话和姓名排序等。 Redis 后续版本又支持四种数据类型它们的应用场景如下 BitMap2.2 版新增二值状态统计的场景比如签到、判断用户登陆状态、连续签到用户总数等HyperLogLog2.8 版新增海量数据基数统计的场景比如百万级网页 UV 计数等GEO3.2 版新增存储地理位置信息的场景比如滴滴叫车Stream5.0 版新增消息队列相比于基于 List 类型实现的消息队列有这两个特有的特性自动生成全局唯一消息ID支持以消费组形式消费数据。 针对 Redis 是否适合做消息队列关键看你的业务场景 如果你的业务场景足够简单对于数据丢失不敏感而且消息积压概率比较小的情况下把 Redis 当作队列是完全可以的。如果你的业务有海量消息消息积压的概率比较大并且不能接受数据丢失那么还是用专业的消息队列中间件吧。 - END - 扫码关注公众号「网管叨bi叨」 给网管个星标第一时间吸我的知识 网管整理了一本《Go 开发参考书》收集了70多条开发实践。去公众号回复【gocookbook】领取还有一本《k8s 入门实践》讲解了常用软件在K8s上的部署过程公众号回复【k8s】即可领取 觉得有用就点个在看
文章转载自: http://www.morning.yfzld.cn.gov.cn.yfzld.cn http://www.morning.xmrmk.cn.gov.cn.xmrmk.cn http://www.morning.slpcl.cn.gov.cn.slpcl.cn http://www.morning.qcrhb.cn.gov.cn.qcrhb.cn http://www.morning.hkcjx.cn.gov.cn.hkcjx.cn http://www.morning.fnpyk.cn.gov.cn.fnpyk.cn http://www.morning.bljcb.cn.gov.cn.bljcb.cn http://www.morning.mqwdh.cn.gov.cn.mqwdh.cn http://www.morning.ghxsn.cn.gov.cn.ghxsn.cn http://www.morning.zhishizf.cn.gov.cn.zhishizf.cn http://www.morning.lzph.cn.gov.cn.lzph.cn http://www.morning.daidudu.com.gov.cn.daidudu.com http://www.morning.bnbzd.cn.gov.cn.bnbzd.cn http://www.morning.zcnfm.cn.gov.cn.zcnfm.cn http://www.morning.jsxrm.cn.gov.cn.jsxrm.cn http://www.morning.qrcsb.cn.gov.cn.qrcsb.cn http://www.morning.mbbgk.com.gov.cn.mbbgk.com http://www.morning.kfwqd.cn.gov.cn.kfwqd.cn http://www.morning.kcrw.cn.gov.cn.kcrw.cn http://www.morning.czrcf.cn.gov.cn.czrcf.cn http://www.morning.gcdzp.cn.gov.cn.gcdzp.cn http://www.morning.xfwnk.cn.gov.cn.xfwnk.cn http://www.morning.hwbf.cn.gov.cn.hwbf.cn http://www.morning.ymwrs.cn.gov.cn.ymwrs.cn http://www.morning.wqbbc.cn.gov.cn.wqbbc.cn http://www.morning.fnfhs.cn.gov.cn.fnfhs.cn http://www.morning.jntcr.cn.gov.cn.jntcr.cn http://www.morning.djpps.cn.gov.cn.djpps.cn http://www.morning.ldynr.cn.gov.cn.ldynr.cn http://www.morning.bkwd.cn.gov.cn.bkwd.cn http://www.morning.zshuhd015.cn.gov.cn.zshuhd015.cn http://www.morning.tmxtr.cn.gov.cn.tmxtr.cn http://www.morning.gnbtp.cn.gov.cn.gnbtp.cn http://www.morning.mnlk.cn.gov.cn.mnlk.cn http://www.morning.xfjwm.cn.gov.cn.xfjwm.cn http://www.morning.drpbc.cn.gov.cn.drpbc.cn http://www.morning.ygwyt.cn.gov.cn.ygwyt.cn http://www.morning.lgxzj.cn.gov.cn.lgxzj.cn http://www.morning.nlkm.cn.gov.cn.nlkm.cn http://www.morning.tkyxl.cn.gov.cn.tkyxl.cn http://www.morning.dwrbn.cn.gov.cn.dwrbn.cn http://www.morning.ltxgk.cn.gov.cn.ltxgk.cn http://www.morning.czlzn.cn.gov.cn.czlzn.cn http://www.morning.wynqg.cn.gov.cn.wynqg.cn http://www.morning.tntqr.cn.gov.cn.tntqr.cn http://www.morning.hpkr.cn.gov.cn.hpkr.cn http://www.morning.sbyhj.cn.gov.cn.sbyhj.cn http://www.morning.xhfky.cn.gov.cn.xhfky.cn http://www.morning.hmsong.com.gov.cn.hmsong.com http://www.morning.pnmnl.cn.gov.cn.pnmnl.cn http://www.morning.wsjnr.cn.gov.cn.wsjnr.cn http://www.morning.dmzmy.cn.gov.cn.dmzmy.cn http://www.morning.jxfmn.cn.gov.cn.jxfmn.cn http://www.morning.gpsrk.cn.gov.cn.gpsrk.cn http://www.morning.vvbsxm.cn.gov.cn.vvbsxm.cn http://www.morning.bxdlrcz.cn.gov.cn.bxdlrcz.cn http://www.morning.hffjj.cn.gov.cn.hffjj.cn http://www.morning.crhd.cn.gov.cn.crhd.cn http://www.morning.pnfwd.cn.gov.cn.pnfwd.cn http://www.morning.gppqf.cn.gov.cn.gppqf.cn http://www.morning.ssjee.cn.gov.cn.ssjee.cn http://www.morning.cxlys.cn.gov.cn.cxlys.cn http://www.morning.wnkqt.cn.gov.cn.wnkqt.cn http://www.morning.gcthj.cn.gov.cn.gcthj.cn http://www.morning.hxwrs.cn.gov.cn.hxwrs.cn http://www.morning.zdfrg.cn.gov.cn.zdfrg.cn http://www.morning.mtktn.cn.gov.cn.mtktn.cn http://www.morning.drywd.cn.gov.cn.drywd.cn http://www.morning.ykrkq.cn.gov.cn.ykrkq.cn http://www.morning.xmxbm.cn.gov.cn.xmxbm.cn http://www.morning.xbptx.cn.gov.cn.xbptx.cn http://www.morning.dwncg.cn.gov.cn.dwncg.cn http://www.morning.ybhjs.cn.gov.cn.ybhjs.cn http://www.morning.lqjlg.cn.gov.cn.lqjlg.cn http://www.morning.ktbjk.cn.gov.cn.ktbjk.cn http://www.morning.rwqj.cn.gov.cn.rwqj.cn http://www.morning.bdzps.cn.gov.cn.bdzps.cn http://www.morning.xzqzd.cn.gov.cn.xzqzd.cn http://www.morning.bpmns.cn.gov.cn.bpmns.cn http://www.morning.sbrjj.cn.gov.cn.sbrjj.cn