文章目录

Redis

redis 简介

redis 采用的是单线程的 KV 模型,由 C 语言编写。由于redis采用了单线程,避免了不必要的线程切换所带来的性能消耗,且不用考虑加锁问题(没有加锁和释放锁的操作)。并且完全是基于内存的,所以性能比较好。

1. Redis 中的事务

1.1 什么是事务?

事务指的是单独的隔离操作,事务中的命令要么都执行,要么都不执行。

1.2 Redis 中的事务是怎么实现的?

Redis 事务的本质是通过multi,exec,discard,watch 等一组命令的集合。一次性,顺序性,排他性的执行一个队列中的一系列命令

一次性: 一次执行多个命令

顺序性: 所有命令都被序列化且按照顺序执行

排他性: 不会被其他客户端发送的命令打断

MULTI: Redis 使用 MULTI 命令标记事务开始,它总是返回OK,MUlTI 执行之后,客户端可以发送多条命令,Redis 会把这些命令放到队列中,而不是立马执行这些命令。所有命令会在 EXEC 命令之后执行。

DISCARD : 命令取消事务,放弃执行事务队列内的所有命令,恢复连接为非 (transaction) 模式。

如果正在使用 WATCH 命令监视某个(或某些) key,那么取消所有监视,等同于执行命令 UNWATCH 。

EXEC: EXEC命令触发所有的命令执行,所以客户端如果是在 EXEC命令前丢失连接,那么所有的命令都不会被执行,相反,如果EXEC被调用,那么所有命令会被执行。当使用 append-only file 方式持久化时,Redis使用单个 write(2) 系统调用将事务写到磁盘上。但是,如果Redis服务器崩溃或被系统管理员以某种硬方式杀死,则可能只注册了部分操作。Redis重启的时候会检测到这种情况,并返回错误退出。使用 redis-check-aof 工具可以删除部分事务,这样Redis可以重新启动。

WATCH: 监视一个或多个key,如果事务在执行前,这个key(或多个key)被其他命令修改,则事务被中断,不会执行事务中的任何命令。
UNWATCH: 取消WATCH对所有key的监视。

1.3 Redis 事务为什么不支持回滚?

1.3.1 语法错误情况下的事务回滚状态

127.0.0.1:6379> keys *
1) "user"
2) "count"
3) "test"
127.0.0.1:6379> set key1 value1
OK
127.0.0.1:6379> set key2 value2
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set key1 value11
QUEUED
127.0.0.1:6379(TX)> sets key2 value22
(error) ERR unknown command `sets`, with args beginning with: `key2`, `value22`, 
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get key1
"value1"
127.0.0.1:6379> get key2
"value2"

可以看到在语法错误时,提交事务时 key1,key2 值并没有被更改

1.3.2 类型错误情况下的事务回滚状态

127.0.0.1:6379> get key1
"value1"
127.0.0.1:6379> get key2
"value2"
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set key1 value11
QUEUED
127.0.0.1:6379(TX)> lpush key2 value22
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> get key1
"value11"
127.0.0.1:6379> get key2
"value2"

我们在事务开启后,将key2设置为错误的类型 List(key2 原本是 String类型),此时 Redis 没有检测出异常,直到事务 EXEC 执行时,才事务失败,但是我们可以看到 key1 设置的值并没有进行回滚。

1.3.3 Redis事务不回滚总结

多数事务失败是由语法错误或者数据结构类型错误导致的,语法错误说明在命令入队前就进行检测的,而类型错误是在执行时检测的,Redis为提升性能而采用这种简单的事务,这是不同于关系型数据库的,特别要注意区分。

2. Redis 中的数据结构

Redis 中存在五种数据结构,String、Hash、Set、Zset、List

在这里插入图片描述

想了解数据结构的底层实现,可以看一下博文:Redis五种数据结构详解

2.1 String

字符串类型,一个key对应一个value

常用命令(set ,get,del,incr,decr)

127.0.0.1:6379> set test helloworld
OK
127.0.0.1:6379> get test
"helloworld"
127.0.0.1:6379> set count 2
OK
127.0.0.1:6379> get count
"2"
127.0.0.1:6379> incr count
(integer) 3
127.0.0.1:6379> incrby count 10
(integer) 13
127.0.0.1:6379> del test
(nil)

2.2 Hash

哈希表类型,一个key对应多个键值对

常用命令:hget 、hset 、 hdel、hgetall

127.0.0.1:6379> hset user name wang
(integer) 1
127.0.0.1:6379> hset user email1 wddong@162 email2 wdding@163
(integer) 2
127.0.0.1:6379> hget user
(error) ERR wrong number of arguments for 'hget' command
127.0.0.1:6379> hget user name
"wang"
127.0.0.1:6379> hgetall user
1) "name"
2) "wang"
3) "email1"
4) "wddong@162"
5) "email2"
6) "wdding@163"
127.0.0.1:6379> hdel user name
(integer) 1
127.0.0.1:6379> hget user name
(nil)

底层实现:Hash对象的实现方式有两种分别是ziplist(压缩列表)、hashtable

2.3 Set

无序不重复集合类型

常用命令:sadd、srem、scard、smembers、sismember

SaddSadd key-name item [item…]——将一个或多个元素添加到集合里面,并返回被添加元素当中原本并不存在于集合里面的元素数量
SremSrem key-name item [item…]——从集合里面移除一个或多个元素,并返回被移除元素的数量
SpopSpop key-name——随机地移除集合中的一个元素,并返回被移除的元素
SmoveSmove source-key dest-key item——如果集合source-key包含元素item,那么从集合source-key里面移除元素item,并将元素item添加到集合dest-key中;如果item被成功移除,那么命令返回1,否则返回0
SismemberSismember key-name item——检查元素item是否存在于集合key-name里
ScardScard key-name——返回集合包含的元素的数量
SmembersSmembers key-name——返回集合包含的所有元素
SrandmemberSrandmember key-name [count]——从集合里面随机地返回一个或多个元素。当count为正数时,命令返回的随机元素不重复;当count为负数时,命令返回的随机元素可能会重复
SdiffSdiff key-name [key-name…]——返回那些存在于第一个集合、但不存在于其他集合中的元素(数学上的差集运算)
SdiffstoreSdiffstore dest-key key-name [key-name…]——将那些存在于第一个集合、但不存在于其他集合中的元素(数学上的差集运算)存储到dest-key键里面
SinterSinter key-name [key-name…]——返回那些同时存在于所有集合中的元素(数学上的交集运算)

底层实现:Set的底层实现是**「hashtable和intset(整数集合)」**

2.3.1 Set 和 List 的区别

  1. set中不允许有重复元素
  2. set中的元素是无序的,不能通过索引下标获取元素
  3. 支持集合间的操作。可以多个集合取交集,并集,差集

