1.Redis 为什么是单线程? 为什么单线程还能这么快?
单线程可能 防止线程切换和竞态产生的耗费 ,而且 单线程能够简化数据结构和算法的实现
至于单线程还快,是因为 Redis 是基于内存的数据库, 内存响应速度是很快 的,并且 采纳 epoll 作为 I / O 多路复用技术 ,再加上 Redis 本身的 事件处理模型将 epoll 中的连贯、读写、敞开都转换为事件 , 不在网络 I / O 上节约过多工夫
epoll 是为了解决 Linux 内核解决大量文件描述符提出的计划,属于 Linux 下多路 I / O 复用接口中 select/poll 的加强, 常常用于 Linux 下高并发服务型程序,特地是大量并发连贯中只有少部分处于沉闷下的状况,能进步 CPU 利用率。
epoll 采纳事件驱动,只须要遍历那些被内核 IO 事件异步唤醒之后退出到就绪队列并返回到用户空间的描述符汇合
epoll 提供两种触发模式,程度触发(LT)和边际触发(ET), 目前效率最高的 IO 操作计划是:epoll+ET+ 非阻塞 IO 模型
2.Redis 应用场景
最多的利用于缓存,其余能够用于排行榜、计数器、音讯队列等
3.Redis 淘汰策略
Redis 3.0 版本反对的策略
- volatile-lru:从设置过期工夫的数据集(server.db[i].expires)中挑选出 最近起码应用 的数据淘汰。没有设置过期工夫的 key 不会被淘汰 ,这样就能够在减少内存空间的同时 保障须要长久化的数据不会失落。
- volatile-ttl:除了淘汰机制采纳 LRU,策略基本上与 volatile-lru 类似,从设置过期工夫的数据集(server.db[i].expires)中筛选 将要过期的数据淘汰,ttl 值越大越优先被淘汰。
- volatile-random:从已设置过期工夫的数据集(server.db[i].expires)中 任意抉择数据淘汰 。当 内存达到限度无奈写入非过期工夫的数据集时,能够通过该淘汰策略在主键空间中随机移除某个 key。
- allkeys-lru:从数据集(server.db[i].dict)中筛选 最近起码应用 的数据淘汰,该策略 要淘汰的 key 面向的是整体 key 汇合,而非过期的 key 汇合。
- allkeys-random:从数据集 (server.db[i].dict)中抉择 任意数据淘汰。
- no-enviction:禁止驱赶数据,也就是当 内存不足以包容新入数据时,新写入操作就会报错 ,申请能够持续进行,线上工作也不能继续进行,采纳 no-enviction 策略能够 保证数据不被失落,这也是零碎默认的一种淘汰策略。
4.Redis 长久化机制
1.RDB 长久化:
把 以后过程数据生成快照保留到硬盘 的过程,触发形式有 手动触发 和主动触发
手动触发命令:save 和 bgsave 命令
save 命令
: 阻塞 以后 Redis 服务,直到 RDB 过程实现为止 , 不倡议线上环境 应用bgsave 命令
:Redis 过程执行 fork 操作创立子过程,RDB 长久化过程由子过程负责 ,实现后主动完结, 阻塞只产生在 fork 阶段 ,个别工夫很短
主动触发:- 应用 save 相干配置,如 ”save m n”。示意 m 秒内数据集存在 n 次批改时,主动触发
- 如果从节点执行全量复制操作,主节点主动执行 bgsave 生成 RDB 文件并发送给从节点
- 执行debug reload 命令从新加载 Redis 时,也会主动触发 save 操作
- 默认状况下 执行 shutdown 命令时,如果没有开始 AOF 长久化则主动执行 bgsave
RDB 文件保留在 dir 配置指定的目录下,文件名通过 dbfilename 配置指定,能够通过执行 config set dir {newDir} 和 config set dbfilename {newFileName} 运行期动静执行,当下次运行时 RDB 文件会保留到新目录
- 遇到磁盘损坏或写满时,能够通过 config set dir 在线批改文件门路到可用的磁盘门路,之后执行 bgsave 进行磁盘切换
- redis默认采纳 LZF 算法对生成的 RDB 文件进行压缩 ,压缩后文件元小于内存大小,默认开启,能够通过参数 config set rdbcompression {yes|no} 动静批改
RDB 长处:
- RDB 是一个 紧凑的二进制文件 ,代表 Redis 在某个工夫点上的 数据快照 , 适宜备份,全量复制 等场景
- Redis 加载 RDB复原数据 远远 快于 AOF 形式
RDB 毛病:
- 没方法做到实时长久化 / 秒级长久化
- RDB 文件应用 特定二进制 保留,存在兼容问题
2.AOF 长久化
以 独立日志的形式记录每次写命令 ,写入的内容间接是 文本协定格局 , 重启时再从新执行 AOF 文件中的命令 达到复原数据的目标,解决了数据长久化实时性问题
开启 AOF:appendonly yes, 默认不开启
AOF 文件名:appendfilename 配置,默认 appendonly.aof
保留门路:同 RDB,通过 dir 配置
AOF 工作流程:
- 所有的 写入命令追加到 aof_buf(缓冲区)中
- AOF 缓冲区依据对应的策略向硬盘做同步操作
- 随着 AOF 文件越来越大,须要 定期对 AOF 文件进行重写,压缩,父过程执行 fork 创立子过程,由子过程依据内存快照执行 AOF 重写,父进行持续响应前面的命令,在子过程实现重写后,父过程再把新增的写入命令写入到新的 AOF 文件中
- Redis 服务 重启 , 加载 AOF 文件进行数据恢复
AOF 为什么间接采纳文本协定格局?
- 文本协定具备良好的 兼容性
- 开启 AOF 后,所有写入命令都蕴含追加操作,间接采纳协定格局,防止二次解决 开销
- 文本协定具备 可读性,不便间接批改和解决
AOF 为什么把命令追加到 aof_buf 中
- 写入缓存区 aof_buf 中,能进步性能,并且 Redis 提供了多种缓存区同步硬盘策略
重写后的 AOF 文件为什么能够变小?
- 过程内曾经 超时的数据不再写入文件
- 旧的 AOF 文件含有有效命令,重写应用过程内数据间接生成,新的 AOF 文件只保留最终数据的写入命令
- 多条写命令能够合并为一个,为了避免溢出,以 64 个元素为界拆分为多条
AOF 缓冲区同步策略,通过参数 appendfsync 管制
可配置值 | 阐明 | 其余 |
---|---|---|
always | 命令写入 aof_buf 后调用零碎 fsync 操作同步到 AOF 文件,fsync 实现后线程返回 | 每次写入都要同步 AOF 文件,在个别的 SATA 硬盘很难达到高性能 |
everysec | 命令写入 aof_buf 后调用零碎 write 操作,write 实现后线程返回。fsync 同步操作由线程每秒调用一次(倡议策略) | 默认同步策略 |
no | 命令写入 aof_buf 后调用零碎 write 操作,不对 AOF 文件做 fsync 同步,同步硬盘操作由操作系统负责,通常同步周期最长 30 秒 | 操作系统每次同步 AOF 文件的周期不可控,而且会加大每次同步硬盘的数据量,尽管晋升了性能,但数据安全性无奈保障 |
AOF 手动触发:调用 bgrewriteaof 命令
主动触发:有两个参数
- auto-aof-rewrite-min-size: 示意运行时 AOF重写时文件最小体积,默认 64MB
- auto-aof-rewrite-percentage: 代表 以后 AOF 文件空间(aof_current_size)和上一次重写后 AOF 文件空间 (aof_base_size) 的比值
主动触发机会 =aof_current_size > auto-aof-rewrite-min-size && (aof_current_size -aof_base_size) / aof_base_size >= auto-aof-rewrite-percentage
RDB 和 AOF 同时开启,并且 AOF 文件存在,优先加载 AOF 文件
AOF 文件谬误,能够通过 redis-check-aof-fix 修复
3.Redis 数据类型
字符串
key 是字符串类型,字符串类型的值能够是字符串、数字、二进制(最大不能超过 512MB),是动静字符串,外部通过预调配冗余空间的形式来缩小内存的频繁调配
命令 | 解释 | 备注 |
---|---|---|
set | 设置值 | key value [ex seconds] 为键设置秒级过期工夫 [px milliseconds] 为键设置毫秒级别的过期工夫 [nx\xx] nx 键必须不存在才能够设置胜利 xx 键必须存在才能够设置胜利 |
mset | 批量设置值 | key value [key value …] |
mget | 批量获取值 | key [key …] |
incr | 对值做自增操作 | 值不是整数,返回谬误 值是整数,返回自增后的后果 键不存在,依照值为 0 自增,返回后果为 1 |
decr | 自减 | key |
incrby | 自增指定数字 | key increment |
decrby | 自减指定数字 | key decrement |
incrbyfloat | 自增浮点数 | key increment |
append | 追加值 | key value |
strlen | 字符串长度 | key |
getset | 设置并返回原值 | key value |
setrange | 设置指定地位的字符 | key offeset value |
getrange | 获取局部字符串 | key start end |
哈希
一个键值对构造, 内部结构同 Java 的 HashMap, 数组 + 链表的构造,值只能存储字符串,编码是 ziplist 或者 hashtable。另外在 rehash 的时候,采纳定时工作渐进式迁徙内容
命令 | 解释 | 备注 |
---|---|---|
hset | 设置值 | key field value |
hsetnx | 相似 setnx | – |
hget | 获取值 | key field |
hdel | 删除一个或多个 field | key field [field …] |
hlen | 计算 field 个数 | – |
hmget | 批量获取 | key field [field …] |
hmset | 批量设置 | key field value [field value …] |
hexists | 判断 field 是否存在 | key field |
hkeys | 获取所有 field | key |
hvals | 获取所有 value | key |
hgetall | 获取所有的 field-value | key 如果元素很多,会造成阻塞,倡议应用 hscan |
hincrby hincrbyfloat | 相似 incrby / incrbyfloat | key field increment |
hstrlen | 计算 value 字符串长度 | key field |
列表
用来存储多个有序的字符串,一个列表最多能够存储 2 32 – 1 个元素, 列表中的元素能够是反复的,相当于 Java 中的 LinkedList, 是一个链表而不是数组, 底层是采纳 quicklist 构造,在数据量少的时候会应用 ziplist 压缩列表,数据量多的时候才应用 quicklist
命令 | 解释 | 备注 | |
---|---|---|---|
rpush | 从左边插入元素 | key value [value …] | |
lpush | 从右边插入元素 | key value [value …] | |
linsert | 向某个元素前或者后插入元素 | key before\ | after pivot value 从列表中找到等于 pivot 元素,在其前 before 或者 after 插入一个新的元素 value |
lrange | 获取指定范畴内的元素列表 | key start end 索引下标从左到右是 0 到 N -1, 从右到左是 - 1 到 -N; end 蕴含本身 |
|
lindex | 获取列表指定索引下标的元素 | key index | |
llen | 获取列表长度 | key | |
lpop | 从列表左侧弹出元素 | key | |
rpop | 从列表右侧弹出元素 | key | |
lrem | 删除指定元素 | key count value 从列表中找到等于 value 的元素进行删除,count>0, 从左到右,删除最多 count 个元素。count<0, 从右到左,删除最多 count 绝对值个元素。count=0, 删除所有 |
|
ltrim | 依照索引范畴修剪列表 | key start end | |
lset | 批改指定索引下标的元素 | key index newValue | |
blpop brpop |
弹出元素阻塞版 | key [key …] 多个键 timeount 阻塞工夫(秒) |
汇合 (set)
用来保留多个的字符串的元素,但和列表元素不一样的是,汇合中不容许有反复元素,并且汇合中的元素是无序的,不能通过索引下标获取元素,一个汇合最多能够存储 2 32 – 1 个元素
当汇合元素都是整数并且元素个数小于 set-max-intset-entries 配置 (默认 512 个) 时,应用 intset 编码缩小内存应用,否则应用 hashtable 编码
相当于 Java 中的 HashSet, 所有的 value 都是一个值 NULL
命令 | 解释 | 备注 |
---|---|---|
sadd | 增加元素 | key element [element …] |
srem | 删除元素 | key element [element …] |
scard | 计算元素个数 | key |
sismember | 判断元素是否在汇合中 | key element |
srandmember | 随机从汇合返回指定个数元素 | key [count] |
spop | 从汇合随机弹出元素, 会删除元素 | key [count] |
smembers | 获取所有元素 | key |
sinter | 求多个汇合的交加 | key [key …] |
sunion | 求多个汇合的并集 | key [key …] |
sdiff | 求多个汇合的差集 | key [key …] |
sinterstore suionstore sdiffstore |
将交加、并集、差集的后果保留 | destination key [key…] destination 指标汇合名称 汇合间的运算在元素比拟多的状况下会比拟耗时 |
有序汇合
汇合不能反复,但汇合中的元素能够依据 score 分数排序, 排序从小到大
当有序汇合的元素个数小于 zset-max-ziplist-entries(默认 128 个),同时每个元素的值都小于 zset-max-ziplist-value 配置(默认 64 字节)时,应用 ziplist 来作为外部编码,否则应用 skiplist 作为外部编码其相当于 Java 的 SortedSet 和 HashMap 结合体,外部实现’跳跃表‘的数据结构
skiplist 编码的有序汇合对象应用 zset 构造做为底层实现,zset 构造包含一个字典和一个跳跃表(依据成员查找分值和范畴操作的效率最高)
命令 | 解释 | 备注 |
---|---|---|
zadd | 增加成员 | key [NX\XX] [CH] [INCR]score member [score member …] [NX\XX] NX: 只有在元素不存在时候增加新元素; XX:更新曾经存在的元素,不增加元素 CH 返回有序汇合元素和分数发生变化的个数 incr 对 score 做减少,相当于 zincrby |
zcard | 计算成员个数 | key |
zscore | 计算某个成员的分数 | key member |
zrank | 计算成员排名, 分数从低到高 | key member |
zrevrank | 计算成员排名,分数从高到低 | key member |
zrem | 删除成员 | key member [member …] |
zincrby | 减少成员分数 | key increment member |
zrange zrevrange |
返回指定排名范畴的成员 | key start end [withscores] 显示分数 |
zrangebyscore zrevrangebyscore |
返回指定分数范畴的成员 | key min max [withscores] [limit offset count] key max min [withscores] [limit offset count] min 和 max 反对开区间(小括号)和闭区间(中括号),-inf 和 +inf 无限小和有限大士大 |
zcount | 返回指定分数范畴成员个数 | key min max |
zremrangebyrank | 删除指定排名内的升序元素 | key start end |
zremrangebyscore | 删除指定分数范畴的成员 | key min max |
zinterstore | 交加 | destination numkeys key [key …] [weights weight [weight …]] [aggregate sum\min\max] destination 交加计算结果保留到这个键 numkeys 须要做交加计算键的个数 key[key…] 须要做交加计算的键 weights weight[weight…] 每个键的权重,在做交加计算时,每个键中的每个 member 会将本人分数乘以这个权重,每个键的权重默认为 1 aggregate sum\min\max 计算成员交加后,分值能够依照 sum、min、max 做汇总,默认是 sum |
zunionstroe | 并集 | 参数同 zinterstore |
4.Pipeline
Pipeline(流水线) 机制 能将一组 Redis 命令进行组装 ,通过 一次 RTT 传输给 Redis, 再将这组 Redis 命令的 执行后果依照程序返回给客户端
原生批量命令与 Pipeline 比照:
- 原生批量命令是 原子 的,Pipeline 是 非原子 的
- 原生批量命令是 一个命令对应多个 key,Pipeline 反对 多个命令
- 原生批量命令是 Redis服务端反对实现 的,而 Pipeline 须要 服务端和客户端独特实现
5. 事务
redis 简略事务性能,将一组 须要一起执行的命令放到 multi 和 exec 两个命令之间 。multi 命令代表事务开始,exec 命令代表事务完结,他们之间的命令是 原子程序执行 的。如果要 进行事务的执行 , 用 discard 命令代替 exec 命令
watch 命令
用来 确保事务中的 key 没有被其余客户端批改过 ,才执行事务,否则不执行( 相似乐观锁)
6.Lua
两种办法:
-
eval
eval 脚本内容 key 个数 key 列表 参数列表eval 'return"helo "..KEYS[1].. ARGV[1]' 1 redis world
还能够应用 redis-cli –eval 间接执行文件
- evalsha
evalsha 脚本 sha1 值 key 个数 key 列表 参数列表
将 Lua 脚本加载到 Redis 服务端,失去该脚本的 SHA1 校验和,evalsha 应用 SHA1 作为参数执行对应的 Lua 脚本,防止每次发送脚本,脚本常驻与内存中
加载脚本到 Redis: redis-cli script load “$(cat ~/lua_test.lua)”
7.Redis 键过期删除策略
键过期,外部保留在 过期字典 expires 中 ,Redis 采纳 惰性删除 和定时工作删除 机制;
惰性删除 用于在客户端读取 带有超时属性的键 时,如果 曾经超过设置的过期工夫,会执行删除并返回空 ,但这种形式 如果键过期,而且始终没有被从新拜访,键始终存在
定时工作删除 :可能解决惰性删除问题,Redis 外部保护一个定时工作, 每秒运行 10 次 。依据键的 过期比例 ,应用 快慢两种速率 模式回收键,毛病是占用 CPU 工夫,在过期键多的时候会影响服务器的响应工夫和吞吐量
定期删除 :每隔一段时间执行一次删除过期键操作,通过限度删除操作的执行时长和频率来缩小影响,毛病 须要正当设置执行时长和频率
生成 RDB 文件的时候已过期的键不会被保留 ,在载入 RDB 文件的时候,主服务器会对键进行查看, 过期的键不会被载入 ,而 从服务会将所有键载入,直到主服务来删除告诉
AOF 写入的时候,如果 某个键过期,会向 AOF 追加一条 DEL 命令 ;AOF 重写的时候会对键进行查看, 过期键不会被写入
复制的时候,主服务器发送删除告诉,从服务器接到删除告诉时才删除过期键
8.Redis 高可用计划
- 主从模式:一主二从
配置 redis.conf , 从节点配置 slaveof 127.0.0.1 6379
确认主从关系:redis-cli -h 127.0.0.1 -p 6379 info replication - 哨兵模式:
配置 redis-sentinel.conf ,sentinel monitor mymaster 127.0.0.1 6379 1
最初一位是选举 master 须要的票数
启动哨兵:
redis-sentinel redis-sentinel.conf
或者 redis-server redis-sentinel.conf –sentinel - redis-cluster:
每个节点保留数据和整个集群状态,每个节点都和其余节点连贯。采纳哈希函数把数据映射到一个固定范畴的整数汇合中,整数定义为槽。所有键依据哈希函数映射到 0~16383 整数槽内,公式 slot = CRC16(key) & 16383 - Codis
- 集群限度:
- key 批量操作反对限度,目前只反对具备雷同 slot 值的 key 执行批量操作
- key 事务操作反对无限
- key 作为数据分区最小粒度,不能将大的键值对象 hash、list 等映射到不同节点
- 只能应用 0 号数据库
- 从节点只能复制主节点,不反对嵌套树状复制构造
-
主从复制过程
- 从节点执行 slaveof 后,从节点保留主节点地址信息
- 从节点外部通过每秒运行的定时工作保护复制相干逻辑,当定时工作发现存在新的主节点后,会尝试与该节点建设网络连接
- 连贯建设胜利后,从节点发送 ping 申请进行首次通信,目标是检测主从之间网络套接字是否可用,主节点以后是否承受解决命令
- 如果主节点配置了明码验证,则从节点必须要配置雷同的明码能力通过验证,进行复制同步
- 通过验证后,主从可失常通信了,主节点会把数据继续发给从节点,同步形式有全量同步和局部同步, 刚建设建设的时候,会进行全量同步,同步完结后,进行局部同步
- 当主节点与从节点同步完以后的数据后,主节点会把后续新增的命令继续发送给从节点进行同步
- 哨兵模式 最小配置 1 主 2 从 3 哨兵,3 个哨兵能监控每个 master 和 salve
9. 缓存问题
-
缓存穿透
缓存穿透是指,缓存中不存在该 key 的数据,于是就是去数据库中查问,数据库也不存在该数据,导致循环查问数据
优化:
-
缓存空对象
对于不存在的数据,仍旧将空值缓存起来。但这会造成内存空间的节约,能够针对这类数据加一个过期工夫。对于缓存和存储层数据的一致性,能够在过期的时候,申请存储层,或者通过音讯零碎更新缓存
-
布隆过滤器
将所有存在的数据做成布隆过滤器,能够应用 Bitmaps 实现,其在大数据量下空间占用小。当有新的申请时,先到布隆过滤器中查问是否存在,如果不存在该条数据间接返回;如果存在该条数据再查问缓存、查询数据库。
Redis4.0 以上采纳插件集成,https://github.com/RedisBloom…
原理:
在 redis 中是一个大型的位数组,通过计算 key 的 hash 而后对数组长度取模失去一个地位,进行写入;在判断是否存在时,判断位数组中几个地位是否都为 1,只有一个位为 0,就阐明这个 key 不存在。布隆过滤器也会存在肯定的误判,如果位数组比拟稠密,概率就会很大,否则就会升高。
-
-
缓存击穿
缓存击穿指,某 key 忽然变成了热点 key,大量申请到该 key,但 key 刚好又生效,导致从数据库中去查问数据
优化:
通过互斥锁形式,在申请数据库之前设置 setnx,在查问完数据库,并更新缓存后,删除 setnx
-
缓存雪崩
指缓存因为大量申请,造成缓存挂掉,大量申请间接打到存储层,造成存储层挂机
优化:
- 应用主从,哨兵,集群模式保障缓存高可用
- 依赖隔离组件为后端限流并降级
- 提前演练,做好后备计划
-
缓存更新形式
同步更新,先写入数据库,写入胜利后,再更新缓存。
异步更新,通过消息中间件进行触发更新。
生效更新,在取不到缓存的时候,从数据库取数据,再更新缓存。
定时更新,通过定时工作来更新缓存
-
缓存不统一
通过减少重试机制,弥补工作,达到最终统一
-
热点 key 重建
优化
- 应用互斥锁,只容许一个线程重建缓存
- 应用逻辑缓存过期,在 value 中存一个 key 过期工夫,在获取 key 的时候通过逻辑工夫进行判断
10.Redis 锁的实现 和 Zookeeper 锁实现区别
Redis 锁 通过 setnx key value 或者 set key value px millseconds nx 当返回 1 时代表获取到锁,返回 0 示意获取锁失败,通过 Redis 的 key 超时机制来开释锁
PS:Redis 锁可能会在业务逻辑还没执行完的时候就曾经超时开释,因而在开释锁的时候,可能其余线程曾经从新持有了该锁,所以要在开释锁的时候验证 key 对应的 value 值,在创立缓存的时候,value 值是随机生成的。或者应用 redisson 做为分布式锁
ZK 锁 通过在服务端新建一个长期有序节点,哪个客户端胜利创立了第一个长期有序节点,就代表该客户端取得了锁,前面节点的客户端会处于监听状态,当开释锁的时候,服务端就会删除第一个长期节点,此时第二个长期节点能监听到上一个节点的开释事件,这样第二个节点就变成第一个节点,此时客户端 2 就代表取得了锁。如果客户端的会话敞开,长期节点会被删除,也就开释了锁
《三种分布式锁的优缺点及解决方案》