能够合理的查看,以及理解修改配置文件,能帮助我们更好的使用 Redis,下面按照 Redis 配置文件的顺序依次往下讲
主从复制会在下面专门讲解,这里暂时略过
接下来的 SECURITY 部分,注释中有提及到关于密码的设置,这里多提一下在客户端中设置密码的方式
127.0.0.1:6379> ping PONG
127.0.0.1:6379> config get requirepass # 获取redis的密码
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass "123456" # 设置redis的密码
OK
127.0.0.1:6379> config get requirepass # 发现所有的命令都没有权限
(error) NOAUTH Authentication required.
127.0.0.1:6379> ping
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth 123456 # 使用密码进行登录
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "123456"
CLIENTS 客户端连接部分,注释太多,这里不好截图,就简单说一说
maxclients —— 最大客户端数量
maxmemory —— 最大内存限制
maxmemory-policy noeviction —— 内存达到限制值的处理策略
redis 中的默认的过期策略是 volatile-lru ,设置方式如下:
config set maxmemory-policy volatile-lru
maxmemory-policy 六种方式
volatile-lru:只对设置了过期时间的key进行LRU(默认值)
allkeys-lru : 删除lru算法的key
volatile-random:随机删除即将过期key
allkeys-random:随机删除volatile-ttl :删除即将过期的
noeviction: 永不过期,返回错误
前面已经讲过,Redis是一个内存数据库,也就是说,我们的数据全部存储在内存中,而我们常见的MySQL和Oracle等SQL数据库会将数据存储到硬盘中,凡事都是有利有弊,虽然内存数据库读写速度要比在硬盘中读写的数据库快的多,但是却出现了一个很麻烦的问题,也就是说,当 Redis 服务器重启或者宕机后,内存中的数据会全部丢失,为了解决这个问题,Redis提供了一种持久化的技术,也就是将内存中的数据存储到硬盘中去,日后方便我们使用这些文件恢复数据库中的数据
在配置文件的解释中,提到了两种持久化方式 RDB、AOF ,下面我们具体来讲解一下:
在指定时间间隔后,将内存中的数据集快照写入数据库 ,在恢复时候,直接读取快照文件,进行数据的恢复
简单理解:一定的时间内,检测key的变化情况,然后持久化数据
默认情况下, Redis 将数据库快照保存在名字为 dump.rdb 的二进制文件中。
文件名可以在配置文件中进行自定义,例如:dbfilename dump.rdb
在进行 RDB
的时候,redis
的主线程是不会做 io
操作的,主线程会 fork
一个子线程来完成该操作(这也是保证了其极大性能的特点)
这种工作方式使得 Redis 可以从写时复制(copy-on-write)机制中获益(因为是使用子进程进行写操作,而父进程依然可以接收来自客户端的请求。)
我们知道了一个进程如何采用请求调页,仅调入包括第一条指令的页面,从而能够很 快开始执行。然而,通过系统调用 fork() 的进程创建最初可以通过使用类似于页面共享的技术,绕过请求调页的需要。这种技术提供了快速的进程创建,并最小化必须分配给新创建进程的新页面的数量。
回想一下,系统调用 fork() 创建了父进程的一个复制,以作为子进程。传统上,fork() 为子进程创建一个父进程地址空间的副本,复制属于父进程的页面。然而,考虑到许多子进程在创建之后立即调用系统调用 exec(),父进程地址空间的复制可能没有必要。
因此,可以采用一种称为写时复制的技术,它通过允许父进程和子进程最初共享相同的页面来工作。这些共享页面标记为写时复制,这意味着如果任何一个进程写入共享页面,那么就创建共享页面的副本。
满足 save 条件会自动触发 rdb 原则
执行save / bgsave / flushall命令,也会触发 rdb 原则
退出 Redis,也会自动产生 rdb 文件(默认生成位置就是 redis 的启动目录)
只要将 rdb 文件,放在 Redis 的启动目录,Redis 会自动在这个目录下检查 dump.rdb 文件,然后恢复其中的数据
查询配置文件中位置的命令
127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin"
优点:
缺点:
以日志的形式来记录每个写的操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
如果你不深究其背后的操作,可以简单理解为:每一个操作执行后,进行持久化操作
想要使用 AOF 方式,需要主动打开,因为默认使用的是 RDB
在配置文件中,我们找到这两行,可以设置 aof 的启动,以及其持久化文件的名字
appendonly no
:no 代表关闭 aof,改为 yes 代表开启
appendfilename "appendonly.aof"
—— 持久化文件的名字
这里可以修改其持久化的一个方式
appendfsync always
—— 每次修改都会 sync(消耗性能 )
appendfsync everysec
—— 每秒执行一次 sync,可能会丢失这1s的数据
appendfsync no
—— 不执行 sync,操作系统自己同步数据,速度最快
其默认是无限追加模式的,如果 aof 文件大于 64m,就 fork一个新的进程来将我们的文件进行重写
no-appendfsync-on-rewrite no
aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
如果这个 aof 文件有错位,这时候redis是启动不起来的
Redis 给我们提供了一个工具 redis-check-aof --fix
# 命令示例
redis-check-aof --fix appendonly.aof
优点
缺点
如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化,即只当做缓存使用
同时开启两种持久化方式
在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF
文件保存的数据集要比RDB文件保存的数据集要完整。
RDB 的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要只使用AOF呢?作者
建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有
AOF可能潜在的Bug,留着作为一个万一的手段。
性能建议
因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够
了,只保留 save 900 1 这条规则。
如果Enable AOF ,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自
己的AOF文件就可以了,代价如下:
一是带来了持续的IO,
二是AOF rewrite 的最后将 rewrite 过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。
因此只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上,默认超过原大小100%大小重写可以改到适当的数值。
如果不Enable AOF ,仅靠 Master-Slave Repllcation 实现高可用性也可以,能省掉一大笔IO,也
减少了rewrite时带来的系统波动。
这部分,用的不是特别多,作为一个补充。 下面是我在 菜鸟教程(runoob)贴过来的定义,重制了一下图
定义:Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。
Redis 客户端可以订阅任意数量的频道。
下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:
当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
PSUBSCRIBE pattern [pattern..]
—— 订阅一个或多个符合给定模式的频道。PUNSUBSCRIBE pattern [pattern..]
—— 退订一个或多个符合给定模式的频道。PUBSUB subcommand [argument[argument]]
—— 查看订阅与发布系统状态。PUBLISH channel message
—— 向指定频道发布消息SUBSCRIBE channel [channel..]
—— 订阅给定的一个或多个频道。SUBSCRIBE channel [channel..]
—— 退订一个或多个频道演示
------------订阅端----------------------
127.0.0.1:6379> SUBSCRIBE ideal-20 # 订阅ideal-20频道
Reading messages... (press Ctrl-C to quit) # 等待接收消息
1) "subscribe" # 订阅成功的消息
2) "ideal-20"
3) (integer) 1
1) "message" # 接收到来自 ideal-20 频道的消息 "hello ideal"
2) "ideal-20"
3) "hello ideal"
1) "message" # 接收到来自 ideal-20 频道的消息 "hello i am ideal-20"
2) "ideal-20"
3) "Hi,i am BWH_Steven"
--------------消息发布端-------------------
127.0.0.1:6379> PUBLISH ideal-20 "hello ideal" # 发布消息到ideal-20频道
(integer) 1
127.0.0.1:6379> PUBLISH ideal-20 "Hi,i am BWH_Steven" # 发布消息
(integer) 1
-----------------查看活跃的频道------------
127.0.0.1:6379> PUBSUB channels
1) "ideal-20"
说明:每个 Redis 服务器进程都维持着一个表示服务器状态的 redis.h/redisServer 结构,而结构的 pubsub_channels 属性是一个字典, 这个字典就用于保存订阅频道的信息
例子示意图:在下图展示的这个 pubsub_channels 示例中, client2 、 client5 和 client1 就订阅了 channel1(频道1) ,其他 channel (频道)同理
有了这个结构上的概念,订阅以及发布的动作就很好理解了:
订阅:当客户端调用 SUBSCRIBE 命令执行订阅频道操作时,程序就会把一个个客户端(client)和要订阅的频道(channel)在 pubsub_channels 中关联起来
发布: 程序首先根据 channel 定位到字典的键(例如找到 channel1), 然后将信息发送给字典值链表中的所有客户端(例如 client2、client5、client1)。
首先,在一个项目中,使用一台 Redis 服务器肯定是有问题的:
一台服务器处理所有请求,压力过大,且容易出现故障,会导致整个相关服务出现问题
一台服务器的内存是有限的,不可能将所有内存用作 Redis 存储(推荐不应该超过 20g)
大部分场景下,大部分都是读的操作,写的操作会相对少一点,所以对读取的要求会大一些
而主从复制就可以将读写分离,下面来一起了解一下
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器
前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower)
数据的复制是单向的!只能由主节点复制到从节点(主节点以写为主、从节点以读为主)
简单理解一下就是:一台服务器作为主机器,其他服务器作为从机器,他们通过命令或配置进行了连接,这样从机就能获取到主机的数据了,从机可以帮主机分担很多读的请求等等
正常的情况,应该是多台不同的服务器,为了演示方便,这里使用几个不同的端口来模拟不同的 Redis 服务器
首先,要使用不同的端口,自然需要多个不同的配置文件了,我们先将原先的配置文件,复制三份(分别代表等会的一台主机和两台从机)
# 一段都是为了告诉大家我的配置文件的目录,即redis 启动目录下面的 myconfig 目录下
[root@centos7 ~]# cd /usr/local/bin
[root@centos7 bin]# ls
appendonly.aof dump.rdb myconfig redis-benchmark redis-check-aof redis-check-rdb redis-cli redis-sentinel redis-server temp-2415.rdb
[root@centos7 bin]# cd myconfig/
[root@centos7 myconfig]# ls
redis.conf
# 复制三份,分别按照等会的端口号起名
[root@centos7 myconfig]# cp redis.conf redis6379.conf
[root@centos7 myconfig]# cp redis.conf redis6380.conf
[root@centos7 myconfig]# cp redis.conf redis6381.conf
# 这样三份就赋值好了
[root@centos7 myconfig]# ls
redis6379.conf redis6380.conf redis6381.conf redis.conf
复制后,就需要分别通过 vim 修改每个配置文件的 port 、daemonize、pid 、 logfile、dbfilename
例如:
port 6380
daemonize yes
pidfile /var/run/redis_6380.pid
logfile "6380.log"
dbfilename dump6380.rdb
在 XShell 中再打开两个窗口,然后分别运行不同端口号的 Redis
在第一个窗口运行 Redis 服务,使用 6379 这个配置文件
[root@centos7 bin]# redis-server myconfig/redis6379.conf
其他两个也是同理,分别启动 6380、6381
查看一下,三个端口的 Redis 都启动了
一主二从,就是代表一台主机,还有两台是从机,而 Redis 默认都是主机,也就是说,我们上面模拟搭建出来的几台 Redis 服务器,现在还都是主机,而且相互之间并不存在什么关系
在客户端中通过 info replication
命令可以查看当前的一个信息
127.0.0.1:6379> info replication
# Replication
role:master # 当前是一个 master 主机
connected_slaves:0
master_replid:bfee90411a4ee99e80ace78ee587fdb7b564b4b4
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
说明:以下演示中主机端口号为 6379,两台从机分别为 6380、6381
配置一主二从,只需要配置从机,使用 SLAVEOF 127.0.0.1 6379
即可
分别在 6380 和 6381 的窗口中执行
然后查询从机自身的信息,例如查询 6380 这台
127.0.0.1:6380> info replication
# Replication
role:slave # 当前身份变成了一台从机
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:364
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:bd7b9c5f3bb1287211b23a3f62e41f24e009b77e
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:364
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:85
repl_backlog_histlen:280
同样在主机中查询,也能看到有两台从机已经连接
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6381,state=online,offset=84,lag=0 # 第一台
slave1:ip=127.0.0.1,port=6380,state=online,offset=84,lag=0 # 第二台
master_replid:bd7b9c5f3bb1287211b23a3f62e41f24e009b77e
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:84
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:84
上面使用命令的方式,需要每一次重启等都需要执行命令,而将其写到配置文件中的时候,就可以每次根据配置自动加载了,首先修改从机配置文件中的 replicaof 后面跟随主机的 ip 和 端口
如果主机的 Redis 设置了密码,别忘了在从机中的 masterauth 中加上主机的密码
从机只能读,不能写,主机可读可写但是多用于写。
主机断电/宕机后,默认角色不变,从机仍是从机,集群只是失去了写操作,等待主机恢复,会重新回到原来的状态
slaveof no one
使其成为主机从机断电/宕机后,若之前使用的是命令的方式称为从机,则启动后无法获取主机,重新配置或者是使用配置文件的方式成为从机,重启后,可以重新获取到主机所有数据
Slave(从机) 启动成功连接到 Master(注解) 后会发送一个 sync(同步命令)
Master 接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行,完毕之后,master将传送整个数据文件到slave,并完成一次完全同步。
全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
增量复制:Master 继续将新的所有收集到的修改命令依次传给slave,完成同步
但是只要是重新连接master,一次完全同步(全量复制)将被自动执行,从机中就能看到所有数据
在前面的主从复制的概念中,我们知道,一旦主服务器宕机,就需要使用手动的方式,将一台从服务器切换为主服务器,这种方式很麻烦,还有一种方式就是哨兵模式,也是一种比较推荐的方式
定义:哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
其作用如下:
单哨兵与多哨兵模式:
单哨兵模式:以独立的进程监控3台 Redis 服务器是否正常运行
多哨兵模式:除了监控Redis 服务器,哨兵之间也会互相监控
Redis 启动目录下的 redis-sentinel 就是我们要启动的哨兵,但是我们需要为其指定配置文件,这样哨兵太知道要监控谁
我在我的 Redis 启动目录 /usr/local/bin/ 下的 myconfig 目录中,创建了一个名为 sentinel.conf 的配置文件
[root@centos7 bin]# vim myconfig/sentinel.conf
里面写入了其核心配置内容,即指定监控我们本地 6379 端口的主机,后面的数字1,代表主机宕机后,会使用投票算法机制选择一台从机作为新的主机
# sentinel monitor 被监控的名称 host port 1
sentinel monitor myredis 127.0.0.1 6379 1
接着我们回到 Redis 启动目录,以刚才那个配置文件启动哨兵
[root@centos7 bin]# redis-sentinel myconfig/sentinel.conf
启动成功如下图:
一旦将主机断开连接,等待一下,哨兵监测到,就会发起投票(这里只有一个哨兵,所以是 100%的),然后切换一台从机成为新的主机,而主机一旦重新上线后,也只能作为新主机的一台从机了
可以根据哨兵这边自动弹出来的日志看到,首先 6379 这台主机断开后,1 个哨兵认为其断开,然后下面的 switch 即选择了新的 6380 作为新的主机,6379 重新上线后,只能作为 6380 的从机了
查看一下 6380 的信息,其果然已经成为了主机
127.0.0.1:6380> info replication
# Replication
role:master # 主机
connected_slaves:2
slave0:ip=127.0.0.1,port=6381,state=online,offset=147896,lag=0
slave1:ip=127.0.0.1,port=6379,state=online,offset=147764,lag=0
master_replid:d32e400babb8bfdabfd8ea1d3fc559f714ef0d5a
master_replid2:bd7b9c5f3bb1287211b23a3f62e41f24e009b77e
master_repl_offset:147896
second_repl_offset:7221
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:85
repl_backlog_histlen:147812
实际上最核心的也就是我们刚才演示中的那一句即下面的:sentinel monitor mymaster 127.0.0.1 6379 1
还有端口修改会用到,其他的可以根据情况设置
其配置文件还是比较复杂的
# Example sentinel.conf
# 哨兵sentinel实例运行的端口 默认26379
port 26379
# 哨兵sentinel的工作目录
dir /tmp
# 哨兵sentinel监控的redis主节点的 ip port
# master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
# sentinel monitor
sentinel monitor mymaster 127.0.0.1 6379 1
# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds
sentinel down-after-milliseconds mymaster 30000
# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,
这个数字越小,完成failover所需的时间就越长,
但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。
可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# sentinel parallel-syncs
sentinel parallel-syncs mymaster 1
# 故障转移的超时时间 failover-timeout 可以用在以下这些方面:
#1. 同一个sentinel对同一个master两次failover之间的间隔时间。
#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
#3.当想要取消一个正在进行的failover所需要的时间。
#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
# 默认三分钟
# sentinel failover-timeout
sentinel failover-timeout mymaster 180000
# SCRIPTS EXECUTION
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,
#这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,
#一个是事件的类型,
#一个是事件的描述。
#如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
#通知脚本
# sentinel notification-script
sentinel notification-script mymaster /var/redis/notify.sh
# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
#
# 目前总是“failover”,
# 是“leader”或者“observer”中的一个。
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
此部分为一个补充知识点,本文重点还是 Redis 的一个基本入门,而下面的这些知识点,更多的是在具体场景中产生的一些问题,而且其每一个内容展开讲都是非常复杂的,所以这里只做一个基本概念的介绍,不做详细说明
用户查询数据,首先在 Redis 缓存中去查,如果没有,也就是缓存没有命中,就会去持久层数据库,例如 MySQL 中去查。
缓存穿透:大量缓存未命中的情况下,大量请求持久层数据库,持久层数据库承载很大的压力,出现问题。
常见解决方案有两种:
① 布隆过滤器:
对所有可能查询的参数以Hash的形式存储,以便快速确定是否存在这个值,在控制层先进行拦截校验,校验不通过直接打回,减轻了存储系统的压力。
② 缓存空对象:
次请求若在缓存和数据库中都没找到,就在缓存中方一个空对象用于处理后续这个请求
不过此方法存在两种问题:
定义:缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞
解决方案:
设置热点数据永不过期
这样就不会出现热点数据过期的情况,但是当 Redis 内存空间满的时候也会清理部分数据,而且此种方案会占用空间,一旦热点数据多了起来,就会占用部分空间。
加互斥锁(分布式锁)
在访问 key 之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期 key 。保证同时刻只有一个线程访问。这样对锁的要求就十分高。
大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。
解决方案:
① redis高可用
② 限流降级
③ 数据预热