Redis 的单线程主要是指 Redis 的网络 IO 和键值对读写是由一个线程来完成的,这也是 Redis 对外提供键值存储服务的主要流程。但 Redis 的其他功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执行的。
因为它所有的数据都在内存中,所有的运算都是内存级别的运算,而且单线程避免了多线程的切换性能损耗问题。正因为 Redis 是单线程,所以要小心使用 Redis 指令,对于那些耗时的指令(比如keys),一定要谨慎使用,一不小心就可能会导致 Redis 卡顿。
Redis的IO多路复用:Redis利用epoll来实现IO多路复用,将连接信息和事件放到队列中,依次放到文件事件分派器,事件分派器将事件分发给事件处理器。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q2IEWBmX-1662477135648)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/79d8c0aa886446509e41308de9fc2e86~tplv-k3u1fbpfcp-zoom-1.image)]
客户端可以一次性发送多个请求而不用等待服务器的响应,待所有命令都发送完后再一次性读取服务的响应,这样可以极大的降低多条命令执行的网络传输开销,管道执行多条命令的网络开销实际上只相当于一次命令执行的网络开销。需要注意到是用pipeline方式打包命令发送,redis必须在处理完所有命令前先缓存起所有命令的处理结果。打包的命令越多,缓存消耗内存也越多。所以并不是打包的命令越多越好。pipeline中发送的每个command都会被server立即执行,如果执行失败,将会在此后的响应中得到信息;也就是pipeline并不是表达“所有command都一起成功”的语义,管道中前面命令失败,后面命令不会有影响,继续执行。
Redis在2.6推出了脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行。使用脚本的好处如下:
1、减少网络开销:本来5次网络请求的操作,可以用一个请求完成,原先5次请求的逻辑放在redis服务器上完成。使用脚本,减少了网络往返时延。这点跟管道类似。
2、原子操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。管道不是原子的,不过redis的批量操作命令(类似mset)是原子的。
3、替代redis的事务功能:redis自带的事务功能很鸡肋,报错不支持回滚,而redis的lua脚本几乎实现了常规的事务功能,支持报错回滚操作,官方推荐如果要使用redis的事务功能可以用redis lua替代。
--- 查看redis最大连接
config get maxclients
--- Info:查看redis服务运行信息
--- Server 服务器运行的环境参数
--- Clients 客户端相关信息
--- Memory 服务器运行内存统计数据
--- Persistence 持久化信息
--- Stats 通用统计数据
--- Replication 主从复制相关信息
--- CPU CPU 使用情况
--- Cluster 集群信息
--- KeySpace 键值对统计数量信息
参数名 | 含义 | 默认值 | 建议 | |
---|---|---|---|---|
1 | maxTotal | 资源池中最大连接数 | 8 | |
2 | maxIdle | 资源池允许最大空闲 的连接数 | 8 | 连接池的最佳性能是maxTotal = maxIdle |
3 | minIdle | 资源池确保最少空闲 的连接数 | 0 | |
4 | blockWhenExhausted | 当资源池用尽后,调 用者是否要等待。只 有当为true时,下面 的maxWaitMillis才会 生效 | true | 建议使用默认值 |
5 | maxWaitMillis | 当资源池连接用尽 后,调用者的最大等 待时间(单位为毫秒) | -1:表示永不超时 | 不建议使用默认值 |
6 | testOnBorrow | 向资源池借用连接时 是否做连接有效性检 测(ping),无效连接 会被移除 | false | 业务量很大时候建议 设置为false(多一次 ping的开销)。 |
7 | testOnReturn | 向资源池归还连接时 是否做连接有效性检 测(ping),无效连接 会被移除 | false | 业务量很大时候建议 设置为false(多一次 ping的开销)。 |
8 | jmxEnabled | 是否开启jmx监控,可 用于监控 | true | 建议开启,但应用本 身也要开启 |
注:如果系统启动完马上就会有很多的请求过来,那么可以给redis连接池做预热,比如快速的创建一 些redis连接,执行简单命令,类似ping(),快速的将连接池里的空闲连接提升到minIdle的数量。
Redis键:虽然有字符串、整型和浮点型,实际所有键都是字符串类型(sds—simple dynamic string),并且键是唯一的。好处:二进制安全的数据结构(兼容了不同语言的数据);提供了内存预分配机制,避免了频繁的内存分配;兼容C语言的函数库。图解如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PX2gtPKk-1662477135648)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/31fc7017d6684ca1b44edf2ddeda2c25~tplv-k3u1fbpfcp-zoom-1.image)]
Redis值分五种数据类型,分别是:String (字符串)、List (列表)、Hash (哈希)、Set (集合) 和 Zset (有序集合)
--- 查看key value存储的类型
type key
--- 查看key value存对应数据底层的编码
object encoding key
结构底层实现结构 | ||||
---|---|---|---|---|
数据量比较小的结构 | 数据量比较大的结构 | 可修改配置 | ||
String | int/empstr存储小于 44 个字节的字符串 | raw | ||
Hash | 数据结构底层实现为一个字典(dict),也是RedisBb用来存储K-V的数据结构 | ziplist | hashtable | hash-max-ziplist-entries 512hash-max-ziplist-value 64 |
List | 有序的数据结构 | ziplist | quicklist(双端链表) | list-max-ziplist-size -2(单个最大8kb)list-compress-depth 1(从第几个压缩) |
Set | 无序的自动去重的集合数据类型 | intset | hashtable | set-max-intset-entries 512 |
Zset | 有序的自动去重的集合数据类型 | ziplist | skiplist(跳表) | zset-max-ziplist-entries 128zset-max-ziplist-value 64 |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QgjaaZD3-1662477135649)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3fad2537b3244824a41af8dd0b5ba086~tplv-k3u1fbpfcp-zoom-1.image)]
\
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RmbSWxlK-1662477135649)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1d40f21b7ae34139baa6e03b8556c151~tplv-k3u1fbpfcp-zoom-1.image)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LfwcvKHc-1662477135649)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8fb8f13cb26a4c9d8959e7d81e2171f4~tplv-k3u1fbpfcp-zoom-1.image)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BUcK1XDr-1662477135650)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9d797ebe1cbc46b2b7527bc3a05f9544~tplv-k3u1fbpfcp-zoom-1.image)]
--- 单值缓存
SET key value
GET key
--- 对象缓存
SET user:1 value(json格式数据)
MSET user:1:name zhuge user:1:balance 1888
--- 分布式锁
SETNX product:10001 true
SETNX product:10001 true
--- 执行业务操作
DEL product:10001
--- 防止程序意外终止导致死锁
SET product:10001 true ex 10 nx
--- 对象缓存
HMSET user {userId}:name zhuge {userId}:balance 1888
--- 电商购物车
--- 数据结构
Stack(栈) :LPUSH + LPOP
Queue(队列):LPUSH + RPOP
Blocking MQ(阻塞队列):LPUSH + BRPOP
--- 微博和微信公号消息流(队列)
--- 微信抽奖小程序
SADD key member...
--- 查看参与抽奖所有用户
SMEMBERS key
--- 抽取count名中奖者
SPOP key [count]
--- 微信微博点赞,收藏,标签
--- 点赞
SADD key member...
--- 取消点赞
SREM key member...
--- 检查用户是否点过赞
SISMEMBER key member...
--- 获取点赞的用户列表
SMEMBERS key
--- 获取点赞用户数
SCARD key
sinter 共同关注
sdiff 第一个和后面不同之处(可能关注)
--- 添加scores
zadd key 1 name
--- 点击新闻
ZINCRBY key 1 守护香港
--- 展示当日排行前十
ZREVRANGE key 0 9 WITHSCORES
--- 七日搜索榜单计算
ZUNIONSTORE newkey 7 key01 key02...
--- 展示七日排行前十
ZREVRANGE key 0 9 WITHSCORES
zrange(正序,展示) zchen 0 -1 withscores :查看所有并且带上scores
zincrby(zincrby zchen 1 zchen01):指定值修改他的scores,用户每点击一次,就改变scores + 1
zrevrange (倒序,展示当日排行前十)
zunionstore destination number key key ... weight number ...
自动保存:在默认情况下,Redis 将内存数据库快照保存在名字为 dump.rdb 的二进制文件中。可以对 Redis 进行设置,让它在“ N 秒内数据集至少有 M 个改动”这一条件被满足时, 自动保存一次数据集。
--- 60 秒内有至少有 1000 个键被改动,会自动保存一次数据集(配置文件中)
save 60 1000
手动保存:还可以手动执行命令生成RDB快照,进入redis客户端执行命令save或bgsave可以生成dump.rdb文件,每次命令执行都会将所有redis内存快照到一个新的rdb文件里,并覆盖原有rdb快照文件。
--- 客户端执行命令
save/bgsave
手动保存命令区别 | ||
---|---|---|
save | bgsave | |
IO类型 | 同步 | 异步(COW,使用操作系统写时复制) |
是否阻塞Redis其他命令 | 是 | 否(在生成子进程执行调用fork函数时会有短暂阻塞) |
复杂度 | O(n) | O(n) |
优点 | 不会消耗额外内存 | 不阻塞客户端其他命令 |
缺点 | 阻塞客户端其他命令 | 消耗内存 |
快照功能并不是非常耐久(durable):如果 Redis 因为某些原因而造成故障停机,那么服务器将丢失最近写入、且仍未保存到快照中的那些数据。从 1.1 版本开始, Redis 增加了一种完全耐久的持久化方式: AOF 持久化,将修改的每一条指令记录进文件。
保存的是一种resp协议格式数据。需要注意的,如果执行带过期时间的set命令,aof文件里记录的是并不是执行的原始命令,而是记录key过期的时间戳。
--- 可以通过修改配置文件来打开 AOF 功能
appendonly yes
从现在开始, 每当 Redis 执行一个改变数据集的命令时(比如 SET), 这个命令就会被追加到 AOF 文件的末尾。这样的话, 当 Redis 重新启时, 程序就可以通过重新执行 AOF 文件中的命令来达到重建数据集的目的。
你可以配置 Redis 多久才将数据 fsync 到磁盘一次(推荐(并且也是默认)的措施为每秒 fsync 一次,这种 fsync 策略可以兼顾速度和安全性);
--- 每次有新命令追加到 AOF 文件时就执行一次 fsync ,非常慢,也非常安全。
appendfsync always:
--- 每秒 fsync 一次,足够快,并且在故障时只会丢失 1 秒钟的数据。
appendfsync everysec:
--- 从不 fsync ,将数据交给操作系统来处理。更快,也更不安全的选择。
appendfsync no:
--- 客户端手动触发(异步)
bgrewriteaof
RDB | AOF | |
---|---|---|
启动优先级 | 低 | 高 |
体积 | 小 | 大 |
恢复速度 | 快 | 慢 |
数据安全性 | 容易丢数据 | 根据策略决定 |
AOF在重写(aof文件里可能有太多没用指令,所以aof会定期根据内存的最新数据生成aof文件)时将重写这一刻之前的内存rdb快照文件的内容和增量的 AOF修改内存数据的命令日志文件存在一起,都写入新的aof文件,新的文件一开始不叫appendonly.aof,等到重写完新的AOF文件才会进行改名,原子的覆盖原有的AOF文件,完成新旧两个AOF文件的替换;
--- 执行rewrite条件
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 举例说明上述配置的意义
比如说上一次 rewrite 之后,日志文件大小是 128 MB
如果发现日志文件增长的比例超过了100%(对应第一条配置),比如日志文件变为 300MB
然后就去跟 64 MB(对应第二条配置)做比较,如果大于 64 MB,才会去触发rewrite
AOF根据配置规则在后台自动重写,也可以人为在客户端执行命令bgrewriteaof重写AOF。 于是在 Redis 重启的时候,可以先加载 rdb 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,重启效率因此大幅得到提升。
--- 开启混合持久化
aof-use-rdb-preamble yes
混合持久化aof文件结构:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FGbtzkGL-1662477135650)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/da712a22e4f441c8b6b83e0c95877b25~tplv-k3u1fbpfcp-zoom-1.image)]
Redis数据备份策略:
写crontab定时调度脚本,每小时都copy一份rdb或aof的备份到一个目录中去,仅仅保留最近48小时的备份。
每天都保留一份当日的数据备份到一个目录中去,可以保留最近1个月的备份。
每次copy备份的时候,都把太旧的备份给删了。
每天晚上将当前机器上的备份复制一份到其他机器上,以防机器损坏。
当 Redis 内存超出物理内存限制时,内存的数据会开始和磁盘产生频繁的交换 (swap)。交换会让 Redis 的性能急剧下降,对于访问量比较频繁的 Redis 来说,这样龟速的存取效率基本上等于不可用。
在生产环境中我们是不允许 Redis 出现交换行为的,为了限制最大使用内存,Redis 提供了配置参数 maxmemory 来限制内存超出期望大小。
当实际内存超出 maxmemory 时,Redis 提供了几种可选策略 (maxmemory-policy) 来让用户自己决定该如何腾出新的空间以继续提供读写服务。
Redis对于过期键有三种清除策略:
①、被动删除:当读/写一个已经过期的key时,会触发惰性删除策略,直接删除掉这个过期 key
②、主动删除:由于惰性删除策略无法保证冷数据被及时删掉,所以Redis会定期主动淘汰一 批已过期的key。
默认每隔 100 毫秒(1秒10次)就会从全局的过期哈希表中随机取出 20 个 key,然后删除其中过期的 key,如果过期 key 的比例超过了 25%,则继续重复此过程,直到过期 key 的比例下降到 25% 以下,或者这次任务的执行耗时超过了 25 毫秒,才会退出循环
③、当前已用内存超过maxmemory限定时,触发主动清理策略 主动清理策略在Redis 4.0 之前一共实现了 6 种内存淘汰策略,在 4.0 之后,又增加了 2 种策 略,总共8种:
缓存淘汰策略 | |
---|---|
noeviction( 默认的淘汰策略 ) | 不会继续服务写请求 (DEL 请求可以继续服务),读请求可以继续进行。这样可以保证不会丢失数据,但是会让线上的业务不能持续进行。 |
volatile-random | 随机淘汰部分带有过期时间的key |
volatile-ttl | 尝试淘汰带有过期时间的Key,key 的剩余寿命 ttl 的值,ttl 越小越优先被淘汰 |
volatile-lru | 最近最少使用 |
volatile-lfu | 带有过期时间的最不常使用(热点数据) |
allkeys-random | 淘汰的策略是随机的 key |
allkeys-lru | 全体的 key 集合 |
allkeys-lfu | 最不常使用(热点数据) |
当存在热点数据时,LRU的效率很好,但偶发性的、周期性的批量操作会导致LRU命中率急剧下降,缓存污染情况比较严重。这时使用LFU可能更好点。
根据自身业务类型,配置好maxmemory-policy(默认是noeviction),推荐使用volatile-lru。如果不设置最大内存,当 Redis 内存超出物理内存限制时,内存的数据会开始和磁盘产生频繁的交换 (swap),会让 Redis 的性能急剧下降。 当Redis运行在主从模式时,只有主结点才会执行过期删除策略,然后把删除操作”del key”同步到从结点删除数据。
如果 value 值是一个整数,还可以对它进行自增操作。自增是有范围的,它的范围是 signed long 的最大最小值,超过了这个值,Redis 会报错。
keys:全量遍历键,用来列出所有满足特定正则字符串规则的key,当redis数据量比较大时,性能比较差,要避免使用
scan:渐进式遍历键, scan 参数提供了三个参数,第一个是 cursor 整数值,第二个是 key 的正则模式,第三个是遍历的 limit hint。第一次遍历时,cursor 值为 0,然后将返回结果中第一个整数值作为下一次遍历的 cursor。一直遍历到返回的 cursor 值为 0 时结束。
scan 0 match key99* count 100