2.4 Zset

有序集合类型,元素不能重复,可排序。根据score分数作为排序的依据。

常用命令: zadd 、 zrange、 zscore

127.0.0.1:6379> zadd myscoreset 100 hao 90 xiaohao
(integer) 2
127.0.0.1:6379> ZRANGE myscoreset 0 -1
1) "xiaohao"
2) "hao"
127.0.0.1:6379> ZSCORE myscoreset hao
"100"

底层实现:ziplistskiplist(跳表)

应用场景可以用来做网站的排行榜。

2.5 List

列表类型,一个key对应一个链表(双端链表,有序,value可重复,可以通过下标取出对应值)

常用命令:lpush、rpush、lrange、lpop、rpop

命令注释
lpush[lpush key value1 value2 value3 …]左边插入
rpush右边插入
lrange[lrange key start stop]获取列表指定范围的元素,lrange key 0 -1:stop=-1代表取出列表中所有元素
lpop从左边取出一个元素
lindex[lindex key index]通过索引获取列表中的元素
llen获取列表的长度
lrem[lrem key count value]移除列表元素
rpoplpush[rpoplpush sourceList targetList]:从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

可以实现 「阻塞队列」,结合lpush和brpop命令就可以实现。生产者使用lupsh从列表的左侧插入元素,消费者使用brpop命令从队列的右侧获取元素进行消费。

底层实现:3.2之前的版本是使用ziplistlinkedlist进行实现的。在3.2之后的版本就是引入了quicklist

3. Redis 中如何保证缓存和数据库双写时的数据一致性?

无论先操作db还是cache,都会有各自的问题,根本原因是cache和db的更新不是一个原子操作,因此总会有不一致的问题。想要彻底解决这种问题必须将cache和db的更新操作归在一个事务之下(例如使用一些分布式事务,或者强一致性的分布式协议)。

或者采用串行化,可以保证强一致性。
在这里插入图片描述

3.1 写请求为什么更新数据库后是删除缓存而不是更新缓存?

在这里插入图片描述
注意看上面的图片,当有两个写请求的线程,线程一比线程二先执行,反而是线程二先执行完。这时候,缓存保存的是A的数据(老数据),数据库保存的是B的数据(新数据),数据不一致了。

3.2 写请求时,为什么更新数据库,然后再删除缓存?

在这里插入图片描述
如果采用写请求,先删除缓存,再更新数据库就会出现如上图的情况,线程B读到的是老的数据,并且缓存中也保存的是老的数据。

3.3 写请求时,先更新数据,后删除缓存一定没有问题吗?

在这里插入图片描述
可以看到一个读请求和一个写请求,读请求可能会读取到旧的数据,或者当写请求删除缓存失败,读请求会一直读取的是旧的缓存数据。只不过是这种情况,相对于其他的实现方式概率要低很多。

3.4 三种方案保证数据库与缓存的一致性

3.4.1 缓存延时双删

在这里插入图片描述
第二次删除缓存一般会采用延时的操作,主要是用来删除读请求产生的缓存数据

3.4.2 删除缓存重试机制

延时双删和普通写操作的删除操作都有可能会操作失败,导致数据不一致,删除重试机制就是为了保证删除可靠性。(删除失败的key放到消息队列中)这种机制会造成大量的业务代码入侵。

3.4.3 读取biglog异步删除缓存

通过binlog日志,将要删除的key发送到消息队列中。

4. 如何使用 Redis 做异步队列和延时队列?

4.1 延时队列

将需要延时执行的任务放到 Redis 中的 Zset 类型中,Zset会根据 score 自动进行数据排序(score使用时间戳),定义一个延时任务检测器,检测器使用 zrangebysocre 命令查询 Redis 中符合执行条件的任务执行

4.2 异步队列

Redis的队列list是有序的且可以重复的,作为消息队列使用时可使用rpush/lpush操作入队,使用lpop/rpop操作出队。当发布消息是执行lpush命令,将消息从列表左侧加入队列。消息接收方执行rpop命令从列表右侧弹出消息。

如果队列空了,消费者会陷入pop死循环,即使没有数据也不会停止。空轮询不但消耗消费者的CPU资源还会影响Redis的性能。并且需要不停的调用rpop查看列表中是否有待处理的消息。每调用一次都会发起一次连接,势必造成不必要的资源浪费。

入队的速度大于出队的速度,消息队列长度会一直增大,时间长了会占用大量的空间。

针对上面的 rpop 命令会一直阻塞队列,Redis提供了一种更优的 brpop命令,brpop可以设置一个超时时间,

5. Redis 中的过期策略

Redis 中的过期策略共有三种

  1. 定时删除
  2. 定期删除
  3. 惰性删除

Redis 采用的过期策略是 定期+惰性 删除

5.1 定时删除

在设置key的过期时间的同时为该key创建一个定时器,让定时器在key的过期时间来临时,对key进行删除

优点: 保证内存被尽快释放

缺点: 过期的key太多,删除这些key会占用很多的CPU时间。设置了过多的定时器,会对redis 的性能造成影响。

5.2 定期删除

默认一段时间就去随机部分扫描redis中的设置了过期时间的key,检查是否过期,过期的话就移除key

5.2.1 为什么定期删除只扫描部分设置了过期时间的key

因为扫描全部的key会非常多,很影响性能

5.3 惰性删除

惰性删除就是等到有查询key的请求过来的时候,我看看这个key有没有过期,过期的话就删除这个key

缺点: 可能会造成内存泄漏

6. Redis 中的内存淘汰机制

设置方式: config set maxmemory-policy volatile-lru

  1. no-eviction: 禁止驱逐数据(当内存达到限制时,就报错)
  2. allkeys-lru: 从redis 中回收最近使用最少的键
  3. volatile-lru: 从设置了过期时间的键中,回收最近使用最少的键
  4. allkeys-random:随机回收redis中的键
  5. volitile-random:从设置了过期时间的键中,随机回收
  6. volitile-ttl:从设置了过期时间的键中,回收存活时间较少的键

关于volatile-lru:LRU 算法实现:1.通过双向链表来实现,新数据插入到链表头部;2.每当缓存命中(即缓 存数据被访问),则将数据移到链表头部;3.当链表满的时候,将链表尾部的数据丢弃。

指定redis 的淘汰策略
# maxmemory-policy noeviction

