架构选择
sentinel
Redis实例的监控管理、通知和实例失效备援服务,是
Redis
集群的管理工具。在一般的分布式中心节点数据库中,
Redis-sentinel
的作用是中心节点的工作,监控各个其他节点的工作情况并且进行故障恢复,来提高集群的高可用性。
redis cluster
cluster 3.0自带的集群,特点在于他的分布式算法不是一致性
hash
,而是
hash
槽的概念,以及自身支持节点设置从节点。具体看官方文档介绍
twemproxy
它类似于一个代理方式,使用方法和普通redis无任何区别,设置好它下属的多个
redis
实例后,使用时在本需要连接
redis
的地方改为连接
twemproxy
,它会以一个代理的身份接收请求并使用一致性
hash
算法,将请求转接到具体
redis
,将结果再返回
twemproxy
。使用方式简便
(
相对
redis
只需修改连接端口
)
,对旧项目扩展的首选问题:
twemproxy
自身单端口实例的压力,使用一致性
hash
后,对
redis
节点数量改变时候的 计算值的改变,数据无法自动移动到新的节点。
codis
基本和twemproxy一致的效果,但它支持在 节点数量改变情况下,旧节点数据可恢复到新
hash
节点
配置优化
Linux 配置优化
vm.overcommit_memory
·
Redis
设置合理的
maxmemory
,保证机器有
20%~30%
的闲置内存。·集中化管理
AOF
重写和
RDB
的
bgsave
。·设置
vm.overcommit_memory=1
,防止极端情况下会造成
fork
失败。
vm.swapniess
OOM(
OutOfMemory
)
killer
机制是指
Linux
操作系统发现可用内存不足时,强制杀死一些用户进程(非内核进程),来保证系统有足够的可用内存进行分配。
如果Linux>3.5,
vm.swapniess=1
,否则
vm.swapniess=0
,从而实现如下两个目标:
·物理内存充足时候,使
Redis
足够快。·物理内存不足时候,避免
Redis
死掉(如果当前
Redis
为高可用,死掉比阻塞更好)。
TransparentHugePages
Linuxkernel在
2.6.38
内核增加了
THP
特性,支持大内存页(
2MB
)分配,默认开启。当开启时可以降低
fork
子进程的速度,但
fork
操作之后,每个内存页从原来
4KB
变为
2MB
,会大幅增加重写期间父进程内存,同时每次写命令引起的复制内存页单位放大了
512
倍,会拖慢写操作的执行时间,导致大量写操作慢查询,例如简单的
incr
命令也会出现在慢查询中。因此
Redis
日志中建议将此特性进行禁用,
OOMkiller
OOMkiller进程会为每个用户进程设置一个权值,这个权值越高,被“下手”的概率就越高,反之概率越低。每个进程的权值存放在
/proc/{progress_id}/oom_score
中,这个值是受
/proc/{progress_id}/oom_adj
的控制,对于
Redis
所在的服务器来说,可以将所有
Redis
的
oom_adj
设置为最低值或者稍小的值,降低被
OOMkiller
杀掉的概率
NTP(
NetworkTimeProtocol
)
NTP(
NetworkTimeProtocol
,网络时间协议)是一种保证不同机器时钟一致性的服务。我们知道像
RedisSentinel
和
RedisCluster
这两种功能需要多个
Redis
节点的类型,可能会涉及多台服务器。虽然
Redis
并没有对多个服务器的时钟有严格要求,但是假如多个
Redis
实例所在的服务器时钟不一致,对于一些异常情况的日志排查是非常困难的,为此我们可以每天定时去同步一次系统时间,从而使得集群中的时间保持统一。
Ulimit
在Linux中,可以通过
ulimit
查看和设置系统当前用户进程的资源数。其中
ulimit-a
命令包含的
openfiles
参数,是单个用户同时打开的最大文件个数:
Redis建议把
openfiles
至少设置成
10032
,因为
maxclients
默认是
10000
,这些是用来处理客户端连接的,除此之外,
Redis
内部会使用最多
32
个文件描述符,所以
TCPbacklog
Redis默认的
tcp-backlog
值为
511
,可以通过修改配置
tcp-backlog
进行调整,如果
Linux
的
tcp-backlog
小于
Redis
设置的
tcp-backlog
Redis 配置
总体配置
tcp-backlog
在高并发的环境下,你需要把这个值调高以避免客户端连接缓慢的问题。
Linux 内核会一声不响的把这个值缩小成
/proc/sys/net/core/somaxconn
对应的值, 所以你要修改这两个值才能达到你的预期。
lua-time-limit
一个Lua脚本最长的执行时间,单位为毫秒,如果为
0
或负数表示无限执行时间,默认为
5000
客户端相关配置
client-output-buffer-limit
一般无需调整
客户端输出缓冲区的配置,对于Redis服务器的输出(也就是命令的返回值)来说,其大小通常是不可控制的。有可能一个简单的命令,能够产生体积庞大的返回数据。另外也有可能因为执行了太多命令,导致产生返回数据的速率超过了往客户端发送的速率,这是也会导致服务器堆积大量消息,从而导致输出缓冲区越来越大,占用过多内存,甚至导致系统崩溃。
timeout
指定在一个 client 空闲多少秒之后关闭连接,建议设置为
0
,不然开发时如果不检测连接有可能抛出
JedisConnectionException
, 并且 提示
Unexpected end of stream
。
maxclients:
客户 端 最大 连接 数
tcp- keepalive:
检测 TCP 连接 活性 的 周期那么
Redis
会 每隔
60
秒 对 它 创建 的
TCP
连接 进行 活性 检测, 防止 大量 死 连接 占用 系统 资源。
内存配置
Maxmemory
Redis 最大内存,一般推荐
Redis
设置内存为最大物理内存的四分之三
maxmemory-policy
内存不够时的淘汰策略,建议设置为volatile-lru
volatile-lru -> 根据
LRU
算法生成的过期时间来删除。
allkeys-lru -> 根据
LRU
算法删除任何
key
。
volatile-random -> 根据过期设置来随机删除
key
。
allkeys->random -> 无差别随机删。
volatile-ttl -> 根据最近过期时间来删除(辅以
TTL
)
noeviction -> 谁也不删,直接在写操作时返回错误。
maxmemory-samples
是说每次进行淘汰的时候 会随机抽取几个key 从里面淘汰最不经常使用的
Aof 相关配置
appendonly
是否开启AOF,默认关闭(
no
)
appendfsync
aof刷写频率
always 只要有新添加的数据就
fsync
everysec 支持延迟
fsync
no 不需要
fsync
appendfsync-on-rewrite
如果该参数设置为no,是最安全的方式,不会丢失数据,但是要忍受阻塞的问题。如果设置为
yes
呢这就相当于将
appendfsync
设置为
no
,这说明并没有执行磁盘操作,只是写入了缓冲区,因此这样并不会造成阻塞
auto-aof-rewrite-percentage
指定Redis重写
aof
文件的条件,默认为
100
,表示与上次
rewrite
的
aof
文件大小相比,当前
aof
文件增长量超过上次
afo
文件大小的
100%
时,就会触发
background rewrite
。若配置为
0
,则会禁用自动
rewriteauto-aof-rewrite-min-size
指定触发rewrite的
aof
文件大小。若
aof
文件小于该值,即使当前文件的增量比例达到
auto-aof-rewrite-percentage
的配置值,也不会触发自动
rewrite
。即这两个配置项同时满足时,才会触发
rewrite
。
aof-rewrite-incremental-fsync
aof rewrite过程中
,
是否采取增量文件同步策略
,
默认为“
yes
”。
rewrite
过程中
,
每
32M
数据进行一次文件同步
,
这样可以减少
aof
大文件写入对磁盘的操作次数
Rdb 配置
Save
设置数据保存到数据文件中的save规则
save 900 1 #900秒时间,至少有一条数据更新,则保存到数据文件中
save 300 10 #300秒时间,至少有
10
条数据更新,则保存到数据文件中
save 60 10000 #60秒时间,至少有
10000
条数据更新,则保存到数据文件中
rdbcompression
指定存储至本地数据库时是否压缩数据,默认是yes,
redis
采用
LZF
压缩,如果为了节省
CPU
时间,可以关闭该选项,但会导致数据库文件扁的巨大
stop-writes-on-bgsave-error
当硬盘因为权限等原因无法写入时,停止写入
rdbchecksum
对rdb文件进行校验
慢查询配置
slowlog-log-slower-than:
单位微妙,指定redis执行命令的最大时间,超过将记录到慢查询日志中
,
不接受负值,如果设置为
0
,每条命令都要记录到慢查询日志中
.
slowlog-max-len:
设置慢查询日志长度,如果慢查询日志已经到最大值,如果有新命令需要记录,就将最老那条记录删除.
latency-monitor-threshold
Redis 延迟监控,在定位问题时可以开启
数据结构优化配置
指定相应数据结构在以下阈值时会使用压缩存储,这个值无需过大,过大虽然可节省内存,但可能导致性能问题
hash-max-zipmap-entries 64
hash-max-zipmap-value 512
list-max-ziplist-entries 512
list-max-ziplist-value 64
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
set-max-intset-entries 512
复制相关配置
slaveof 复制的主节点
ip
repl-ping-slave-period repl-timeout
slave会每隔
repl-ping-slave-period(
默认
10
秒
)ping
一次
master
,如果超过
repl-timeout(
默认
60
秒
)
都没有收到响应,就会认为
Master
挂了。如果
Master
明明没挂但被阻塞住了也会报这个错。可以适当调大
repl-timeout
。
repl-backlog-size
过小,会导致主从节点拉复制失败,因为全量复制的时候,父节点的更新(应用更新,主动过期删除等)会临时存放在backlog中待全量复制完成后增量发到子节点,必须为此保留足够的空间。
repl-backlog-ttl
多久释放backlog,当确认
master
不再需要
slave
的时候,多久释放。
0
是永远不释放
slave-priority
当master不可用
,Sentinel
会根据
slave
的优先级选举一个
master
。最低的优先级的
slave,
当选
master.
而配置成
0,
永远不会被选举。
(
必须≥
0)
。默认是
100
min-slaves-to-write和
min-slaves-max-lag
从服务器的数量少于min-slaves-to-write个,或者三个从服务器的延迟(
lag
)值都大于或等于
min-slaves-max-lag
秒时,主服务器将拒绝执行写命令
slave-serve-stale-data
当一个slave失去和
master
的连接,或者同步正在进行中,
slave
的行为可以有两种表现:
1) 如果
slave-serve-stale-data
设置为
"yes" (
默认值
)
,
slave
会继续响应客户端请求,
可能是正常数据,或者是过时了的数据,也可能是还没获得值的空数据。
2) 如果
slave-serve-stale-data
设置为
"no"
,
slave
会回复
"
正在从
master
同步
(SYNC with master in progress)
"
来处理各种请求,除了
INFO
和
SLAVEOF
命令。
slave-read-only
你可以配置salve实例是否接受写操作。可写的
slave
实例可能对存储临时数据比较有用
repl-disable-tcp-nodelay
是否在slave套接字发送
SYNC
之后禁用
TCP_NODELAY
?
如果你选择“
yes
”
Redis
将使用更少的
TCP
包和带宽来向
slaves
发送数据。但是这将使数据传输到
slave
上有延迟,
Linux
内核的默认配置会达到
40
毫秒
如果你选择了 "no" 数据传输到
salve
的延迟将会减少但要使用更多的带宽
repl-diskless-sync
1、支持
disk
:
master
端将
RDB file
写到
disk
,稍后再传送到
slave
端;
2、无磁盘
diskless
:
master
端直接将
RDB file
传到
slave socket
,不需要与
disk
进行交互。
无磁盘diskless方式适合磁盘读写速度慢但网络带宽非常高的环境。 默认不使用
diskless
同步方式
repl-diskless-sync-delay
无磁盘diskless方式在进行数据传递之前会有一个时间的延迟,以便
slave
端能够进行到待传送的目标队列中,这个时间默认是
5
秒
安全相关配置
Requirepass
配置redis访问密码的参数
Bind 监听指定的网络接口
masterauth
如果master端启用了密码保护(
requirepass
),那
slave
端就需要配置此选项
设计开发运维
内存优化
redisObject
Redis存储的数据都使用
redisObject
来封装,包括
string
、
hash
、
list
、
set
、
zset
在内的所有数据类型。理解
redisObject
对内存优化非常有帮助,
·
type
字段:表示当前对象使用的数据类型,
Redis
主要支持
5
种数据类型:
string
、
hash
、
list
、
set
、
zset
。可以使用
type{key}
命令查看对象所属类型,
type
命令返回的是值对象类型,键都是
string
类型。
·
encoding
字段:表示
Redis
内部编码类型,
encoding
在
Redis
内部使用,代表当前对象内部采用哪种数据结构实现。理解
Redis
内部编码方式对于优化内存非常重要,同一个对象采用不同的编码实现内存占用存在明显差异。
·
lru
字段:记录对象最后一次被访问的时间,当配置了
maxmemory
和
maxmemory-policy=volatile-lru
或者
allkeys-lru
时,用于辅助
LRU
算法删除键数据。可以使用
objectidletime{key}
命令在不更新
lru
字段情况下查看当前键的空闲时间。
·
refcount
字段:记录当前对象被引用的次数,用于通过引用次数回收内存,当
refcount=0
时,可以安全回收当前对象空间。使用
objectrefcount{key}
获取当前对象引用。当对象为整数且范围在
[0-9999]
时,
Redis
可以使用共享对象的方式来节省内存。
·
*ptr
字段:与对象的数据内容相关,如果是整数,直接存储数据;否则表示指向数据的指针。
Redis
在
3.0
之后对值对象是字符串且长度
<=39
字节的数据,内部编码为
embstr
类型,字符串
sds
和
redisObject
一起分配,从而只要一次内存操作即可。
高并发写入场景中,在条件允许的情况下,建议字符串长度控制在39字节以内,减少创建
redisObject
内存分配次数,从而提高性能。
缩减键值对象
降低Redis内存使用最直接的方式就是缩减键(
key
)和值(
value
)的长度。·
key
长度:如在设计键时,在完整描述业务情况下,键值越短越好。如
user
:
{uid}
:
friends
:
notify
:
{fid}
可以简化为
u
:
{uid}
:
fs
:
nt
:
{fid}
。·
value
长度:值对象缩减比较复杂,常见需求是把业务对象序列化成二进制数组放入
Redis
。首先应该在业务上精简业务对象,去掉不必要的属性避免存储无效数据。其次在序列化工具选择上,应该选择更高效的序列化工具来降低字节数组大小。
共享对象池
共享对象池是指Redis内部维护
[0-9999]
的整数对象池。创建大量的整数类型
redisObject
存在内存开销,每个
redisObject
内部结构至少占
16
字节,甚至超过了整数自身空间消耗。所以
Redis
内存维护一个
[0-9999]
的整数对象池,用于节约内存。除了整数值对象,其他类型如
list
、
hash
、
set
、
zset
内部元素也可以使用整数对象池。因此开发中在满足需求的前提下,尽量使用整数对象以节省内存。
字符串优化
尽量减少字符串频繁修改操作如append、
setrange
,改为直接使用
set
修改字符串,降低预分配带来的内存浪费和内存碎片化。不一定把每份数据作为字符串整体存储,像
json
这样的数据可以使用
hash
结构
编码优化
Redis对外提供了
string
、
list
、
hash
、
set
、
zet
等类型,但是
Redis
内部针对不同类型存在编码的概念,所谓编码就是具体使用哪种底层数据结构来实现。编码不同将直接影响数据的内存占用和读写效率。
ziplist编码
ziplist
编码主要目的是为了节约内存,因此所有数据都是采用线性连续的内存结构。
ziplist
编码是应用范围最广的一种,可以分别作为
hash
、
list
、
zset
类型的底层数据结构实现。
.
intset编码是集合(
set
)类型编码的一种,内部表现为存储有序、不重复的整数集。当集合只包含整数且长度不超过
set-max-intset-entries
配置时被启用。使用
intset
编码的集合时,尽量保持整数范围一致,如都在
int-16
范围内。防止个别大整数触发集合升级操作,产生内存浪费。
控制键的数量
防止穿透优化
缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不会命中,通常出于容错的考虑,如果从存储层查不到数据则不写入缓存穿透将导致不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储的意义。缓存空对象,存储层不命中后,仍然将空对象保留到缓存层中,之后再访问这个数据将会从缓存中获取,这样就保护了后端数据源。
无底洞优化
·客户端一次批量操作会涉及多次网络操作,也就意味着批量操作会随着节点的增多,耗时会不断增大。·网络连接数变多,对节点的性能也有一定影响。用一句通俗的话总结就是,更多的节点不代表更高的性能,所谓“无底洞”就是说投入越多不一定产出越多。
就可以将属于同一个节点的key进行归档,得到每个节点的
key
子列表,之后多线程对每个节点执行
mget
或者
Pipeline
操作,它的操作时间
=node
次网络时间
+n
次命令时间,网络次数是
node
的个数
雪崩优化
由于缓存层承载着大量请求,有效地保护了存储层,但是如果缓存层由于某些原因不能提供服务,于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会级联宕机的情况。依赖隔离组件为后端限流并降级。
防阻塞
防止高算法复杂度命令和big key
如对一个包含上万个元素的hash结构执行
hgetall
操作,由于数据量比较大且命令算法复杂度是
O
(
n
),这条命令执行速度必然很慢。这个问题就是典型的不合理使用
API
和数据结构。对于高并发的场景我们应该尽量避免在大对象上执行算法复杂度超过
O
(
n
)的命令,
调整大对象:缩减大对象数据或把大对象拆分为多个小对象,防止一次命令操作过多的数据。
防止CPU饱和
单线程的Redis处理命令时只能使用一个
CPU
。而
CPU
饱和是指
Redis
把单核
CPU
使用率跑到接近
100%
。情况,大量使用高算法复杂的命令将导致这个问题
防止持久化阻塞
Fork 阻塞
fork操作发生在
RDB
和
AOF
重写时,
Redis
主线程调用
fork
操作产生共享内存的子进程,由子进程完成持久化文件重写工作。如果
fork
操作本身耗时过长,必然会导致主线程的阻塞。
AOF刷盘阻塞
当我们开启AOF持久化功能时,文件刷盘的方式一般采用每秒一次,后台线程每秒对
AOF
文件做
fsync
操作。当硬盘压力过大时,
fsync
操作需要等待,直到写入完成
HugePage
HugePage写操作阻塞子进程在执行重写期间利用
Linux
写时复制技术降低内存开销,因此只有写操作时
Redis
才复制要修改的内存页。对于开启
TransparentHugePages
的操作系统,每次写命令引起的复制内存页单位由
4K
变为
2MB
,放大了
512
倍,会拖慢写操作的执行时间,导致大量写操作慢查询
防止进程竞争
Redis是典型的
CPU
密集型应用,不建议和其他多核
CPU
密集型服务部署在一起。当其他进程过度消耗
CPU
时,将严重影响
Redis
吞吐量。可以通过
top
、
sar
等命令定位到
CPU
消耗的时间点和具体进程,这个问题比较容易发现,需要调整服务之间部署结构。
禁用内存交换
内存交换(swap)对于
Redis
来说是非常致命的,
Redis
保证高性能的一个重要前提是所有的数据在内存中。如果操作系统把
Redis
使用的部分内存换出到硬盘,由于内存与硬盘读写速度差几个数量级,会导致发生交换后的
Redis
性能急剧下降。
网络闪断
一般发生在网络割接或者带宽耗尽的情况,对于网络闪断的识别比较困难
Redis连接拒绝
Redis通过
maxclients
参数控制客户端最大连接数,默认
10000
。当
Redis
连接数大于
maxclients
时会拒绝新的连接进入,
infostats
的
rejected_connections
统计指标记录所有被拒绝连接的数量:
Redis
使用多路复用
IO
模型可支撑大量连接,但是不代表可以无限连接。客户端访问
Redis
时尽量采用
NIO
长连接或者连接池的方式。
连接溢出
操作系统一般会对进程使用的资源做限制,其中一项是对进程可打开最大文件数控制,通过ulimit-n查看,通常默认
1024
。由于
Linux
系统对
TCP
连接也定义为一个文件句柄,因此对于支撑大量连接的
Redis
来说需要增大这个值,如设置
ulimit-n65535
,防止
Toomanyopenfiles
错误。
backlog
队列溢出系统对于特定端口的
TCP
连接使用
backlog
队列保存。
Redis默认的长度为
511
,通过
tcp-backlog
参数设置。如果如果
Redis
用于高并发场景为了防止缓慢连接占用,可适当增大这个设置,但必须大于操作系统允许值才能生效。当
Redis
启动时如果
tcp-backlog
设置大于系统允许值将以系统值为准
网络延迟
网络延迟取决于客户端到Redis服务器之间的网络环境。主要包括它们之间的物理拓扑和带宽占用情况。
网卡软中断
网卡软中断是指由于单个网卡队列只能使用一个CPU,高并发下网卡数据交互都集中在同一个
CPU
,导致无法充分利用多核
CPU
的情况。
虚拟机内在延迟
如果您正在使用虚拟机,则可能存在与Redis无关的内部延迟
https://redis.io/topics/latency
规避全量复制
·节点运行
ID
不匹配:当主从复制关系建立后,从节点会保存主节点的运行
ID
,如果此时主节点因故障重启,那么它的运行
ID
会改变,从节点发现主节点运行
ID
不匹配时,会认为自己复制的是一个新的主节点从而进行全量复制。对于这种情况应该从架构上规避,比如提供故障转移功能。当主节点发生故障后,手动提升从节点为主节点或者采用支持自动故障转移的哨兵或集群方案。
·复制积压缓冲区不足:当主从节点网络中断后,从节点再次连上主节点时会发送
psync{offset}{runId}
命令请求部分复制,如果请求的偏移量不在主节点的积压缓冲区内,则无法提供给从节点数据,因此部分复制会退化为全量复制。针对这种情况需要根据网络中断时长,写命令数据量分析出合理的积压缓冲区大小。网络中断一般有闪断、机房割接、网络分区等情况。这时网络中断的时长一般在分钟级(
net_break_time
)。写命令数据量可以统计高峰期主节点每秒
inforeplication
的
master_repl_offset
差值获取(
write_size_per_minute
)。积压缓冲区默认为
1MB
,对于大流量场景显然不够,这时需要增大积压缓冲区,保证
repl_backlog_size>net_break_time*write_size_per_minute
,从而避免因复制积压缓冲区不足造成的全量复制。
规避复制风暴
复制风暴是指大量从节点对同一主节点或者对同一台机器的多个主节点短时间内发起全量复制的过程。复制风暴对发起复制的主节点或者机器造成大量开销,导致CPU、内存、带宽消耗。因此我们应该分析出复制风暴发生的场景,提前采用合理的方式规避。单主节点复制风暴单主节点复制风暴一般发生在主节点挂载多个从节点的场景。当主节点重启恢复后,从节点会发起全量复制流程,这时主节点就会为从节点创建
RDB
快照,如果在快照创建完毕之前,有多个从节点都尝试与主节点进行全量同步,那么其他从节点将共享这份
RDB
快照。这点
Redis
做了优化,有效避免了创建多个快照。但是,同时向多个从节点发送
RDB
快照,可能使主节点的网络带宽消耗严重,造成主节点的延迟变大,极端情况会发生主从节点连接断开,导致复制失败。解决方案首先可以减少主节点(
master
)挂载从节点(
slave
)的数量,或者采用树状复制结构,加入中间层从节点用来保护主节点。