前言
一主二从三哨兵(三个主机)
一台主机两台从机三台哨兵,如果主实例宕机,哨兵将将一台从机升级为主机
ip | 主从 | redis端口 | 哨兵端口 |
---|---|---|---|
10.99.6.101 | 主 | 6379 | 26379 |
10.99.6.102 | 从 | 6379 | 26379 |
10.99.6.103 | 从 | 6379 | 26379 |
安装redis
每个主机都进行如下命令安装redis
# 下载
wget http://download.redis.io/releases/redis-4.0.8.tar.gz
# 解压
tar -zxvf redis-4.0.8.tar.gz
# 进入目录
cd redis-4.0.8
make
cd src
#测试
make test
#指定安装目录
make install PREFIX=/usr/local/redis
如果make test报如下错误,解决步骤如下,没有报错的略过此步骤
cd /opt
# 下载
wget http://downloads.sourceforge.net/tcl/tcl8.6.4-src.tar.gz
# 解压
tar -zxvf tcl8.6.4-src.tar.gz
cd tcl8.6.4/unix/
./configure
make
make install
安装完成后再次依次执行
cd /opt/redis-4.0.8/src
make test
make install PREFIX=/usr/local/redis
配置redis.conf
# 进入到安装目录
cd /usr/local/redis
mkdir conf
# 复制两份配置文件
cp /opt/redis-4.0.8/redis.conf conf/redis.conf
cp /opt/redis-4.0.8/sentinel.conf conf/sentinel.conf
主机10.99.6.101redis配置文件redis.conf需要修改的地方
bind:是绑定本机的IP地址,(准确的是:本机的网卡对应的IP地址,每一个网卡都有一个IP地址),而不是redis允许来自其他计算机的IP地址。
如果指定了bind,则说明只允许来自指定网卡的Redis请求。如果没有指定,就说明可以接受来自任意一个网卡的Redis请求
#注释掉bind
#bind 127.0.0.1
protected-mode no
# 后台启动
daemonize yes
# 指定日志文件,方便排查问题
logfile "redis.log"
dir "/usr/local/redis"
两台从机redis配置文件redis.conf需要修改的地方
#注释掉bind
#bind 127.0.0.1
protected-mode no
# 后台启动
daemonize yes
# 指定日志文件,方便排查问题
logfile "redis.log"
dir "/usr/local/redis"
# 这是与主redis配置文件唯一不同的地方
slaveof 10.99.6.201 6379
配置sentinel.conf
redis配置文件sentinel.conf需要修改的地方
三台机器的sentinel.conf都做如下配置
protected-mode no
daemonize yes
sentinel monitor mymaster 10.99.6.101 6379 2
启动
先到10.99.6.101
cd /usr/local/redis
# 启动redis
./bin/redis-server conf/redis.conf
# 启动哨兵
./bin/redis-sentinel conf/sentinel.conf
再到两台从机器做同样操作
检查
来到主redis
cd /usr/local/redis
./bin/redis-cli
输入info Replication
返回如下图显示角色role为master,connected_slaves有2个从节点
来到从redis
cd /usr/local/redis
./bin/redis-cli
输入info Replication
显示角色role为slave
此时如果我们将master的redis停掉
./bin/redis-cli
shutdown
主节点会转移到另外两台机器中的一台
springboot连接redis哨兵
spring:
redis:
sentinel:
master: mymaster
nodes: 192.168.1.201:26379,192.168.1.202:26380,192.168.1.203:26381 #此处为哨兵的ip:port
database: 2
lettuce:
pool:
max-active: 10 # 连接池最大连接数(使用负值表示没有限制),如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)
max-idle: 8 # 连接池中的最大空闲连接 ,默认值也是8
max-wait: 100 # # 等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException
min-idle: 2 # 连接池中的最小空闲连接 ,默认值也是0
shutdown-timeout: 100ms
使用过程中遇到的一些问题
这个我们在生产环境使用redis_exporter+grafana进行监控,有需要的可看另外一篇文章
1、报警从节点失联,但是各redis以及哨兵的进程都还在
查看redis日志,三台redis都在报
Can’t save in background: fork: Cannot allocate memory()
系统不能在后台保存,fork进程时无法指定内存
清理一下缓存后发现空闲内存还有1.5G
但是主redis仍然无法正常fork
为什么呢?
因为在Linux下有个CommitLimit 用于限制系统应用使用的内存资源
解决方式
# 编辑
vi /etc/sysctl.conf
# 添加
vm.overcommit_memory=1
# 保存后执行使配置生效
sysctl -p
我们使用该命令看一下
grep -i commit /proc/meminfo
CommitLimit是一个内存分配上限
Committed_AS是已经分配的内存大小
虚拟内存算法:
CommitLimit = 物理内存 * overcommit_ratio(/proc/sys/vm/overcmmit_ratio,默认50,即50%) + swap大小
这是由上面修改的内核参数overcommit_ratio的控制的,当我们的应用申请内存的时候,当出现以下情况
应用程序要申请的内存 + 系统已经分配的内存(也就是Committed_AS)> CommitLimit
可以看出我的系统不用加应用程序要申请的内存已经远超,在还有1.5G闲余内存的情况下,这个计算公式超了,所以一直无法fork成功。
vm.overcommit_memory = 0 启发策略 比较
此次请求分配的虚拟内存大小和系统当前空闲的物理内存加上swap,决定是否放行。系统在为应用进程分配虚拟地址空间时,会判断当前申请的虚拟地址空间大小是否超过剩余内存大小,如果超过,则虚拟地址空间分配失败。因此,也就是如果进程本身占用的虚拟地址空间比较大或者剩余内存比较小时,fork、malloc等调用可能会失败。vm.overcommit_memory = 1 允许overcommit
直接放行,系统在为应用进程分配虚拟地址空间时,完全不进行限制,这种情况下,避免了fork可能产生的失败,但由于malloc是先分配虚拟地址空间,而后通过异常陷入内核分配真正的物理内存,在内存不足的情况下,这相当于完全屏蔽了应用进程对系统内存状态的感知,即malloc总是能成功,一旦内存不足,会引起系统OOM杀进程,应用程序对于这种后果是无法预测的。vm.overcommit_memory = 2 禁止overcommit
根据系统内存状态确定了虚拟地址空间的上限,由于很多情况下,进程的虚拟地址空间占用远大于其实际占用的物理内存,这样一旦内存使用量上去以后,对于一些动态产生的进程(需要复制父进程地址空间)则很容易创建失败,如果业务过程没有过多的这种动态申请内存或者创建子进程,则影响不大,否则会产生比较大的影响
。这种情况下系统所能分配的内存不会超过上面提到的CommitLimit大小,如果这么多资源已经用光,那么后面任何尝试申请内存的行为都会返回错误,这通常意味着此时没法运行任何新程序。
使用info Memory看到当前Redis内存使用情况
查到的一些资料
属性名 | 属性说明 |
---|---|
used_memory | Redis内部存储的所有数据占用的内存 |
used_memory_human | used_memory带单位 |
used_memory_rss | 操作系统为Redis进程分配的物理内存总量 |
used_memory_peak | 内存使用过的最大值 |
maxmemory | 给Redis分配的内存上限 |
total_system_memory | 系统内存 |
used_memory_lua | Lua脚本占用内存大小 |
used_memory 表示Redis内部存储的所有数据占用的内存,也就是对象内存
used_memory_rss 表示操作系统为Redis进程分配的物理内存总量,也就是上面四种内存的总和
当used_memory < used_memory_rss时,说明used_memory_rss中多余出来的内存没有被用来存储对象。如果两个值的差值很大,说明碎片率很高。
当used_memory > used_memory_rss时,说明操作系统采用了内存交换,把Redis内存交换(swap)到硬盘。内存交换(swap)对于Redis来说是致命的,Redis能保证高性能的一个重要前提就是读写都基于内存。如果操作系统把Redis使用的部分内存交换到硬盘,由于硬盘的读写效率比内存低上百倍,会导致Redis性能急剧下降,特别容易引起Redis阻塞。
Redis的数据回写机制分同步和异步两种
同步回写即SAVE命令,主进程直接向磁盘回写数据。在数据大的情况下会导致系统假死很长时间,所以一般不是推荐的。
异步回写即BGSAVE命令,主进程fork后,复制自身并通过这个新的进程回写磁盘,回写结束后新进程自行关闭。由于这样做不需要主进程阻塞,系统不会假死,一般默认会采用这个方法。
所以如果我们要将数据刷到硬盘上,这时redis分配内存不能太大,否则很容易发生内存不够用无法fork的问题
这个时候maxMemeroy的分配和写磁盘的策略都要考虑到
极限情况:留出一倍内存。比如你的redis数据占用了8G内存,那么你还需要再预留8G空闲内存。也就是内存需求是16G。内存占用率低于50%是最安全的。
普通情况:正常情况下,在序列化周期内,不会更改所有数据,只会有部分数据更改,那么,预留出可能产生的更改部分的空间,就行。如果实在要说一个数据的话,内存占用率低于75%都是安全的。
多留一倍内存是最安全的。
重写AOF文件和RDB文件的进程(即使不做持久化,复制到Slave的时候也要写RDB)会fork出一条新进程来,采用了操作系统的Copy-On-Write策略(如果父进程的内存没被修改,子进程与父进程共享Page。如果父进程的Page被修改,
会复制一份改动前的内容给新进程),留意Console打出来的报告,如”RDB: 1215 MB of memory used by
copy-on-write”。 在系统极度繁忙时,如果父进程的所有Page在子进程写RDB过程中都被修改过了,就需要两倍内存。
按照Redis启动时的提醒,设置 vm.overcommit_memory = 1
,使得fork()一条10G的进程时,因为COW策略而不一定需要有10G的free memory.
当最大内存到达时,按照配置的Policy进行处理, 默认policy为volatile-lru, 对设置了expire
time的key进行LRU清除(不是按实际expire time)。如果沒有数据设置了expire
time或者policy为noeviction,则直接报错,但此时系统仍支持get之类的读操作。
另外还有几种policy,比如volatile-ttl按最接近expire time的,allkeys-lru对所有key都做LRU。