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删除一个或多个fieldkey field [field ...]
hlen计算field个数-
hmget批量获取key field [field ...]
hmset批量设置key field value [field value ...]
hexists判断field是否存在key field
hkeys获取所有fieldkey
hvals获取所有valuekey
hgetall获取所有的field-valuekey
如果元素很多,会造成阻塞,倡议应用hscan
hincrby hincrbyfloat相似incrby / incrbyfloatkey field increment
hstrlen计算value字符串长度key field
列表
用来存储多个有序的字符串,一个列表最多能够存储232 - 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)
用来保留多个的字符串的元素,但和列表元素不一样的是,汇合中不容许有反复元素,并且汇合中的元素是无序的,不能通过索引下标获取元素,一个汇合最多能够存储232 - 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就代表取得了锁。如果客户端的会话敞开,长期节点会被删除,也就开释了锁

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