7. Redis 中的缓存击穿,缓存穿透,缓存雪崩

  1. 缓存雪崩:缓存同一时间大面积的失效,后面的请求都会落到数据库上,造成数据库短时间内承受大量的数据请求
    1. 解决方案:缓存数据的过期时间随机设置,防止同一时间大量的数据过期的情况发生
  2. 缓存穿透:是指缓存和数据库中都没有数据,导致所有的请求都落到数据库上。数据库短时间承受大量的请求而崩掉
    1. 缓存取不到,数据库也娶不到的数据, 设置为key—null 的方式
    2. 布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap 中去
  3. 缓存击穿: 高并发查询同一条数据,但是redis 中的数据过期了。导致请求发送到了数据库,造成缓存击穿
    1. 解决方案: 设置热点数据永不过期;
    2. 使用互斥锁。分布式情况下使用分布式的锁

8. Redis 中的多路I/O复用模型

Redis采用的是多路I/O复用模型。

8.1 什么是多路I/O复用模型?

多路:多个socket连接,

复用:复用一个线程

多路I/O复用技术可以让单个线程高效的处理多个连接请求(尽量的减少网络IO的时间消耗)。

8.2 Redis 为什么这么快?

Redis采用多路I/O复用模型,且完全基于内存操作。

9. Redis 中的发布订阅模式

Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。Redis客户端可以订阅任意数量的频道。
客户端订阅频道

# 第一个客户端
redis 127.0.0.1:6379> SUBSCRIBE runoobChat    # 这个客户端订阅了一个叫 runoobChat  的频道
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "runoobChat"
3) (integer) 1
# 第二个客户端
redis 127.0.0.1:6379> PUBLISH runoobChat "Redis PUBLISH test"    #向runoobChat 频道中发布消息
(integer) 1
redis 127.0.0.1:6379> PUBLISH runoobChat "Learn redis by runoob.com"
(integer) 1

发布消息之后,就能再 订阅频道的客户端上看到发布的消息数据

10. Redis 的过期通知功能

  1. 修改redis.conf 文件(notify-keyspace-events Ex)
  2. 订阅Key过期事件
//psubscribe 订阅命令
//以 keyspace 为前缀的频道被称为键空间通知(key-space notification)
//而以 keyevent 为前缀的频道则被称为键事件通知(key-event notification)
//0 表示数据库 表示对1号库操作
//expired 通知(每当一个键因为过期而被删除时产生通知)
psubscribe __keyevent@0__:expired
  1. 开启终端并设置一个过期时间 setex name 10 expire
  2. 10秒过后订阅端会展示一下信息
1) "pmessage"
2) "__keyevent@0__:expired"
3) "__keyevent@0__:expired"
4) "name"

