乐趣区

关于java:Java面试问题汇总Redis

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 版本反对的策略

  1. volatile-lru:从设置过期工夫的数据集(server.db[i].expires)中挑选出 最近起码应用 的数据淘汰。没有设置过期工夫的 key 不会被淘汰 ,这样就能够在减少内存空间的同时 保障须要长久化的数据不会失落
  2. volatile-ttl:除了淘汰机制采纳 LRU,策略基本上与 volatile-lru 类似,从设置过期工夫的数据集(server.db[i].expires)中筛选 将要过期的数据淘汰ttl 值越大越优先被淘汰
  3. volatile-random:从已设置过期工夫的数据集(server.db[i].expires)中 任意抉择数据淘汰 。当 内存达到限度无奈写入非过期工夫的数据集时,能够通过该淘汰策略在主键空间中随机移除某个 key。
  4. allkeys-lru:从数据集(server.db[i].dict)中筛选 最近起码应用 的数据淘汰,该策略 要淘汰的 key 面向的是整体 key 汇合,而非过期的 key 汇合。
  5. allkeys-random:从数据集 (server.db[i].dict)中抉择 任意数据淘汰
  6. 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 工作流程:

  1. 所有的 写入命令追加到 aof_buf(缓冲区)
  2. AOF 缓冲区依据对应的策略向硬盘做同步操作
  3. 随着 AOF 文件越来越大,须要 定期对 AOF 文件进行重写,压缩,父过程执行 fork 创立子过程,由子过程依据内存快照执行 AOF 重写,父进行持续响应前面的命令,在子过程实现重写后,父过程再把新增的写入命令写入到新的 AOF 文件中
  4. 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 高可用计划

  1. 主从模式:一主二从
    配置 redis.conf , 从节点配置 slaveof 127.0.0.1 6379
    确认主从关系:redis-cli -h 127.0.0.1 -p 6379 info replication
  2. 哨兵模式:
    配置 redis-sentinel.conf ,sentinel monitor mymaster 127.0.0.1 6379 1
    最初一位是选举 master 须要的票数
    启动哨兵:
    redis-sentinel redis-sentinel.conf
    或者 redis-server redis-sentinel.conf –sentinel
  3. redis-cluster:
    每个节点保留数据和整个集群状态,每个节点都和其余节点连贯。采纳哈希函数把数据映射到一个固定范畴的整数汇合中,整数定义为槽。所有键依据哈希函数映射到 0~16383 整数槽内,公式 slot = CRC16(key) & 16383
  4. Codis
  • 集群限度:
  1. key 批量操作反对限度,目前只反对具备雷同 slot 值的 key 执行批量操作
  2. key 事务操作反对无限
  3. key 作为数据分区最小粒度,不能将大的键值对象 hash、list 等映射到不同节点
  4. 只能应用 0 号数据库
  5. 从节点只能复制主节点,不反对嵌套树状复制构造
  • 主从复制过程

    1. 从节点执行 slaveof 后,从节点保留主节点地址信息
    2. 从节点外部通过每秒运行的定时工作保护复制相干逻辑,当定时工作发现存在新的主节点后,会尝试与该节点建设网络连接
    3. 连贯建设胜利后,从节点发送 ping 申请进行首次通信,目标是检测主从之间网络套接字是否可用,主节点以后是否承受解决命令
    4. 如果主节点配置了明码验证,则从节点必须要配置雷同的明码能力通过验证,进行复制同步
    5. 通过验证后,主从可失常通信了,主节点会把数据继续发给从节点,同步形式有全量同步和局部同步, 刚建设建设的时候,会进行全量同步,同步完结后,进行局部同步
    6. 当主节点与从节点同步完以后的数据后,主节点会把后续新增的命令继续发送给从节点进行同步
  • 哨兵模式 最小配置 1 主 2 从 3 哨兵,3 个哨兵能监控每个 master 和 salve

9. 缓存问题

  • 缓存穿透

    缓存穿透是指,缓存中不存在该 key 的数据,于是就是去数据库中查问,数据库也不存在该数据,导致循环查问数据

    优化:

    1. 缓存空对象

      对于不存在的数据,仍旧将空值缓存起来。但这会造成内存空间的节约,能够针对这类数据加一个过期工夫。对于缓存和存储层数据的一致性,能够在过期的时候,申请存储层,或者通过音讯零碎更新缓存

    2. 布隆过滤器

      将所有存在的数据做成布隆过滤器,能够应用 Bitmaps 实现,其在大数据量下空间占用小。当有新的申请时,先到布隆过滤器中查问是否存在,如果不存在该条数据间接返回;如果存在该条数据再查问缓存、查询数据库。

      Redis4.0 以上采纳插件集成,https://github.com/RedisBloom…

      原理:

      在 redis 中是一个大型的位数组,通过计算 key 的 hash 而后对数组长度取模失去一个地位,进行写入;在判断是否存在时,判断位数组中几个地位是否都为 1,只有一个位为 0,就阐明这个 key 不存在。布隆过滤器也会存在肯定的误判,如果位数组比拟稠密,概率就会很大,否则就会升高。

  • 缓存击穿

    缓存击穿指,某 key 忽然变成了热点 key,大量申请到该 key,但 key 刚好又生效,导致从数据库中去查问数据

    优化:

    ​ 通过互斥锁形式,在申请数据库之前设置 setnx,在查问完数据库,并更新缓存后,删除 setnx

  • 缓存雪崩

    指缓存因为大量申请,造成缓存挂掉,大量申请间接打到存储层,造成存储层挂机

    优化:

    1. 应用主从,哨兵,集群模式保障缓存高可用
    2. 依赖隔离组件为后端限流并降级
    3. 提前演练,做好后备计划
  • 缓存更新形式

    同步更新,先写入数据库,写入胜利后,再更新缓存。

    异步更新,通过消息中间件进行触发更新。

    生效更新,在取不到缓存的时候,从数据库取数据,再更新缓存。

    定时更新,通过定时工作来更新缓存

  • 缓存不统一

    通过减少重试机制,弥补工作,达到最终统一

  • 热点 key 重建

    优化

    1. 应用互斥锁,只容许一个线程重建缓存
    2. 应用逻辑缓存过期,在 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 就代表取得了锁。如果客户端的会话敞开,长期节点会被删除,也就开释了锁

《三种分布式锁的优缺点及解决方案》

退出移动版