基础
Redis 数据结构
常用数据结构有:字符串 String、列表 List、字典 Hash、集合 Set、有序集合 SortedSet。
常用数据结构说明
String
- 命令
命令 | 说明 |
---|---|
set key value | 设置一个key,值为value,类型为String类型;如果这个key已经存在,则更新这个key的值。返回值:1 表示成功、0 表示失败 |
setnx key value | 如果这个key不存在,则设置一个key,值为value;如果key存在,则不做更新。返回值:1 表示成功、0 表示失败 |
append key value | 如果key已经存在,则将value追加到这个key原先的value值的末尾。如果这个key不存在,则执行set操作。 |
incr key | 将 key对应的value中储存的数字值增一,然后返回。注意:[1]如果这个key不存在,那么key的值会先被初始化为0,然后再执行incr操作。[2]如果这个key对应的value值不是数字,则会返回一个错误。 |
incrby key step | 将key对应的value值增加指定的step。注意:类似同incr。 |
decr key | 将 key 对应的value中储存的数字值减一,然后返回。注意:类似同incr。 |
decrby key decrement | 将key减少对应的步长值。注意:类似同incr。 |
help @string | 查看string类型的帮助文档 |
- 场景
- 缓存:k-v 缓存,mysql 做持久层,降低数据库读写压力
- 计数器:点赞功能等
- 抢购秒杀:redis 是单线程模型,命令顺序执行
- session:SpringSession + Redis 实现 session 共享,Redisson + Redis 实现分布式锁
List
Redis 使用双端链表实现 List,有序,值可重复。
【1】基于Linked List实现。
【2】元素是字符串类型。
【3】列表头尾增删快,中间增删慢,增删元素是常态。
【4】从左至右,从0开始,从右至左,从-1开始。
【5】元素可以重复出现。
【6】最多包含2^32-1元素。
- 命令
- lpush + lpop = Stack(栈)
- lpush + rpop = Queue(队列)
- lpush + ltrim = Capped Collection(有限集合)
- lpush + rpop = Message Queue(消息队列)
- 场景
- timeline:微博时间轴等
- 微博评论
- 聊天室
Hash
【1】hash内容由field和与之关联的value组成map键值对组成
【2】key、field和value是字符串类型
【3】一个hash中最多包含2^32-1键值对
- 命令 hget、hset、hdel 等
- 场景
- 缓存:数据结构更清晰,更节省空间
Set
集合中元素无序、不重复,支持集合间操作,如:交集、并集、差集。
【1】无序的、无重复的。
【2】元素是字符串类型。
【3】最多包含2^32-1元素。
- 命令 sadd、spop、srem、scard、smemebers、sismember 等
- 场景
- 标签:对某事物添加标签,可计算多个事物的集合关系,如共同关注的人等
- 点赞、收藏、关注等数量及集合的计算信息
SortedSet
Set 集合的有序版本,值不可重复,元素可排序,每个元素有数值得分,该得分用于排序等。
【1】类似Set集合
【2】有序的、无重复的
【3】元素是字符串类型
【4】每一个元素都关联着一个浮点数分值(Score),并按照分值从小到大的顺序排列集合中的元素。注意:分值可以相同
【5】最多包含2^32-1元素
- 命令 zadd、zrange、zscore、acount、zrank、zrem 等
- 场景
- 排行榜: 榜单等
HyperLogLog
基数:一个集合(注意:这里集合的含义是 Object 的聚合,可以包含重复元素)中不重复元素的个数。例如集合
{1,2,3,1,2}
,它有5个元素,但它的 基数(Distinct 数)为3。
Redis HyperLogLog 是用来做基数统计的算法,是近似统计海量去重元素数量的算法。HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
- 基数不大,数据量不大就用不上,会大材小用、浪费空间
- 有局限性,就是只能统计基数数量,无法知道具体内容
- HyperLogLog 去重比 bitmap 方便
- 一般可以 bitmap 和 hyperloglog 配合使用,bitmap 标识哪些用户活跃,hyperloglog 计数
- 命令
命令 | 说明 |
---|---|
pfadd key element [element...] | 添加指定元素到 HyperLogLog 中。将任意数量的元素添加到指定的 HyperLogLog 里面。时间复杂度: 每添加一个元素的复杂度为 O(1) 。如果 HyperLogLog 估计的近似基数(approximated cardinality)在命令执行之后出现了变化, 那么命令返回 1 , 否则返回 0 。 如果命令执行时给定的键不存在, 那么程序将先创建一个空的 HyperLogLog 结构, 然后再执行命令。 |
pfcount key [key...] | 返回给定 HyperLogLog 的基数估算值 |
pfmerge destkey sourcekey [sourcekey...] | 将多个 HyperLogLog 合并为一个 HyperLogLog |
- 应用
- 统计注册 IP 数
- 统计每日访问 IP 数
- 统计页面实时 UV 数
- 统计在线用户数
- 统计用户每天搜索不同词条的个数
Geo
- 命令
命令 | 说明 |
---|---|
geoadd key longitude latitude member [longitude latitude member ...] | 将指定的地理空间位置(纬度、经度、名称)添加到指定的key中 |
geodist key member1 member2 [unit] | 返回两个给定位置之间的距离。如果两个位置之间的其中一个不存在,那么命令返回空值 |
geopos key member [member ...] | 从key里返回所有给定位置元素的位置(经度和纬度) |
geohash key member [member ...] | 返回一个或多个位置元素的 Geohash 表示。通常使用表示位置的元素使用不同的技术,使用Geohash位置52点整数编码。由于编码和解码过程中所使用的初始最小和最大座标不同,编码的编码也不同于标准。此命令返回一个标准的Geohash |
georadius key longitude latitude radius m\km\ft\mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] | 以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素 |
GEORADIUSBYMEMBER key member radius m\km\ft\mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] | 与 GEORADIUS 命令一样, 都可以找出位于指定范围内的元素, 但是 GEORADIUSBYMEMBER 的中心点是由给定的位置元素决定的 |
GeoHash 算法
比较通用的地理位置距离排序算法是 GeoHash 算法,Redis 也使用 GeoHash 算法。GeoHash 算法将二维的经纬度数据映射到一维的整数,这样所有的元素都将在挂载到一 条线上,距离靠近的二维座标映射到一维后的点之间距离也会很接近。当我们想要计算「附近的人时」,首先将目标位置映射到这条线上,然后在这个一维的线上获取附近的点就行了。
- 应用
- 地图距离:利用redis的GEO地理定位计算可以得出,数据库中存放商家的经纬度(座标),通过geo计算得出距离
Pub/Sub
Pub/Sub功能(means Publish, Subscribe)即发布及订阅功能。基于事件的系统中,Pub/Sub是目前广泛使用的通信模型,它采用事件作为基本的通信机制,提供大规模系统所要求的松散耦合的交互模式:订阅者(如客户端)以事件订阅的方式表达出它有兴趣接收的一个事件或一类事件;发布者(如服务器)可将订阅者感兴趣的事件随时通知相关订阅者。
Redis的pub/sub是一种消息通信模式,主要的目的是解除消息发布者和消息订阅者之间的耦合,Redis作为一个pub/sub的server,在订阅者和发布者之间起到了消息路由的功能。
BloomFilter 布隆过滤器
RedisSearch
Redis-ML 机器学习模型服务器
Redis分布式锁
常用的分布式锁有三种解决方案:
- 基于数据库实现
- 基于zookeeper的临时序列化节点实现
- redis实现
先用 setnx 抢到锁,抢到之后,使用 setnx 和 expire 合成一条命令对 redis 进行原子写,保证写数据和设置过期时间原子操作,避免请求中断带来的异常。命令如下:
# shell
# 从 Redis 2.6.12 版本开始, SET 命令的行为可以通过一系列参数来修改
# EX second :设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。
# PX millisecond :设置键的过期时间为 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX key millisecond value 。
# NX :只在键不存在时,才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。
# XX :只在键已经存在时,才对键进行设置操作。
## 时间默认毫秒
$ set key value nx px 10000
# lua
if redis.call('get', KEYS[1]) == ARGV[1]
then
return redis.call('del', KEYS[1])
else
return 0
end
// java
public static boolean lock(String key,String lockValue,int expire){
if(null == key){
return false;
}
try {
Jedis jedis = getJedisPool().getResource();
String res = jedis.set(key,lockValue,"NX","EX",expire);
jedis.close();
return res!=null && res.equals("OK");
} catch (Exception e) {
return false;
}
}
Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如何将它们全部找出来
- 使用
keys
指令获取指定模式的 key 列表 - 若该 redis 正在提供线上业务,直接使用
keys
会导致线程阻塞,因为 redis 是单线程的,此时线上服务会停顿,知道指定执行完毕。此时,可使用scan
指令,scan
可无责色的提取指定模式的 key 列表,但有一定的重复概率,在客户端进行去重即可,但比直接使用keys
指令耗时长。
SCAN
是一个基于游标的迭代器。这意味着命令每次被调用都需要使用上一次这个调用返回的游标作为该次调用的游标参数,以此来延续之前的迭代过程。
当 SCAN
指令的游标参数(即cursor)被设置为 0 时, 服务器将开始一次新的迭代, 而当服务器向用户返回值为 0 的游标时, 表示迭代已结束。
# KEYS pattern
redis> mset aa 1 bb 2 cc 3
redis> mset a1 1 b1 2 c1 3
redis> keys a*
1) "a1"
2) "aa"
redis> keys ?1
1) "a1"
2) "c1"
3) "b1"
# SCAN cursor [MATCH pattern] [COUNT count]
redis> scan 0 MATCH a* COUNT 100
Redis 如何实现异步队列
- 使用 List 实现异步消息列表,使用
lpush/rpop
或rpush/lpop
生产/消费消息。当无消息时,适当sleep
线程后重试。 - 在不使用
sleep
时,使用blpop/brpop
阻塞读。阻塞读在队列没有数据的时候,会立即进入休眠状态,一旦数据到来,则立即醒来,延迟几乎为零。但若长时间无新消息到来,该线程长时间阻塞就造成了空闲连接,此时服务器通常会主动断开连接,因此编写代码时需要捕获异常及重试。 - 生产一次消费多次:使用 pub/sub 主题订阅者模式,可实现 1:N 的消息队列。
- pub/sub 缺点:在消费者离线时,生产的消息将丢失,可考虑 RabbitMQ 等。
- 如何实现延时队列:延时队列是在客户端处理请求时加锁失败时,将当前冲入的请求集中放到另一个队列进行延后处理以避免冲突。延时队列可使用
zset
实现,消息内容作为key 调用zadd
来生产消息,消费者用zrangebyscore
指令获取N秒之前的数据轮询进行处理。多线程执行时可能发生一个线程抢到任务但无法获取 redis 命令执行的情况,因此需要线程使用lua
脚本在服务端进行原子执行。
Redis有大量key在同一时间过期,如何处理
Redis 在同一时间有大量 key 过期,可能会出现短暂的卡顿。
这类事件以预防为主,通常需要在设置时将 key 的过期时间加入随机值,核心思想是使过期时间尽量分散。
Redis如何做持久化
AOF(AppendOnly File):命令 bgrewriteaof
,用于异步执行一个 AOF 文件重写操作,进行增量持久化。旧 AOF 在 Bgrewriteaof 成功前不会被修改。从 2.4 开始,AOF 自行触发,bgrewriteaof
仅用于手动触发重写操作。
RDB(Redis Bgsave):命令 bgsave
,用于后台异步保存当前数据库的数据到磁盘,进行全量镜像持久化。
使用 RDB 耗时较长,实时性不足,可能会导致一定时间内的数据丢失,所以需要配合 AOF。Redis 重启时,优先使用 AOF 回复内存状态,如果没有 AOF,使用 RDB 恢复。 通常使用方式,AOF 保证数据不丢失,RDB 做冷备。
- 若 AOF 文件过大,导致恢复时间过长怎么办? Redis 会定期进行 AOF 文件重写,重写会创建一个基于当前 AOF 文件的体积优化版本。
- 混合持久化 混合持久化结合了 RDB 和 AOF 的有点,写入时,先把当前的数据以 RDB 的形式写入文件开头,再将后续操作命令以 AOF 格式写入文件,这样既能保证 Redis 重启速度,又能降低数据丢失风险。
- 宕机影响 丢失数据取决于 AOF 的 sync 属性配置,若不要求性能,可对每条命令都执行一次 sync 磁盘同步。但通常使用定时 sync,如 1秒1次,则宕机最多丢失 1 秒数据。
Pipeline有什么好处
Pipeline指的是管道技术,指的是客户端允许将多个请求依次发给服务器,过程中不需要等待请求的回复,减少请求执行过程中的往返时间,等全部请求执行完成后再读取结果。
Pipeline 支持多命令,本身非原子性,配合 multi
、exec
实现事务控制。
Redis的同步机制
旧版实现
Redis 复制功能分为同步(sync)和命令传播(command propagate) 两个过程。
- 同步:当客户端向 slave 发送
slaveof
命令要求 slave 同步 master 时,slave 首先执行同步操作,同步 master 当前数据库状态。- slave 向 master 发送
sync
命令 - master 收到
sync
后,执行bgsave
生成 RDB 镜像,同时使用一个缓冲区记录新之星的所有操作 - master 将 RDB 发送给 slave,slave 通过该 RDB 文件恢复数据
- master 将缓冲区的命令发送给 slave,slave 进行状态更新
- slave 向 master 发送
- 命令传播:在同步操作执行完成后,主从达到状态一致,但此时客户端新命令到达 master 后,主从一直被打破,为了保证主从一致,master 将把这些新执行的命令发送给 slave。
缺陷:Redis 主从复制时若发生断线重复制现象,master 对重连后的复制命令依然执行全量 bgsave
,若两次复制操作间隔时间很短,则相当于第二次 bgsave
仅比第一次多了有限少数量的新命令,这对服务器会造成一些性能损耗。因为,sync
是一个非常消耗资源的操作,通常体现在生成 RDB 时的 CPU、内存、磁盘消耗,RDB 发送给 slave 时的网络消耗,slave 恢复 RDB 时的性能消耗等。
因此,Redis 需要保证在有必要的时候执行 sync
。
新版实现(since 2.8)
为解决旧版断线重复制的低效问题,从 2.8 开始使用 psync
执行复制时的同步操作。
psync
有完整重同步(full resynchronuzation)和部分重同步(partial resynchronization)两种模式。
- 完整重同步:用于初次复制
- 部分重同步:用于断线后重复制
部分重同步的设计核心有三块:
- master 的复制偏移量(replication offset)和 slave 的复制偏移量
- master 的复制积压缓冲区(replication backlog)
- 服务器的运行 ID(run ID)
Redis集群原理
Redis 有三种集群模式:
- 主从模式
- Sentinel(哨兵) 模式
- Cluster(集群) 模式
主从模式
主从模式特点:
- 主库可读写,当读写操作导致数据变化时,变化会自动将数据同步给从库
- 从库一般只读
- 一个 master 可有多个 slave,一个 slave 只有一个 master
- slave 宕机不影响其他机器,重启该 slave 将触发 master 数据同步
- master 宕机后,集群不会重选 master,此时 slave 可继续读,但集群不再提供写,master 重启后集群恢复写
安全设置(当 master 设置密码后):
- 客户端访问 master 需要密码
- 启动 slave 需要密码,可将密码直接配置到配置文件中
- 客户端访问 slave 不需要密码
缺点:
- master 节点在集群中唯一
- master 宕机后,集群无法继续提供写服务
Sentinel 模式
Sentinel 模式特点:
- sentinel 建立在主从模式上,如果只有一个 redis,sentinel 将失去意义
- 当 master 宕机,sentinel 会从 slave 中选择一个新的 master
- 当旧 master 重新启动后,它将作为 slave 接收新 master 的同步数据
- sentinel 是一个进程,可能该进程会挂掉,所以 sentinel 会启动多个而形成一个 sentinel 集群
- 多 sentinel 配置时,sentinel 之间会自动监控
- 当主从模式需要密码时,sentinel 也需要密码
- 一个 sentinel 或 sentinel 集群可以管理多个主从集群,多个 sentinel 也可以监控同一个主从集群
- sentinel 与 Redis 最好部署在不同机器上
Sentinel 模式集群工作机制:
- 每个 sentinel 每秒向它所知的 master、slave 以及其他 sentinel 实例发送一个 PING
- 在一般情况下,每个 sentinel 每 10 秒向它所知的 master、slave 发送 INFO
- 当一个实例距离最后一次有效回复 PING 的时间超过
down-after-milliseconds
配置时间,该实例被 sentinel 标记为主观下线 - 当一个 master 被标记为主观下线,则监控该 master 的 sentinel 每秒一次确认该 master 的确进入了主观下线
- 当有足够多的 sentinel 在指定时间内确认该 master 进入了主观下线,则该 master 被 sentinel 标记为客观下线
- 当 master 被标记为客观下线,sentinel 向该 master 的所有 slave 发送 INFO 的频率变为每秒一次
- 若没有足够的 sentinel 同意该 master 已经下线,该 master 的客观下线状态解除
- 若该 master 重新向 sentinel 的 PING 返回有效回复,则其主观下线状态解除
缺点: 当数据量大于一台服务器承受能力时,主从模式或 sentinel 模式将不能满足需求,此时需要对存储的数据进行分片,将数据存储到多个 Redis 实例中。
Cluster 模式
Cluster 模式解决了单机 Redis 容量有限的问题,将 Redis 的数据根据一定规则分配到多台机器。
Cluster 模式集成了主从模式和 sentinel 模式的有点,通过 cluster 可实现主从么 master 重选功能。每个集群至少需要三个主数据库才能正常运行。
Cluster 模式集群特点:
- 多个 Redis 阶段网络互联,数据共享
- 所有节点都是一主一从(也可一主多从),其中从不提供服务,仅作为备用
- 不支持同时处理多个 key(如 mset/mget),因为 redis 需要把 key 均匀分不到各个节点,并发量很高的情况下同时创建 k-v 会降低性能并导致不可预测的行为
- 支持在线增减节点
- 客户端可连接任何一个主节点进行读写
- 新增的节点都以 master 加入集群