11. Redis和memcache的区别

  1. 都是基于内存的非关系型数据库
  2. memcache支持的数据类型比较单一String类型(但是可以存储图片,视频),redis支持的数据类型比较多。有五种(string,set,zset,list,hash)
  3. redis支持持久化,memcache不支持
  4. memcache 键名字段限制在250个字节,值限制1M。redis键名和值的限制都是512M
  5. memcache不支持主从,redis支持主从
  6. redis是单线程(多路 IO 复用模型),memcache是多线程(非阻塞IO复用的网络模型

memcache如何实现主从?

memcache自己没有防止单点的措施,为了保障memcache服务的高可用,需要借用外部的Repcached 工具来实现,Repcached 用来实现 Memcached 复制功能的一个工具。它所构建的主从方案是一个单主单从的方案,不支持多主多从。但是,它的特点是,主从两个节点可以互相读写,从而可以达到互相同步的效果。

假设主节点坏掉,从节点会自动切换为监听状态从而成为主节点,等待其他从节点额加入。

为什么高并发下有时单线程的 redis 比多线程的 memcached 效率要高?

原因:memcache 多线程模型引入了缓存一致性和锁,加锁带来了性能损耗。

12. Redis 中的持久化机制

什么是持久化?

Redis 中的数据都是保存在内存中的,当Redis服务重启后,内存中的数据都会丢失,所以需要将内存中的数据保存到磁盘上,方便系统故障时,从磁盘上的备份数据恢复到内存中。

Redis 中的持久化方式有两种,RDB全量持久化和AOF增量持久化。一般情况下,Redis 中的RDB和AOF方式都需要开启。两者同时开启的情况下,默认使用 AOF方式。

12.1 RDB持久化

RDB持久化的方式是对Redis中的数据,进行周期性的存储。RDB方式会生成一个RDB文件,RDB文件中记录的都是某一时刻里Redis中的数据。

12.1.1 RDB 的工作方式

RDB方式,Redis会是通过一个fork程来做持久化的工作。fork线程先将数据写入到一个临时文件中,写入成功之后,再替换掉之前的 RDB 文件,不会影响主线程的工作,所以这种方式对Redis的性能影响很小。

12.1.2 如何调整RDB方式的工作参数?

通过调整 redis.conf 配置文件中的save参数

save 900 1  代表 900s 之内有 1个 key发生修改,则发起内存快照保存
save 300 10  代表 300s 之内有 10个 key发生修改,则发起内存快照保存

#当RDB持久化出现错误后,是否依然进行继续进行工作,yes:不能进行工作,no:可以继续进行工作 
stop-writes-on-bgsave-error yes
#使用压缩rdb文件,rdb文件压缩使用LZF压缩算法,yes:压缩,但是需要一些cpu的消耗。no:不压缩,需要更多的磁盘空间
rdbcompression yes
#rdb文件的名称
dbfilename dump.rdb
#数据目录,数据库的写入会在这个目录。rdb、aof文件也会写在这个目录
dir /var/lib/redis

12.1.3 缺点

由于RDB都是快照文件,并不是实时更新的,当Redis突然宕机,会造成数据丢失的情况。

12.2 AOF持久化

12.2.1 AOF的工作方式

AOF方式默认是通过一秒一次的一个后台线程 fsync 来进行数据备份操作的,操作方式是 append-only (只追加)的方式

12.2.2 AOF方式的工作参数调整

Redis 运行时打开AOF

redis-cli> CONFIG SET appendonly yes

修改 redis.conf 文件,设置不同的fsync策略

appendfsync yes        # 开启AOF功能
appendfsync no        #  表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。由操作系统决定何时同步
appendfsync always    #  每次有数据修改时都会写入AOF文件
appendfsync everysec   # 每秒同步一次,该策略为AOF的缺省策略

#aof文件名, 保存目录由 dir 参数决定
appendfilename "appendonly.aof"
# AOF文件重写时,是否需要将写指令通过追加的方式追加到原有的AOF文件中。(yes只写入到内存缓冲区,no写入到内存缓冲区同时追加到原有AOF文件)
no-appendfsync-on-rewrite no

#aof自动重写配置。当目前aof文件大小超过上一次重写的aof文件大小的百分之多少进行重写,当前AOF文件大小是上次日志重写得到AOF文件大小的二倍(设置为100)时,自动启动新的日志重写过程。
auto-aof-rewrite-percentage 100
#设置允许重写的最小aof文件大小,避免了达到约定百分比但尺寸仍然很小的情况还要重写
auto-aof-rewrite-min-size 64mb

#aof文件可能在尾部是不完整的,当redis启动的时候,aof文件的数据被载入内存。重启可能发生在redis所在的主机操作系统宕机后,尤其在ext4文件系统没有加上data=ordered选项(redis宕机或者异常终止不会造成尾部不完整现象。)出现这种现象,可以选择让redis退出,或者导入尽可能多的数据。如果选择的是yes,当截断的aof文件被导入的时候,会自动发布一个log给客户端然后load。如果是no,用户必须手动redis-check-aof修复AOF文件才可以。
aof-load-truncated yes

12.2.3 缺点

AOF方式采用只追加的方式,每条命令都会追加到磁盘中,对Redis性能的影响很大。

12.2.4 AOF 的 rewrite(重写)机制

由于AOF是采用只追加的方式,这种方式会导致 AOF 文件越来越大,所以 Redis 为 AOF 提供了rewrite(重写)机制。

rewrite机制:当AOF文件大小超过所设定的阈值,redis就会启动AOF的文件内容压缩。

rewrite是怎么实现文件内容压缩的?
比如我们的AOF文件中由 100 条INCR指令,压缩时会把这一百条指令合并成一条SET指令。
FLUSHALL指令被执行后,如何通过AOF文件恢复?
FLuSHALL是将redis中的内存数据全部清空,这种情况下,`只要开启了AOF方式,而且AOF还没有被重写`,我们只需要通过快速停止Redis并编辑AOF文件,删除最后的FLUSHALL指令。重启Redis就能将数据恢复。

rewrite方式的内部工作原理

在重写开始时,Redis 会创建一个 fork(重写子进程),这个fork进程会读取现有的 AOF 文件,将现有AOF文件中的指令压缩并写入到一个临时文件中。

在fork进程压缩写入临时文件的同时,主工作进程会将新接收到的写指令一边写入到内存缓冲区一边继续写入到原有的AOF文件中(保证原有AOF文件的可用性,防止重写时发生意外)

当fork进程重写完成后,会向主进程发送一个信号,主进程收到信号后,会将内存缓冲区中的写指令以追加的方式追加到临时文件中,当临时文件(新AOF文件)追加结束后,就会替换掉原有的AOF文件。之后再有新的写指令就会追加到新的AOF中了;

12.3 RDB和AOF 方式差异比较

性能:RDB方式使用一条fork线程来进行持久化,对Redis性能影响较小,AOF方式通过后台线程 fsync 通过追加的方式,对Redis的性能影响较大。

文件:相同数据集的情况下,AOF文件的大小会远大于RDB文件

存储方式:RDB采用快照的方式,AOF采用只追加的方式

恢复速度:RDB快于AOF

数据方面:AOF和RDB都有可能造成数据丢失,AOF方式更加可靠。

13. Redis 架构

13.1 单机模式

13.1.1 单机模式的风险和问题

  1. 机器故障
    1. 硬盘故障:导致数据丢失
    2. 系统崩溃:可能会对业务造成灾难性打击
  2. 容量瓶颈
    1. 内存不足

为了避免单点Redis服务器故障,准备多台服务器,互相连通。将数据复制多个副本保存在不同的服务器上,连接在一起,并保证数据同步。这样即使是有一台服务器宕机,其他服务依然可以提供服务。实现Redis的高可用。同时实现数据的冗余备份。这种形式也就是主从复制架构

13.2 主从复制模式

一个master可以拥有多个slave节点,一个slave节点只能有一个master节点

master:
	1. 写数据
	2. 执行写操作时,将变化的数据自动同步到slave节点
slave:
	1. 读数据
master节点是可以用来读数据的(不建议master节点做读操作)
slave节点是禁止用来写数据的(redis 2.6后默认从服务器时不能进行写操作的,但是可以通过 replica-read-only  yes|no 开启从服务器写)

在这里插入图片描述

13.2.1 主从架构的优点

  1. 读写分离(master写,slave读,提高服务器的读写负载)
  2. 负载均衡(基于主从架构,配合读写分离,由slave分担master负载,改变slave的数量,通过多个从节点分担数据读取负载,提高Redis服务器并发量与数据吞吐量)
  3. 故障恢复(当master出现问题,由slave提供服务,实现快速的故障恢复)
  4. 数据冗余(实现数据热备份,是持久化之外的一种数据冗余方式)
  5. 高可用的基石(基于主从复制,构建哨兵模式与集群,实现Redis的高可用方案)

13.2.2 主从复制的三个阶段

  1. 建立连接阶段工作流程(结果:master中保存slave的IP端口,slave保存master的IP端口,同时之间建立socket连接)

    1. 设置master的地址和端口,保存master信息

      1. slave节点:发送指令 slaveof ip port
      2. master节点:接收到指令,响应slave节点
      3. slave节点:保存master的IP和端口
    2. 建立socket连接(slave节点:根据保存的信息创建和master连接的socket)

    3. 发送ping命令

      1. slave 定时器发送ping指令(测试连接是否断开)
      2. master响应pong指令(回应slave连接未断开)
    4. 身份验证

      1. slave:发送指令 auth password (master设置密码,就需要进行权限校验)
      2. master 验证授权
      3. slave发送自己的端口给到master(master通过slave的端口实现监听)
      4. master保存slave的端口号
      redis设置密码的方式和连接方式
      # 配置文件方式
      master节点:redis.conf ==> requirepass <password>
      slave节点:redis.conf  ==>	masterauth <password>
      # 命令方式
      master:config set requirepass <password>
      	    config get requirepass
      slave:auth <password>
      客户端:redis-cli -a <password>
      
  2. 数据同步阶段(结果:slave包含有master端的全部数据,master端保存有slave当前数据同步的位置)

    1. 请求同步数据(slave发送psync2指令)

      redis 1.0 :sync 指令
      redis 2.8 :psync 指令
      redis 4.0 :psync2 指令
      
      1. slave 发送 psync2 指令
      2. master执行bgsave指令,并且创建一个复制缓冲区
    2. 创建RDB文件同步数据

      1. master生成RDB文件,通过socket发送给slave
    3. 恢复RDB同步数据

      1. slave节点接收RDB文件,清空原有的slave数据,执行RDB文件恢复过程
      上述的几个步骤统称为“全量复制”
      
    4. 请求部分同步数据

      1. slave发送命令告知RDB恢复已经完成
      2. master 发送复制缓冲区中的aof指令
    5. 恢复部分同步数据

      1. slave接收aof指令,执行 bgrewriteaof,恢复数据
      “全量复制”后的三步称之为 “增量复制”
      
  3. 命令传播阶段(实时保证数据同步)

    当master数据库状态被修改后,导致主从服务器数据库状态不一致,此时需要让主从数据同步到一致状态。master将接收到的数据变更命令发送给slave,slave接收命令后执行命令

    命令传播阶段中的心跳机制

    命令传播阶段,master和slave间的交换信息通过心跳机制进行维护,实现双方连接保持在线

    1. master:通过ping指令判断slave是否在线(周期由:repl-ping-slave-period 决定,默认10s)
    2. slave:通过 REPLCONF ACK {offset}指令判断master是否在线,并向master汇报slave自己的偏移量,获取最新的数据变更指令。(周期1s一次)

    服务器运行ID(runid): 每台服务器的唯一身份标识(同一个服务器每次运行都有不同的runid),40位字符组成,runid在服务器间传输被用作身份标识,master首次连接slave时,会将自己的runid发送给slave,slave保存runid。

    复制缓冲区: 一个FIFO(先进先出)队列,用于存储服务器执行过的命令,每次传播命令,master都会将传播的命令记录下来,并保存在复制缓冲区。

在这里插入图片描述

复制缓冲区的组成:

  1. 偏移量
  2. 字节值

复制缓冲区的内部工作原理:

  1. 通过offset区分不同的slave当前数据传播的差异
  2. master记录已发送的信息对应的offset (master中会有多个slave节点的偏移量)
  3. slave记录已接收的信息对应的offset(一个自己从master节点接收到的偏移量)
master 通过命令传播程序发送命令到slave节点时,可能会由于网络原因导致一部分slave节点没有接收到命令,通过复制缓冲区保存命令,并通过master节点和slave节点确认偏移量来保证数据的准确同步。
偏移量:同步信息,比对master和slave的差异,当slave断线后,恢复数据使用

13.2.3 主从数据同步三阶段的总结(数据同步阶段的细节)

  1. slave节点通过保存的master端口IP和master节点建立socket连接,连接成功之后。
  2. slave发送指令
psync2	?	-1
# psync2 <runid> <offset>
# slave 节点第一次连接不知道master节点的runid所以为 “?”
# -1 :代表全部
  1. master节点接收到指令后,执行bgsave 生成RDB文件,记录当前的复制偏移量 offset。并发送
+FULLRESYNC runid offset
# +FULLRESYNC:全量

​ 通过socket发送RDB文件给到slave(发送RDB文件期间offset可能会发生变化)

  1. slave收到 +FULLRESYNC 保存master的runid和offset,清空当前全部数据,通过socket接收RDB文件,恢复RDB文件(2,3,4为全量复制的过程

  2. slave发送命令

    psync2 runid offset
    
  3. master 接收命令,判断runid是否匹配,判定offset是否在复制缓冲区中

  4. master判断如果 runid和offset有一个不满足,执行全量复制(回退到2节点)

  5. master判断如果 runid和offset校验通过,master.offset 和 slave.offset 相同,忽略

  6. master判断如果 runid和offset校验通过,master.offset 和 slave.offset 不相同,发送

    +CONTINUE offset
    

    通过socket发送复制缓冲区中的 master.offset 到 slave.offset 的数据

  7. slave 收到 +CONTINUE 保存master的offset,接收信息后,执行 bgwriteaof 恢复数据(5,6,7,8,9,10 为增量复制的过程

  8. slave发送心跳命令 replconf ack offset

  9. master判断如果 offset 不满足,执行全量复制(回退到2节点)

  10. master判断如果 offset校验通过,master.offset 和 slave.offset 相同,忽略

  11. master判断如果 offset校验通过,master.offset 和 slave.offset 不相同,发送

    +CONTINUE offset
    

    通过socket发送复制缓冲区中的 master.offset 到 slave.offset 的数据

  12. slave 收到 +CONTINUE 保存master的offset,接收信息后,执行 bgwriteaof 恢复数据

13.2.4 主从复制数据同步阶段需要注意的问题

  1. 如果master数据量巨大,数据同步阶段应该避开流量高峰期,避免造成master阻塞,影响业务正常运行
  2. 复制缓冲区大小设定不合理,会导致数据溢出(数据丢失)。如进行全量复制周期太长,进行部分复制时发现数据已经丢失的情况,则必须进行第二次全量复制,致使slave陷入死循环
数据溢出就会造成复制缓冲区中的数据不完整(数据丢失),不完整的情况下,master和slave进行增量同步时,就不能正常完成。在增量同步不能正常完成的情况下,slave和master会再次进行全量复制。全量复制的过程中,又有大量的信息进入到复制缓冲区导致数据溢出,就会进入一个死循环。
# 调整复制缓冲区的大小
master节点: repl-backlog-size 1mb
  1. 多个slave节点同时对master请求数据同步,master发送的RDB文件增多,会对带宽造成巨大冲击,如果master带宽不足,需要适当错峰

  2. slave节点过多时,可以调整拓扑结构变为树状结构,中间的节点既是master又是slave节点(采用树状结构时,由于层级关系,导致深层次的slave节点与最顶层master数据同步延迟较大,数据一致性会变差)

在这里插入图片描述

13.2.5 主从复制的配置(如何配置主从)

连接slave节点,在slave节点的redis.conf配置文件中添加一行配置

slaveof <masterip> <masterport>
# masterip: master节点的ip
# masterport: master节点的port

13.2.6 主从复制模式常见问题

全量复制问题
  1. master节点重启,runid发生变化,带来的全量复制问题。
    解决方案:(master内部的优化方案,不需要手动操作)master内部创建 master_replid 变量,在master关闭时执行shutdown save 命令。
master_replid : 使用 runid 想用的策略生成,长度41 位,并发送给所有的slave节点(通过 redis-check-rdb  [dump文件名]  命令可以查看该信息)
shutdown save 命令:关闭时进行RDB持久化,将runid与offset 保存到RDB文件中,重启时加载RDB文件,恢复数据。
  1. 频繁全量复制,缓冲区设置太小导致 (调整缓冲区大小 通过repl-backlog-size命令)

如果从服务器断线重连,会根据新的主服务器id和之前的主服务器id进行对比来判定是否是之前的主服务器来选择进行完整重同步还是部分重同步。

节点间数据不一致

由于网络原因导致slave节点间的数据不一致(slave 中的偏移量不相同)(分布式环境中很常见的问题,是否需要解决看业务容忍度),
可以通过优化主从间的网络环境解决,同机房部署。或者通过监控 主从节点的延迟(比较slave节点的offset差异),暂时屏蔽延迟大的slave节点。(通过 slave-serve-stale-data yes|no ,这个命令执行后,slave节点的数据就不能访问了。)

13.3 哨兵模式

哨兵也是一台redis服务器,只是不提供数据服务。通常哨兵配置数量为单数()
在这里插入图片描述

13.3.1 Sentinel 的使用

Sentinel 的配置文件
bind 0.0.0.0
port 26379 
dir /tmp  # 工作信息的存储目录
sentinel monitor mymaster 192.168.200.128 6379 1 
sentinel down-after-milliseconds mymaster 10000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
# mymaster 主节点名,可以任意起名,但必须和后面的配置保持一致。
# 192.168.200.129 6379 主节点连接地址。
# 1 将主服务器判断为失效需要投票,这里设置至少需要 1个 Sentinel 同意。
# sentinel down-after-milliseconds mymaster 10000:设置Sentinel认为服务器已经断线所需的毫秒数。
# sentinel failover-timeout mymaster 60000:设置failover(故障转移)的过期时间。多长时间内同步数据成功的限值
# sentinel parallel-syncs mymaster 1 设置在执行故障转移时, 最多可以有多少个从服务器同时对新的主服务器进行同步, 这个数字越小,表示同时进行同步的从服务器越少,那么完成故障转移所需的时间就越长。
启动Sentinel
/redis-sentinel sentinel01.conf  # sentinel01.conf:sentinel的配置文件

13.3.2 Sentinel 的三大作用

监控(Monitoring): Sentinel 会不断地检查(ping指令)你的主服务器和从服务器是否运作正常。
提醒(Notification): 当个 Sentinel 监控 Redis服务器的状态,会在多个Sentinel之间进行数据共享
自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时,Sentinel会开始一次自动故障转移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器。

故障转移挑选新master的原则:
1. 在线的slave节点 
2. 响应快的(与Sentinel响应)
3. 与原master响应频繁的
4. 优先原则(offset小的,runid 小的)
挑选好新的master节点后,Sentinel 向 新master发送 slaveof no one 指令,向其他的slave发送   slaveof [新master的IP] [新master的端口]

Sentinel 的原理

主动下线

概念:主观下线(Subjectively Down, 简称 SDOWN)指的是单个 Sentinel 实例对服务器做出的下线判断

如果一个 redis 服务器没有在 master-down-after-milliseconds 选项所指定的时间内, 对向它发送 PING 命令的 Sentinel 返回一个有效回复, 那么Sentinel 就会将这个服务器标记为主观下线

单个Sentine判断Redis服务器主观下线之后,会通过提醒(流言传播(Gossip))告知其他的Sentinel服务器,其他的Sentinel就来围观这台Redis服务器,超过半数的Sentinel认为Redis主管下线后,则该Redis服务器的状态变为客观下线。

客观下线

概念:多个 Sentinel 实例在对同一个服务器做出 SDOWN 判断, 并且通过SENTINEL is-master-down-by-addr 命令互相交流之后, 得出的服务器下线判断ODOWN。 (一个Sentinel 可以通过向另一个 Sentinel 发送命令来询问对方是否认为给定的服务器已下线)

客观下线条件只适用于主服务器,对于其他类型的 Redis 实例, Sentinel
在将它们判断为下线前不不需要进行协商, 所以从服务器或者其他
Sentinel 不会达到客观下线条件。 只要一个 Sentinel 发现某个主服务器进
入了客观下线状态, 这个Sentinel就可能会被其他 Sentinel 推选出,并对
失效的主服务器执行自动故障迁移操作。

14. Redis-Cluster 高可用集群

Redis Cluster内置集群,在Redis3.0才推出的实现方案。Redis Cluster是无中心节点的集群架构,依靠Gossip协议(谣言传播(PING-PONG机制))协同自动化修复集群的状态。节点的fail是通过集群中超过半数的节点检测失效时才生效。客户端与redis节点直连,不需要中间proxy层,客户端不需要连接集群所有节点。
为什么需要三主三从?
因为内部存在一个投票机制,节点的有效性是需要靠投票的,一个节点无法给自己投票,两个节点互相都连不上,A=>B,B=>A互相都说对方挂了没有定论.三个节点才能满足投票的需求

14.1 集群的优点:

  1. 负载均衡(降低单台Redis服务器的访问压力)
  2. 可拓展性(降低单台Redis服务器的存储压力)
  3. 容灾处理

14.2 Redis-Cluster中的数据分布

Redis-Cluster中引入虚拟槽分区(哈希槽),共有16384(0~16383)个槽,这些槽平均分配到每个 master 上,在存储数据时利用 CRC16 (slot=CRC16(key)/16384 )算法计算key属于哪个槽。每个master节点都会分配一定槽节点数量,并且其他节点存储的槽范围在节点上也是透明的(内部可知)

14.3 Redis-Cluster 的配置

需要修改redis.conf 配置文件

cluster-enable yes  # 表示这个Redis 是一个cluster节点
cluster-config-file [filename.conf]  # cluster的配置文件
cluster-node-timeout 10000  # 节点服务超时时间,用于判定该节点是否下线或切换为从节点

cluster集群中各个redis依次启动完成后,需要使用

./redis-trib.rb create --replicas 1 ip1:port1 ip2:port2 ip3:port3 ip4:port4 ip5:port5 ip6:port6
# create 代表创建集群
# --replicas: 指定内部结构 1:代表一个master连接一个slave节点 ip1:port1->ip4:port4 ip2:port2->ip5:port5 ip3:port3->ip6:port6 
# 2: 代表一个master连接两个slave节点 ip1:port1->ip3:port3,ip4:port4  ip2:port2->ip5:port5,ip6:port6 

cluster集群搭建好之后,需要注意的是要使用 redis-cli -c 来连接 cluster集群。

14.4 Cluster的 slot 槽重分配

Redis-Cluster 支持动态的新增和删除节点

新增

./redis-trib.rb add-node [ip1:port1] [ip2:port],新增成功后我们可以通过 cluster nodes 命令查看集群节点信息,我们可以看到 新增的节点是没有被分配 slot槽的。

  1. 手动分配槽节点
    cluster setslot 5462 node e4fee6a90d5b071ad514cd30f8d5d9a1c13947b3
    cluster setslot [] node [目标id]
    
  2. 自动分配槽节点
    ./redis-trib.rb reshard ip:port
    

删除

对于已经分配了槽区间的节点,我们是无法直接删除的,需要先将槽区间分配出去,然后再进行删除

  1. 将槽区间分配到其他节点
./redis-trib.rb reshard ip:port
  1. 删除命令
./redis-trib.rb del-node ip:port 目标id

14.5 Cluster集群的问题

  1. 集群内部采用Gossip协议(谣言传播)协同自动化修复集群的状态,但Gossip有消息延时和消息冗余的问题,在集群节点数量过多的时候,节点之间需要不断进行PING/PANG通讯,不必须要的流量占用了大量的网络资源。
  2. Redis Cluster可以进行节点的动态扩容缩容,在扩缩容的时候,就需要进行数据迁移。而Redis 为了保证迁移的一致性, 迁移所有操作都是同步操作,执行迁移时,两端的 Redis 均会进入时长不等的 阻塞状态。对于小 Key,该时间可以忽略不计,但如果一旦 Key 的内存使用过大,严重的时候会接触发集群内的故障转移,造成不必要的切换
  3. Gossip 的缺陷 - 消息的延迟 由于 Gossip 协议中,节点只会随机向少数几个节点发送消息,消息最终是通过多 个轮次的散播而到达全网的。 因此使用 Gossip 协议会造成不可避免的消息延迟。不适合用在对实时性要求较高 的场景下。 - 消息冗余 Gossip 协议规定,节点会定期随机选择周围节点发送消息,而收到消息的节点也 会重复该步骤。 因此存在消息重复发送给同一节点的情况,造成了消息的冗余,同时也增加了收到 消息的节点的处理压力。 而且,由于是定期发送而且不反馈,因此即使节点收到了消息,还是会反复收到重 复消息,加重了消息的冗余。

15. twemproxy集群

Twemproxy由Twitter开源,是一个redis和memcache快速/轻量级代理服务器,利用中间件做分片的技术。twemproxy处于客户端和服务器的中间,将客户端发来的请求,进行一定的处理后(sharding),再转发给后端真正的redis服务器。
在这里插入图片描述
twemproxy主要的角色是代理服务器的作用,是对数据库进行分片操作。twemproxy的分片保证需要存储的数据散列存放在集群的节点上,尽量做到平均分布。如何实现呢,这里就涉及到一致性哈希算法。

15.1 哈希算法比较

判断哈希算法好坏的指标

  1. 平衡性(数量大致相同)
  2. 单调性(单调性是指如果已经有一些请求通过哈希分派到了相应的服务器进行处理,
    又有新的服务器加入到系统中时候,哈希的结果应保证原有的请求可以被映射
    到原有的或者新的服务器中去,而不会被映射到原来的其它服务器上去。)
  3. 分散性(尽量降低分散性)

15.1.1 传统Hash算法

哈希算法: 哈希算法的哈希函数比较简单,一般是根据某个key的值或者key 的哈希值与当前可用的 master节点数取模,根据取模的值获取具体的服务器

15.1.2 一致性Hash算法

一致性算法:一致性哈希算法可以说是哈希算法的升级版,解决了哈希算法扩展性差的问题,一致性哈希算法跟哈希算法不一样,一致性哈希算法会将服务器和数据都通过哈希函数映射到一个首尾相连的哈希环上,存储节点映射可以根据 ip 地址来进行哈希取值,数据映射到哈希环上后按照顺时针的方向查找存储节点,即从数据映射在环上的位置开始,顺时针方向找到的第一个存储节点,那么他就存储在这个节点上。twemproxy也选择这种算法,解决将key-value均匀分配到众多 server上的问题。它可以取代传统的取模操作,解决了取模操作应对增删 Server的问题。

15.1.2.1 实现步骤
  1. 先用hash算法将对应的节点ip哈希到一个具有232次方个桶的空间中,即0~(232)-1的数字空间。现在我们可以将这些数字头尾相连,连接成一个闭合的环形
    在这里插入图片描述
  2. 当用户在客户端进行请求时候,首先根据key计算路由hash值,然后看hash值落到了hash环的哪个地方,根据hash值在hash环上的位置顺时针找距离最近的节点
    在这里插入图片描述
  3. 当新增节点的时候,和之前的做法一样,只需要把受到影响的数据迁移到新节点即可新增Master4节点
    在这里插入图片描述
  4. 当移除节点的时候,和之前的做法一样,把移除节点的数据,迁移到顺时针距离最近的节点移除Master2节点
    在这里插入图片描述
15.1.2.2 虚拟节点

一部分节点下线之后,虽然剩余机器都在处理请求,但是明显每个机器的负载不不均衡,这样称
为一致性hash的倾斜,虚拟节点的出现就是为了了解决这个问题。在刚才的例子当中,如果Master3节点也挂掉,那么一致性hash倾斜就很明显了:
在这里插入图片描述
上面这个例子中,我们可以对已有的两个节点创建虚拟节点,每个节点创建两个虚拟节点。那么实际的Master1节点就变成了两个虚拟节点Master1-1和Master1-2,而另一个实际的Master4节点就变成了两个虚拟节点Master4-1和Master4-2,这个时候数据基本均衡了:
在这里插入图片描述

15.1.3 Hash算法的优缺点对比

哈希算法: 哈希算法在分布式中的作用,比较简单,可以看出只要你哈希函数设计的好,数据在各个服务器上是比较均匀分布的,但是哈希算法有一个致命的缺点:扩展性特别的差,比如我们的集群中,服务器server3 宕机了,这时候集群中可用的机器只有两台了,这样哈希函数就变成了id % 2了,这就会导致一个问题,所有的数据需要重新计算,找到新的存储节点,每次有服务器宕机或者添加机器时,都需要进行大量的数据迁移,这会使得系统的可用性、稳定性变差。
一致性哈希算法: 一致性哈希会造成数据分布不均匀的问题(数据倾斜)。相比普通哈希算法,能够减少节点扩展时的数据迁移问题

15.1.4 Hash算法案例分析

假设有三个master节点
哈希算法:
假设哈希算法中的哈希函数为“id % master 节点数”,结果为 0 的数据存放到 server1 服务器上,结果为 1 的数据存放到 server2 服务器上,结果为 2 的数据存放到 server3 服务器上。
所以经过哈希算法之后,id=3、id=6 的数据与 master 节点数取模为 0 (3%3=0,6%3=0),所以这两个数据会存放到 server1 服务器 ,以此类推,id=1、id=4 的数据将存放到 server2 服务器中,id=2、id=5 的数据将存放到 server3 上。
一致性哈希算法:
按照一致性哈希算法的规则,数据沿着顺时针的方向查找数据,那么 id=4 的数据存放在 server1 服务器,id=2 的数据存放在服务器 server2 上,id=3、id=1、id=5、id=6 的数据都存放在服务器 server3 上,如果你比较敏感的话,也许你就会发现一致性哈希算法的不足之处, 从图中可以看出,我们六条数据分布不均匀,并不是每台服务器存储 2 条数据,而且差距好像还有点大,这里我们就要来说一说一致性哈希算法的缺点:一致性哈希算法会会造成数据分布不均匀的问题或者叫做数据倾斜问题,就像我们图中那样,数据分布不均匀可能会造成某一个节点的负载过大,从而宕机。造成数据分布不均匀有以下两种情况:
第一:哈希函数的原因,经过哈希函数之后服务器在哈希环上的分布不均匀,服务器之间的间距不相等,这样就会导致数据不均匀。
第二:某服务器宕机了,后继节点就需要承受原本属于宕机机器的数据,这样也会造成数据不均匀。
前面我们提到过一致性哈希算法解决了哈希算法中扩展性差的问题,这个怎么理解呢?我们来看看,在一致性哈希算法中当有存储节点加入或者退出时,只会影响应该该节点的后继节点,举个例子说明一下,例如我们要在服务器server3 和服务 server2 之间加入了一个服务器存储节点 server4,只会对服务器server3 造成影响,原本存储到服务器server3 上的数据有一部分会落入到服务器 server4 上,对服务器 server1 和 server2 并没有任何影响,这样就不会进行大量的数据迁移,扩展性就变强了。
​​​​​​​​在这里插入图片描述

16. 集群使用场景总结

  1. 单机版:数据量,QPS不大的情况使用
  2. 主从复制:需要读写分离,高可用的时候使用
  3. Sentinel哨兵:需要自动容错容灾的时候使用
  4. 内置集群:数据量比较大,QPS有一定要求的时候使用,但集群节点不能过多
  5. twemproxy集群:数据量,QPS要求非常高,可以使用

17. Redis 配置文件

redis.conf
# 端口号
port 7000
# 后台启动
daemonize yes
# 开启集群
cluster-enabled yes
#集群节点配置文件
cluster-config-file nodes-7000.conf
# 集群连接超时时间
cluster-node-timeout 5000
# 进程pid的文件位置
pidfile /var/run/redis-7000.pid
# 开启aof
appendonly yes
# aof文件路径
appendfilename "appendonly-7005.aof"
# rdb文件路径
dbfilename dump-7000.rdb
# 是否打开aof⽇志功能(appendonly yes)
appendonly no
# aof⽂件的存放路径与⽂件名称
# appendfilename appendonly.aof
#每⼀个命令,都⽴即同步到aof⽂件中去(很安全,但是速度慢,因为每⼀个命令都会进⾏⼀次磁盘操作)
# appendfsync always
#每秒将数据写⼀次到aof⽂件
appendfsync everysec
#将写⼊⼯作交给操作系统,由操作系统来判断缓冲区⼤⼩,统⼀写到aof⽂件(速度快,但是同步频率低,容易
丢数据)
# appendfsync no
# 在RDB持久化数据的时候,此时的aof操作是否停⽌,若为yes则停⽌
# 在停⽌的这段时间内,执⾏的命令会写⼊内存队列,等RDB持久化完成后,统⼀将这些命令写⼊aof⽂件
# 该参数的配置是考虑到RDB持久化执⾏的频率低,但是执⾏的时间⻓,⽽AOF执⾏的频率⾼,执⾏的时间短,
# 若同时执⾏两个⼦进程(RDB⼦进程、AOF⼦进程)效率会低(两个⼦进程都是磁盘读写)
# 但是若改为yes可能造成的后果是,由于RDB持久化执⾏时间⻓,在这段时间内有很多命令写⼊了内存队列,
# 最后导致队列放不下,这样AOF写⼊到AOF⽂件中的命令可能就少了很多
# 在恢复数据的时候,根据aof⽂件恢复就会丢很多数据
# 所以,选择no就好
no-appendfsync-on-rewrite no
# AOF重写:把内存中的数据逆化成命令,然后将这些命令重新写⼊aof⽂件
# 重写的⽬的:假设在我们在内存中对同⼀个key进⾏了100次操作,最后该key的value是100,
# 那么在aof中就会存在100条命令⽇志,这样的话,有两个缺点:
# 1)AOF⽂件过⼤,占据硬盘空间 2)根据AOF⽂件恢复数据极慢(需要执⾏100条命令)
# 如果我们将内存中的该key逆化成"set key 100",然后写⼊aof⽂件,
# 那么aof⽂件的⼤⼩会⼤幅度减少,⽽且根据aof⽂件恢复数据很快(只需要执⾏1条命令)
# 注意:下边两个约束都要满⾜的条件下,才会发⽣aof重写;
# 假设没有第⼆个,那么在aof的前期,只要稍微添加⼀些数据,就发⽣aof重写
# 当aof的增⻓的百分⽐是原来的100%(即是原来⼤⼩的2倍,例如原来是100m,下⼀次重写是当aof⽂件是20
0m的 时候),AOF重写
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb #AOF重写仅发⽣在当aof⽂件⼤于64m时
aof-load-truncated yes #如果AOF⽂件结尾损坏,Redis启动时是否仍载⼊AOF⽂件

18. Redis 中常见问题解答

1. Redis 中的管道有什么用?

一次请求/响应服务器能实现处理新的请求即使旧的请求还未被响应,这样就可以将多个命令发送到服务
器,而不用等待回复,最后在一个步骤中读取该答复。
这就是管道(pipelining),是一种几十年来广泛使用的技术。例如许多 POP3 协议已经实现支持这个
功能,大大加快了从服务器下载新邮件的过程

2. Redis 如何做内存优化?

尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该
尽可能的将你的数据模型抽象到一个散列表里面。
比如你的 web 系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的 key,而
是应该把这个用户的所有信息存储到一张散列表里面。

3. Redis 回收进程如何工作的?

一个客户端运行了新的命令,添加了新的数据。
Redi 检查内存使用情况,如果大于 maxmemory 的限制, 则根据设定好的策略进行回收。

4. Redis 常见的性能问题和解决方案

  1. master 最好不要做持久化工作,如 RDB 内存快照和 AOF 日志文件
  2. 如果数据比较重要,某个 slave 开启 AOF 备份,策略设置成每秒同步一次
  3. 为了主从复制的速度和连接的稳定性,master 和 Slave 最好在一个局域网内
  4. 尽量避免在压力大得主库上增加从库
  5. 主从复制不要采用网状结构,尽量是线性结构,Master<–Slave1<----Slave2 …

5. 假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来?

使用 keys 指令可以扫出指定模式的 key 列表。

5.1 如果这个 redis 正在给线上的业务提供服务,那使用 keys 指令会有什么问题?

redis 的单线程的。keys 指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用 scan 指
令,scan 指令可以无阻塞的提取出指定模式的 key 列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用 keys 指令长。

分片方式 codis

Codis 是一个分布式 Redis 解决方案

Logo

华为云1024程序员节送福利,参与活动赢单人4000元礼包,更有热门技术干货免费学习

更多推荐