主题 |
链接 |
Java基础知识 |
面试题 |
Java集合容器 |
面试题 |
Java并发编程 |
面试题 |
Java底层知识 |
面试题 |
Java常用框架 |
面试题 |
计算机网络 |
面试题 |
数据库 |
面试题 |
RabbitMQ |
面试题 |
Redis |
面试题 |
Elasticsearch |
面试题 |
Zookeeper |
面试题 |
系统设计 |
面试题 |
文章目录
-
- 什么是Redis
- Redis 有哪些适合的场景?
- Redis有哪些优缺点
- 为什么要用 Redis 而不用 map/guava 做缓存?
- Redis为什么这么快
- Redis有哪些数据类型
- string
- hash
- list
- set
- zset
- Redis 有哪几种数据淘汰策略?
- Redis 集群方案应该怎么做?都有哪些方案?
- 说说 Redis 哈希槽的概念?
- Redis高可用方案
- 什么是缓存穿透?
- 怎么避免缓存穿透?
- 什么是缓存雪崩?
- 怎么避免缓存雪崩?
- 什么是缓存击穿?何如避免?
- Redis 实现异步队列
- Redis 实现延时队列
- redis 主从复制如何实现的?
- Redis怎么做主从切换?数据丢失怎么办?
- Redis 持久化
- 缓存与数据库不一致怎么办
- 主从数据库不一致如何解决
- Redis 常见的性能问题和解决方案
- 假如 Redis 里面有 1 亿个 key ,其中有 10w 个 个 key 是以某个固定的已知的前缀开头的,如何将它们全部找出来?
- MySQL 里有 2000w 数据,redis 中只存 20w 的数据,如何保证 redis 中的数据都是热点数据?
为什么用redis,怎么使用的
使用了哪些数据结构
介绍sortedset
你会怎么实现sortedset
SoertedSet使用红黑树、跳表哪个实现更好?
什么是Redis
- Redis(Remote Dictionary Server) 是一个使用 C语言编写的,开源的(BSD许可)高性能非关系型(NoSQL)的键值对数据库。
- Redis 可以存储键和五种不同类型的值之间的映射。键的类型只能为字符串,值支持五种数据类型:字符串、列表、集合、散列表、有序集合。
- 与传统数据库不同的是 Redis 的数据是存在内存中的,所以读写速度非常快,Redis 经常用来做分布式锁。
- Redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。
Redis 有哪些适合的场景?
- 缓存
- 排行榜:使用Redis的SortSet数据结构
- 计算器/限速器,利用Redis中原子性的自增操作,我们可以统计类似用户点赞数、用户访问数等
- 好友关系,利用集合的一些命令,比如求交集、并集、差集等。可以方便搞定一些共同好友、共同爱好之类的功能;
- 简单消息队列,除了Redis自身的发布/订阅模式,我们也可以利用List来实现一个队列机制,完成异步解耦;
- Session共享
- 分布式锁
Redis有哪些优缺点
优点
- 读写性能优异
- 支持数据持久化,支持AOF和RDB两种持久化方式。
- 支持事务,Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行。
- 数据结构丰富,除了支持string类型的value外还支持hash、set、zset、list等数据结构。
- 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。
缺点
- 数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。
- Redis 不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的IP才能恢复。
- 主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性。
- Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。
为什么要用 Redis 而不用 map/guava 做缓存?
- map/guava属于本地换成,最主要的特点是轻量以及快速,生命周期随着 jvm的销毁而结束,并且在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性。
- 使用 redis 或 memcached 之类的称为分布式缓存,在多实例的情况下,各实例共用一份缓存数据,缓存具有一致性。缺点是需要保持服务的高可用,架构上较为复杂。
Redis为什么这么快
- 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于 HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
- 数据结构简单,对数据操作也简单,Redis 中的数据结构是专门进行设计的;
- 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
- 使用多路 I/O 复用模型,非阻塞 IO;
- 使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis 直接自己构建了 VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
Redis有哪些数据类型
Redis主要有5种数据类型,包括String,List,Set,Zset,Hash,满足大部分的使用要求
string
Redis的数据结构
字符串类型是 Redis 最基础的数据结构,键都是字符串类型,而且其他几种数据结构都是在字符串类型的基础上构建的。字符串类型的值可以实际可以是字符串(简单的字符串、复杂的字符串如 JSON、XML)、数字(整形、浮点数)、甚至二进制(图片、音频、视频),但是值最大不能超过 512 MB。
设置值
set key value [ex seconds] [px millseconds] [nx|xx]
ex seconds:为键设置秒级过期时间,跟 setex 效果一样
px millseconds:为键设置毫秒级过期时间
nx:键必须不存在才可以设置成功,用于添加,跟 setnx 效果一样。由于 Redis 的单线程命令处理机制,如果多个客户端同时执行,则只有一个客户端能设置成功,可以用作分布式锁的一种实现。
xx:键必须存在才可以设置成功,用于更新
获取值
get key ,如果不存在返回 nil
批量设置值
mset key value [key value…]
批量获取值
mget key [key…]
计数
incr key
incr 命令用于对值做自增操作,返回结果分为三种:① 值不是整数返回错误。② 值是整数,返回自增后的结果。③ 值不存在,按照值为 0 自增,返回结果 1。除了 incr 命令,还有自减 decr、自增指定数字 incrby、自减指定数组 decrby、自增浮点数 incrbyfloat。
string 的内部编码
字符串对象底层数据结构实现为简单动态字符串(SDS)和直接存储,但其编码方式可以是int、raw或者embstr,区别在于内存结构的不同。
struct sdshdr{
int length;
int free;
char[] buf;
};
string 的应用场景
缓存功能、计数、共享 Session、限速
限速:例如为了短信接口不被频繁访问会限制用户每分钟获取验证码的次数或者网站限制一个 IP 地址不能在一秒内访问超过 n 次。可以使用键过期策略和自增计数实现。
hash
- 设置值 hset key field value ,如果设置成功会返回 1,反之会返回 0,此外还提供了 hsetnx 命令,作用和setnx 类似,只是作用于由键变为 field。
- 获取值 hget key field ,如果不存在会返回 nil。
- 删除 field hdel key field [field…] ,会删除一个或多个 field,返回结果为删除成功 field 的个数。
- 计算 field 个数 hlen key
- 批量设置或获取 field-value
- 判断 field 是否存在 hexists key field ,存在返回 1,否则返回 0。
- 获取所有的 field hkeys key ,返回指定哈希键的所有 field。
- 获取所有 value hvals key ,获取指定键的所有 value。
- 获取所有的 field-value hgetall key ,获取指定键的所有 field-value。
hash 的内部编码
ziplist 压缩列表:当哈希类型元素个数和值小于配置值(默认 512 个和 64 字节)时会使用 ziplist 作为内部实现,使用更紧凑的结构实现多个元素的连续存储,在节省内存方面比 hashtable 更优秀。
hashtable 哈希表:当哈希类型无法满足 ziplist 的条件时会使用 hashtable 作为哈希的内部实现,因为此时 ziplist 的读写效率会下降,而 hashtable 的读写时间复杂度都为 O(1)。
hash 的应用场景
缓存用户信息,每个用户属性使用一对 field-value,但只用一个键保存。
优点:简单直观,如果合理使用可以减少内存空间使用。
缺点:要控制哈希在 ziplist 和 hashtable 两种内部编码的转换,hashtable 会消耗更多内存。
list
list 是用来存储多个有序的字符串,列表中的每个字符串称为元素,一个列表最多可以存储 2 32 -1 个元
素。可以对列表两端插入(push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素等。列表是一种比较灵活的数据结构,它可以充当栈和队列的角色,在实际开发中有很多应用场景。
list 有两个特点:① 列表中的元素是有序的,可以通过索引下标获取某个元素或者某个范围内的元素列
表。② 列表中的元素可以重复。
hmget key field [field…]
hmset key field value [field value…]
list 的命令
添加
- 从右边插入元素: rpush key value [value…]
- 从左到右获取列表的所有元素: lrange 0 -1
- 从左边插入元素: lpush key value [value…]
- 向某个元素前或者后插入元素: linsert key before|after pivot value ,会在列表中找到等于pivot 的元素,在其前或后插入一个新的元素 value。
查找
- 获取指定范围内的元素列表: lrange key start end ,索引从左到右的范围是0N-1,从右到左是-1-N,lrange 中的 end 包含了自身。
- 获取列表指定索引下标的元素: lindex key index ,获取最后一个元素可以使用 lindex key -1 。
- 获取列表长度: llen key
删除
- 从列表左侧弹出元素: lpop key
- 从列表右侧弹出元素: rpop key
- 删除指定元素: lrem key count value ,如果 count 大于 0,从左到右删除最多 count 个元素,如果 count 小于 0,从右到左删除最多个 count 绝对值个元素,如果 count 等于 0,删除所有。
- 按照索引范围修剪列表: ltrim key start end ,只会保留 start ~ end 范围的元素。
修改
- 修改指定索引下标的元素: lset key index newValue 。
阻塞操作
- 阻塞式弹出: blpop/brpop key [key…] timeout ,timeout 表示阻塞时间。当列表为空时,如果 timeout = 0,客户端会一直阻塞,如果在此期间添加了元素,客户端会立即返
回。如果是多个键,那么brpop会从左至右遍历键,一旦有一个键能弹出元素,客户端立即返回。如果多个客户端对同一个键执行 brpop,那么最先执行该命令的客户端可以获取弹出的值。
list 的内部编码
ziplist 压缩列表:跟哈希的 zipilist 相同,元素个数和大小小于配置值(默认 512 个和 64 字节)时使
用。
linkedlist 链表:当列表类型无法满足 ziplist 的条件时会使用linkedlist。
Redis 3.2 提供了 quicklist 内部编码,它是以一个 ziplist 为节点的 linkedlist,它结合了两者的优势,
为列表类提供了一种更为优秀的内部编码实现。
list 的应用场景
消息队列
Redis 的 lpush + brpop 即可实现阻塞队列,生产者客户端使用 lpush 从列表左侧插入元素,多个消费
者客户端使用 brpop 命令阻塞式地抢列表尾部的元素,多个客户端保证了消费的负载均衡和高可用
性。
文章列表
每个用户有属于自己的文章列表,现在需要分页展示文章列表,就可以考虑使用列表。因为列表不但有
序,同时支持按照索引范围获取元素。每篇文章使用哈希结构存储。
lpush + lpop = 栈、lpush + rpop = 队列、lpush + ltrim = 优先集合、lpush + brpop = 消息队列。
set
集合类型也是用来保存多个字符串元素,和列表不同的是集合不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。一个集合最多可以存储 2 32 -1 个元素。Redis 除了支持集合内的增删改查,还支持多个集合取交集、并集、差集。
set 的命令
添加元素
sadd key element [element…] ,返回结果为添加成功的元素个数。
删除元素
srem key element [element…] ,返回结果为成功删除的元素个数。
计算元素个数
scard key ,时间复杂度为 O(1),会直接使用 Redis 内部的遍历。
判断元素是否在集合中
sismember key element ,如果存在返回 1,否则返回 0。
随机从集合返回指定个数个元素
srandmember key [count] ,如果不指定 count 默认为 1。
从集合随机弹出元素
spop key ,可以从集合中随机弹出一个元素。
获取所有元素
smembers key
求多个集合的交集/并集/差集
sinter key [key…]
sunion key [key…]
sdiff key [key…]
保存交集、并集、差集的结果
sinterstore/sunionstore/sdiffstore destination key [key…]
集合间运算在元素较多情况下比较耗时,Redis 提供这三个指令将集合间交集、并集、差集的结果保存
在 destination key 中。
set 的内部编码
intset 整数集合:当集合中的元素个数小于配置值(默认 512 个时),使用 intset。
hashtable 哈希表:当集合类型无法满足 intset 条件时使用 hashtable。当某个元素不为整数时,也会使用 hashtable。
set 的应用场景
set 比较典型的使用场景是标签,例如一个用户可能与娱乐、体育比较感兴趣,另一个用户可能对例时、新闻比较感兴趣,这些兴趣点就是标签。这些数据对于用户体验以及增强用户黏度比较重要。sadd = 标签、spop/srandmember = 生成随机数,比如抽奖、sadd + sinter = 社交需求。
zset
有序集合保留了集合不能有重复成员的特性,不同的是可以排序。但是它和列表使用索引下标作为排序依据不同的是,他给每个元素设置一个分数(score)作为排序的依据。有序集合提供了获取指定分数和元素查询范围、计算成员排名等功能。
zset 的命令
添加成员
zadd key score member [score member…] ,返回结果是成功添加成员的个数
Redis 3.2 为 zadd 命令添加了 nx、xx、ch、incr 四个选项:
nx:member 必须不存在才可以设置成功,用于添加。
xx:member 必须存在才能设置成功,用于更新。
ch:返回此次操作后,有序集合元素和分数变化的个数。
incr:对 score 做增加,相当于 zincrby。
zadd 的时间复杂度为 O(log n ),sadd 的时间复杂度为 O(1)。
计算成员个数
zcard key ,时间复杂度为 O(1)。
计算某个成员的分数
zscore key member ,如果不存在则返回 nil。
计算成员排名
zrank key member ,从低到高返回排名。
zrevrank key member ,从高到低返回排名。
删除成员
zrem key member [member…] ,返回结果是成功删除的个数。
增加成员的分数
zincrby key increment member
返回指定排名范围的成员
zrange key start end [withscores] ,从低到高返回
zrevrange key start end [withscores] , 从高到底返回
返回指定分数范围的成员
zrangebyscore key min max [withscores] [limit offset count] ,从低到高返回
zrevrangebyscore key min max [withscores] [limit offset count] , 从高到底返回
返回指定分数范围成员个数
zcount key min max
删除指定分数范围内的成员
zremrangebyscore key min max
交集和并集
zinterstore/zunionstore destination numkeys key [key…] [weights weight
[weight…]] [aggregate sum|min|max]
destination :交集结果保存到这个键
numkeys :要做交集计算键的个数
key :需要做交集计算的键
weight :每个键的权重,默认 1
aggregate sum|min|max :计算交集后,分值可以按和、最小值、最大值汇总,默认 sum。
zset 的内部编码
ziplist 压缩列表:当有序集合元素个数和值小于配置值(默认128 个和 64 字节)时会使用 ziplist 作为内部实现。
skiplist 跳跃表:当 ziplist 不满足条件时使用,因为此时 ziplist 的读写效率会下降。
zset 的应用场景
有序集合的典型使用场景就是排行榜系统,例如用户上传了一个视频并获得了赞,可以使用 zadd 和zincrby。如果需要将用户从榜单删除,可以使用 zrem。如果要展示获取赞数最多的十个用户,可以使用 zrange。
Redis 有哪几种数据淘汰策略?
- volatile-lru:从设置了过期时间的数据集中,选择最近最久未使用的数据释放;
- allkeys-lru:从数据集中(包括设置过期时间以及未设置过期时间的数据集中),选择最近最久未使用的数据释放;
- volatile-random:从设置了过期时间的数据集中,随机选择一个数据进行释放;
- allkeys-random:从数据集中(包括了设置过期时间以及未设置过期时间)随机选择一个数据进行入释放;
- volatile-ttl:从设置了过期时间的数据集中,选择马上就要过期的数据进行释放操作;
- noeviction:不删除任意数据(但redis还会根据引用计数器进行释放),这时如果内存不够时,会直接返回错误。
Redis 集群方案应该怎么做?都有哪些方案?
redis集群方案
官方cluster方案
- 从redis3.0版本开始支持redis-cluster集群,redis-cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他节点连接。redis-cluster是一种服务端分片技术。
- 每个节点都和n-1个节点通信,这被称为集群总线(cluster bus)。它们使用特殊的端口号,,其内部采用特殊的二进制协议优化传输速度和带宽
- redis-cluster把所有的物理节点映射到[0,16383]slot(槽)上,cluster负责维护node–slot–value。
- 集群预分好16384个桶,当需要在redis集群中插入数据时,根据CRC16(KEY) mod 16384的值,决定将一个key放到哪个桶中。
- 客户端与redis节点直连,不需要连接集群所有的节点,连接集群中任何一个可用节点即可。
- 节点的fail是通过集群中超过半数的节点检测失效时才生效。
- 整个cluster被看做是一个整体,客户端可连接任意一个节点进行操作,当客户端操作的key没有分配在该节点上时,redis会返回转向指令,指向正确的节点。
- 为了增加集群的可访问性,官方推荐的方案是将node配置成主从结构,即一个master主节点,挂n个slave从节点。如果主节点失效,redis cluster会根据选举算法从slave节点中选择一个上升为master节点,整个集群继续对外提供服务。
twemproxy代理
- Redis代理中间件twemproxy是一种利用中间件做分片的技术。twemproxy处于客户端和服务器的中间,将客户端发来的请求,进行一定的处理后(sharding),再转发给后端真正的redis服务器。也就是说,客户端不直接访问redis服务器,而是通过twemproxy代理中间件间接访问。降低了客户端直连后端服务器的连接数量,并且支持服务器集群水平扩展。
- twemproxy中间件的内部处理是无状态的,它本身可以很轻松地集群,这样可以避免单点压力或故障。
Sentinel哨兵
- Sentinel(哨兵)是Redis的高可用性解决方案:由一个或多个Sentinel实例组成的Sentinel系统可以监视任意多个主服务器以及这些主服务器下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器。
codis
- 连接codis proxy和连接原生的redis server没什么明显的区别,上层应用可以像使用单机的redis一样使用,codis底层会处理请求的转发,不停机的数据迁移等工作,所有后边的事情,对于前面的客户端来说是透明的,可以简单的认为后边连接的是一个内存无限大的redis服务。
客户端分片
- 分区的逻辑在客户端实现,由客户端自己选择请求到哪个节点。方案可参考一致性哈希,这种方案通常适用于用户对客户端的行为有完全控制能力的场景。
说说 Redis 哈希槽的概念?
官方cluster方案中没有使用一致性 hash,而是引入了哈希槽的概念,Redis 集群有 16384 个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽,集群的每个节点负责一部分 hash 槽。
Redis高可用方案
参考链接
什么是缓存穿透?
缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。
怎么避免缓存穿透?
布隆过滤器:对所有可能查询的参数以hash形式存储,当用户想要查询的时候,使用布隆过滤器发现不在集合中,就直接丢弃,不再对持久层查询。
缓存空对象:当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源;但是这种方法会存在两个问题:
- 如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键;
- 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。
什么是缓存雪崩?
如果缓存集中在一段时间内失效,发生大量的缓存穿透,所有的查询都落在数据库上,造成了缓存雪崩。或者缓存层出现了错误,不能正常工作了。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。
这个没有完美解决办法,但可以分析用户行为,尽量让失效时间点均匀分布。大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。
怎么避免缓存雪崩?
- 保证redis高可用:搭建集群
- 限流降级:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
- 数据预热:在正式部署之前,把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
什么是缓存击穿?何如避免?
- 缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
- 设置热点数据永远不过期、或者加上互斥锁。
Redis 实现异步队列
- 一般使用 list 结构作为队列,rpush 生产消息,lpop 消费消息。当 lpop 没有消息的时候,要适当 sleep一会再重试。
- 在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如 rabbitmq 等。
- 使用 pub/sub 主题订阅者模式,可以实现 1:N 的消息队列。
Redis 实现延时队列
一些可以通过定时任务去轮询实现,但是当数据量过大的时候,高频轮询数据库会消耗大量的资源,此时用延迟队列来应对这类场景比较好:
- 订单超过 30 分钟未支付,则自动取消。
- 外卖商家超时未接单,则自动取消。
Rabbitmq 延时队列
- 优点:消息持久化,分布式
- 缺点:延时相同的消息必须扔在同一个队列,每一种延时就需要建立一个队列。因为当后面的消息比前面的消息先过期,还是只能等待前面的消息过期,这里的过期检测是惰性的。
- 使用: RabbitMQ 可以针对 Queue 设置 x-expires 或者针对 Message 设置 x-message-ttl ,来控制消息的生存时间(可以根据 Queue 来设置,也可以根据 message 设置), Queue 还可以配置 x-dead-letter-exchange 和 x-dead-letter-routing-key(可选)两个参数,如果队列内出现了 dead letter ,则按照这两个参数重新路由转发到指定的队列,此时就可以实现延时队列了。
DelayQueue 延时队列
- 优点:无界、延迟、阻塞队列
- 缺点:非持久化,JDK 自带的延时队列,没有过期元素的话,使用 poll() 方法会返回 null 值,超时判定是通过getDelay(TimeUnit.NANOSECONDS) 方法的返回值小于等于0来判断,并且不能存放空元素。
- 使用:getDelay 方法定义了剩余到期时间,compareTo 方法定义了元素排序规则。poll() 是非阻塞的获取数据,take() 是阻塞形式获取数据。实现 Delayed 接口即可使用延时队列。
Redis 延迟队列
- 消息持久化,消息至少被消费一次
- 支持指定消息 remove
- 高可用性
- 使用 sortedset,使用时间戳做 score, 消息内容作为 key,调用 zadd 来生产消息,消费者使用 zrangbyscore 获取 n 秒之前的数据做轮询处理。
redis 主从复制如何实现的?
Redis虽然读取写入的速度都特别快,但是也会产生读压力特别大的情况。为了分担读压力,Redis支持主从复制,Redis的主从结构可以采用一主多从或者级联结构,Redis主从复制可以根据是否是全量分为全量同步和增量同步。
master 复制数据给 slave :slave 启动成功之后连接到 master 后会发送一个 sync命令。master 接收到这个同步命令之后启动后台的存盘进程,即将内存的数据持久化到 rdb 或 aof。持久化完毕之后,master 将整个数据文件传送给 slave。
slave 接收 master 复制数据:
全量复制:slave 刚与 master 建立连接的时候,会将接收到的 master 发来的整个数据库文件存盘并加载到内存。
增量复制:slave 已经与 master 建立好连接关系的时候,master 会将收集到的修改数据的命令传送给 slave,slave 执行这些命令,完成同步。而不是再一次重新加载整个数据文件到内存。
首先,Redis 的主从数据是异步同步的,保证最终一致性,从节点会努力追赶主节点,最终从节点的状态会和主节点的状态将保持一致。如果网络断开了,主从节点的数据将会出现大量不一致,一旦网络恢复,从节点会采用多种策略努力追赶上落后的数据,继续尽力保持和主节点一致。然后,Redis 同步主要是两种同步方式:
- 增量同步:同步的是指令流,主节点会将那些对自己的状态产生修改性影响的指令记录在本地的内存 buffer 中,然后异步将 buffer中的指令同步到从节点,从节点一边执行同步的指令,一边向主节点反馈自己同步到哪里了。由于同步的是指令,所以比较轻量。这个Buffer是有界环形buffer,如果满了,就会覆盖原来的命令,从头写。出现很长时间的网络延迟时,就会有命令丢失的问题,所以需要另一种同步方式弥补。再出现buffer 满了的时候,会启用快照同步
- 快照同步:同步的是全量数据,首先需要在主库上进行一次 bgsave 将当前内存的数据全部dump 到文件中,然后再将快照文件的内容全部传送到从节点。从节点同步快照文件后,将当前内存的数据清空,执行一次全量加载。加载完毕后通知主节点继续进行增量同步所以,设置增量同步的 buffer 大小是一个很重要的事情,否则会一直快照同步。
Redis怎么做主从切换?数据丢失怎么办?
- 监控+手动切换redis主/从节点手动切换
- 哨兵模式自动切换主/从
哨兵模式 + Redis主从复制这种部署结构,无法保证数据不会出现丢失。哨兵模式下数据丢失主要有两种情况:
- 因为主从复制是异步操作,可能主从复制还没成功,主节点宕机了。这时候还没成功复制的数据就会丢失了。
- 如果主节点无法与其他从节点连接,但是实际上还在运行。这时候哨兵会将一个从节点切换成新的主节点,但是在这个过程中实际上主节点还在运行,所以继续向这个主节点写入的数据会被丢失。
解决数据丢失方案
使用命令:
min-slaves-to-write 1
min-slaves-max-lag 10
使用这组命令可以设置至少有一个从节点数据复制延迟不能超过10S,也就是说如果一个直接点下所有从节点数据复制延迟都超过10S,则停止主节点继续接收处理新的请求。这样可以保证数据丢失最多只会丢失10S内的数据。
Redis 持久化
redis 持久化的两种方式:
- RDB 持久化机制,是对 redis 中的数据执行周期性的持久化。
- AOF 机制对每条写入命令作为日志,以 append-only 的模式写入一个日志文件中,在redis 重启的时候,可以通过回放 AOF 日志中的写入指令来重新构建整个数据集。
- 如果同时使用 RDB 和 AOF 两种持久化机制,那么在 redis 重启的时候,会使用 AOF 来重新构建数据,因为 AOF 中的数据更加完整。
RDB 优缺点
- RDB 会生成多个数据文件,每个数据文件都代表了某一个时刻中 redis 的数据,适合做冷备,可以按预定好的备份策略来定期备份 redis 中的数据。
- RDB 对 redis 对外提供的读写服务,影响非常小,可以让 redis 保持高性能,因为 redis 主进程只需要 fork 一个子进程,让子进程执行磁盘 IO 操作来进行 RDB 持久化即可。
- 相对于 AOF 持久化机制来说,直接基于 RDB 数据文件来重启和恢复 redis 进程,更加快速。
- 一般来说,RDB 数据快照文件,都是每隔 5 分钟,或者更长时间生成一次,这个时候就得接受一旦 redis 进程宕机,那么会丢失最近 5 分钟的数据。
- RDB 每次在 fork 子进程来执行 RDB 快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒。
AOF 优缺点
- AOF 可以更好的保护数据不丢失,一般 AOF 会每隔 1 秒,通过一个后台线程执行一次 fsync 操作,最多丢失 1 秒钟的数据。
- AOF 日志文件以 append-only 模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修复。
- AOF 日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。因为在 rewrite log 的时候,会对其中的指令进行压缩,创建出一份需要恢复数据的最小日志出来。在创建新日志文件的时候,老的日志文件还是照常写入。当新的 merge 后的日志文件 ready 的时候,再交换新老日志文件即可。
- AOF 日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用 flushall 命令清空了所有数据,只要这个时候后台 rewrite 还没有发生,那么就可以立即拷贝 AOF 文件,将最后一条 flushall 命令给删了,然后再将该 AOF 文件放回去,就可以通过恢复机制,自动恢复所有数据。
- 对于同一份数据来说,AOF 日志文件通常比 RDB 数据快照文件更大。
- AOF 开启后,支持的写 QPS 会比 RDB 支持的写 QPS 低,因为 AOF 一般会配置成每秒 fsync 一次日志文件,当然,每秒一次 fsync,性能也还是很高的。(如果实时写入,那么 QPS 会大降,redis 性能会大大降低)
- 以前 AOF 发生过 bug,就是通过 AOF 记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。所以说,类似 AOF 这种较为复杂的基于命令日志 / merge / 回放的方式,比基于 RDB 每次持久化一份完整的数据快照文件的方式,更加脆弱一些,容易有 bug。不过 AOF 就是为了避免rewrite 过程导致的 bug,因此每次 rewrite 并不是基于旧的指令日志进行 merge 的,而是基于当时内存中的数据进行指令的重新构建,这样健壮性会好很多。
redis 支持同时开启开启两种持久化方式,我们可以综合使用 AOF 和 RDB 两种持久化机制,用AOF 来保证数据不丢失,作为数据恢复的第一选择; 用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复。
缓存与数据库不一致怎么办
参考
主从数据库不一致如何解决
参考
Redis 常见的性能问题和解决方案
- master 最好不要做持久化工作,如 RDB 内存快照和 AOF 日志文件
- 如果数据比较重要,某个 slave 开启 AOF 备份,策略设置成每秒同步一次
- 为了主从复制的速度和连接的稳定性,master 和 Slave 最好在一个局域网内
- 尽量避免在压力大的主库上增加从库
- 主从复制不要采用网状结构,尽量是线性结构
假如 Redis 里面有 1 亿个 key ,其中有 10w 个 个 key 是以某个固定的已知的前缀开头的,如何将它们全部找出来?
答:
对应的问题:
- 因为redis 是单线程 所以一次性操作大量的数据 可能会导致业务出现卡顿
解决办法:
- 使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长.
MySQL 里有 2000w 数据,redis 中只存 20w 的数据,如何保证 redis 中的数据都是热点数据?
- 限定 Redis 占用的内存,Redis 会根据自身数据淘汰策略,加载热数据到内存。所以,计算一下 20W数据大约占用的内存,然后设置一下 Redis 内存限制即可。
- 如果是否是热点数据可以通过一个值来判断,如用户登录时间等,可以使用sortedset,用户登录时间当做score, zadd覆盖性设置,使用 zrangbyscore 获取值,通过定时任务删除score分数低的数据。