redis
官方文档
https://redis.io/docs/getting-started/
概念
REmote DIctionary Server(Redis) 是一个由 Salvatore Sanfilippo 写的key-value 存储系统,是跨平台的非关系型数据库。
Redis 通常被称为数据结构服务器,因为值(value)可以是字符串(String)、哈希(Hash)、列表(list)、集合(sets)和有序集合(sorted sets)等类型。
redis是否可以当作消息组件使用
消息队列的使用场景
消息队列主要用来暂存生产者生产的消息,供消费者使用。
-
解耦
当多个消费者依赖一个生产者时,每次增加或减少消费者都要修改生产者相关的代码。使用消息队列能够减少对生产者的修改。
-
异步
当多个消费者依赖一个生产者时,每个消费者处理信息的速度不一样,这样会导致生产者的效率过低。使用消息队列大大增加了生产者的效率。
-
削峰
在一个时间段过多的请求出现,会导致消费者无法处理甚至奔溃,使用消息队列能够将请求存储,让消费者按照能力来获取消息处理。
使用消息队列需要解决的问题
-
技术问题
-
CPU空转
问题:当消息队列为空时,消费者频繁拉取消息。
解决方式:
1、让消费者休眠一段时间(会产生延时)。 2、通过阻塞式获取消息,即如果队列为空,消费者在拉取消息时就「阻塞等待」,一旦有新消息过来,就通知我的消费者立即处理新消息。
-
不可重复消费
问题:当消息被使用后消息就消失。
解决方式:
1、在redis中可以使用Pub/Sub方式或stream解决
-
消息丢失
问题:如果发生以下场景,就有可能导致数据丢失:
-
生产者发送信息失败或无法判断消息是否发送成功:
解决方式:重复发送消息,消息队列对消息进行处理。
-
消费者下线或出现故障导致信息获取但未处理完成
解决方式:当消费者处理完成信息后返回成功信息给消息队列
-
消息队列宕机
解决方式:将数据持久化(redis通过RDB和AOF方式持久化)
-
消息堆积
解决方式:
1. 生产者限流:避免消费者处理不及时,导致持续积压 2. 丢弃消息:中间件丢弃旧消息,只保留固定长度的新消息,其他消息可放入数据库等可保存数据的地方
-
-
-
业务问题
- 增加了系统的复杂性和维护的消耗
- 维护者需要有相关消息队列的部署和运维能力
答案
能否使用消息队列主要看它是否能解决上述问题(即在使用消息队列需要解决的问题)。
技术方面:
1、redis在消息堆积做的不是很好,因为redis使用内存存储数据,它能存储的数据比磁盘小。
2、redis会产生极少量的消息丢失。在使用aof持久化时突然宕机会导致最新的几条命令丢失,
业务方面:redis的stream使用较为简单,相较其他消息队列较为快速。
总结:如果业务场景足够简单,对于数据丢失不敏感,而且消息积压概率比较小的情况下,把 Redis 当作队列是完全可以的。
使用建议及注意事项
Key的使用
- 不要太长,太长的可以使用hash值
- 不要太短,因为key所占的空间相对其他来说较少,命名最好易读
- 命名最好统一,官方建议object-type:id,多词组合的命名可以使用.或-,例如“comment🔢reply.to” 或者 “comment🔢reply-to”
通用操作
-
exists:
判断该键是否存在
-
del:
删除改键
-
type:
判断类型
-
expire(pexpire):
设置过时时间
-
ttl(pttl):
检查剩余的过时时间
数据结构
Strings
-
使用情景:
缓存HTML页面
-
主要操作:
-
set与get操作:
赋值与获取(其中set会替换已存在的数据)
-
incr(incrby)与decr(decrby)操作:
增加和减少(它们是原子操作)
-
mset与mget操作:
同时赋值与获取多个值
-
Lists
-
实现:
redis的list通过链表(Linked Lists)实现
-
使用场景:
- 社交平台上用户的最新发布的信息
- 使用生产者-消费者模式进行通信的程序
-
主要操作:
-
rpush(lpush):
往链表的头(尾)增加数据
-
rpop(lpop):
从链表的头(尾)删除数据
-
brpop(blpop):
从链表的头(尾)删除数据,为了解决rpop(lpop)中lists为空所造成的问题
-
lmove(rmove):
从链表的头(尾)移除数据并放入另一个列表,可以防止因为一些原因(如网络问题)导致的信息丢失
-
lrange:
往链表的头(尾)开始引索数据
-
ltrim:
往链表的头(尾)开始引索数据并将其他元素丢弃
-
Hashes
-
使用场景:
存储对象(就是存储多个属性值)
-
主要操作:
-
hset:
给该hash对象赋值
-
hget(hmget/hgetall):
得到对应的单个(多个/所有)的属性的值
-
Sets
-
使用场景:
- 粉丝、关注、好友
- 标签
- 黑名单/白名单
-
主要操作:
-
sadd:
为该集合添加元素
-
smembers
得到该集合的所有值
-
sismember
判断该元素是否存在该集合中
-
SUNIONSTORE
获取多个集合的并集,并将该并集赋给另外一个集合
-
sinter
获取多个集合的交集
-
srandmember
随机获取该集合的指定个数的值
-
pop
随机移除该集合的一个值
-
scard
获取该集合的元素总量
-
Sorted sets
-
使用场景
- 排行榜
- 优先级
-
主要操作
-
zadd
为该集合增加元素
-
zrange(zrevrange)
按分数(或字符串[注:按字符串需要所有的元素的分数为0])正序(倒序)引索该集合中的元素
-
zrangebyscore
获取指定分数范围内的元素
-
zremrangebyscore(zremrangebylex)
移除指定分数(字符串)范围内的元素,并返回移除数量
-
zrank(zrevrank)
按正序(倒序)获取指定元素的引索
-
zcount(zlexcount)
统计指定分数(字符串)范围内的元素数量
-
Bitmaps
-
实现:
bitmap不是一种数据结构,底层是string数据结构
-
使用场景:
- 用户签到
- 实时分析
-
主要操作:
-
setbit
给相应的位数赋值
-
getbit
得到相应的位数的值
-
bitop
进行位运算,并将结果付给另一个string类型数据结构
-
bitcount
统计指定范围内值为1的总数
-
bitpos
获取指定元素的引索
-
HyperLogLogs
-
使用场景:
一些要求的精度不高但数据量极大的数据统计,例如,统计google被多少用户访问的次数
-
主要操作:
-
pfadd
增加元素
-
pfcount
返回估计的总量
-
streams
-
实现:
注意:与redis其他的数据结构不同,stream删除最后一个item时不会导致该stream的删除(因为stream拥有consumer_group)
redis stream通过redis tree实现。 每个redis由多个item组成,每个item由一个id和一个字典(即多个键值对)组成。 stream通常与Consumer groups协同工作。 每个Consumer group有自己的名字,对应的流,最后收到的消息id,与它的消费者(包括它拥有的挂起的messages) +----------------------------------------+ | consumer_group_name: mygroup | | consumer_group_stream: somekey | | last_delivered_id: 1292309234234-92 | | | | consumers: | | "consumer-1" with pending messages | | 1292309234234-4 | | 1292309234232-8 | | "consumer-42" with pending messages | | ... (and so forth) | +----------------------------------------+ 每个流可以有多个Consumer group,每个Consumer group可以有多个consumer
-
使用场景:
1、业务场景足够简单,对于数据丢失不敏感,而且消息积压概率比较小的情况下,把 Redis 当作队列是完全可以的。
2、因为redis stream支持时间序列数据,并且跨平台能力强。可以用于IoT数据采集:Redis作为一个嵌入式的存储系统跑在各个IoT设备上,各个设备使用Redis Stream暂存产生的时序数据,然后再异步的推送到云端。
3、服务器端负载均衡系统,即当多个功能一致的consumer处理大量的message时,可以根据每个consumer的性能来获取相应的message(即性能好的可以处理更多的messages)
-
查询模式:
-
只读取最新得到的数据
-
访问指定时间的数据
-
通过consumer groups访问,每个consumer获取stream的一个部分
-
-
主要操作:
-
基本操作
-
xadd
往stream中增加item(即多个键值对和一个id),id可以指定或自动生成,通过maxlen可以指定stream的长度(超过长度的(最旧的)item会被去除),当使用maxlen时使用~符号能够更加节省资源(因为redis stream是由redis tree组成的,当删除节点时要求修改后面的节点信息,使用该符号能使出现一个空的节点后再删除,虽然这样会导致保留的数据略大于maxlen)
-
xlen
计算一个stream中的item个数(注意,一个item里有一个id和多个键值对)
-
xrange(xrevrange)
正序(倒序)访问指定id范围内指定个数的item
-
xread
以阻塞(通过block)或非阻塞方式监听/获取指定流(流可以有多个)中,指定个数且id大于所规定的id中的item
-
xdel
根据id和stream来删除stream中的某个item
-
-
consumer groups操作
-
xgroup
通过指定stream与groups的名字创建Consumer groups
-
xreadgroup
通过指定stream,Consumer group和consumer来获取stream中最新的消息(通过>,会让该message挂起[挂起指该message已给了消费者,但消费者没有确认该消息处理完成])或获取该consumer所挂起的所有message(通过0)
-
xack
通过指定stream,Consumer group和需要确认信息处理完成的信息来让该message取消挂起
-
xpending
通过指定stream,Consumer group来获取该group的挂起情况(通过指定范围和个数可以获得详细信息,不指定则获得概述信息)
注:在使用该指令获取信息后可用xrange获取该message的更具体的信息。 -
xclaim
通过指定stream,Consumer group,需要传给的consumer,超时时间和指定的message的id,来将长时间没有确定的挂起信息传给其他消费者处理(通过justid可以只获取改变的message的id)
-
xautoclaim
通过指定stream,Consumer group,需要传给的consumer,超时时间和需要超过的id(自动检测大于id的挂起的message),来将长时间没有确定的挂起信息传给其他消费者处理(通过justid可以只获取改变的message的id)
-
xinfo
获取stream,group还有consumer的相关信息
-
xtrim
可以指定stream的长度(超过长度的(最旧的)item会被去除),当使用maxlen时使用~符号能够更加节省资源(因为redis stream是由redis tree组成的,当删除节点时要求修改后面的节点信息,使用该符号能使出现一个空的节点后再删除,虽然这样会导致保留的数据略大于maxlen)
-
-
-
主要通配符
- + stream数据结构中最小的和最大的id $ 该stream中已存在message的最大的id > 只有XREADGROUP指令使用,表示只接收从来没有发送过的message * 只有xadd指令使用,表示自动选择id为新增加的item
-
message被使用的延时
Processed between 0 and 1 ms -> 74.11% Processed between 1 and 2 ms -> 25.80% Processed between 2 and 3 ms -> 0.06% Processed between 3 and 4 ms -> 0.01% Processed between 4 and 5 ms -> 0.02% So 99.9% of requests have a latency <= 2 milliseconds, with the outliers that remain still very close to the average.
-
故障与解决:
-
消息损坏或该消息在处理程序发生错误
可以检查该message被传递的次数,如果次数达到一定的量,则将该message传到其他的消息队列,由其他程序(或人)处理
-
消息丢失
配置redis持久化设置
-