基础数据结构
复杂的数据结构
位图(bitmaps)
- Bitmap 在 Redis 中不是一种实际的数据类型,而是一种将 String 作为 Bitmap 使用的方法。可以理解为将 String 转换为 bit 数组。使用 Bitmap 来存储 true/false 类型的简单数据极为节省空间。
算法数据结构(hyperloglogs)
- HyperLogLogs 是一种主要用于数量统计的数据结构,它和 Set 类似,维护一个不可重复的 String 集合,但是 HyperLogLogs 并不维护具体的 member 内容,只维护 member 的个数。也就是说,HyperLogLogs 只能用于计算一个集合中不重复的元素数量,所以它比 Set 要节省很多内存空间
非分布式场景下Redis应用的备份与容灾
方案一
- 一个Master节点,两个Slave节点。客户端写数据的时候是写Master节点,读的时候,是读取两个Slave,这样实现读的扩展,减轻了Master节点读负载。
方案二
- Master和Slave1使用keepalived进行VIP转移。Client连接Master的时候是通过VIP进行连接的。避免了方案一IP更改的情况。
Redis Sentinel架构
- Sentinel集群对自身和Redis主从复制进行监控。当发现Master节点出现故障时,会经过如下步骤:
- Sentinel之间进行选举,选举出一个leader,由选举出的leader进行failover
- Sentinel leader选取slave节点中的一个slave作为新的Master节点。
- Sentinel集群对自身和Redis主从复制进行监控。当发现Master节点出现故障时,会经过如下步骤:
客户端工具
性能测试工具
使用默认参数测试
redis-benchmark
自定义参数测试
redis-benchmark -n 1000000 --csv
雪球 rdr:
https://github.com/xueqiu/rdr
redis-rdb-tools:
https://github.com/sripathikrishnan/redis-rdb-tools
工具命令
检查修复本地数据文件工具
redis-check-dump dump.rdb
检查修复AOF日志文件工具
redis-check-aof appendonly.aof
基础命令
keys
- 列出Redis所有的key
del
- 删除一个或多个key,多个key之间用空格分隔,其返回值为整数,表示成功删除了多少个存在的key,因此,如果只删除一个key,则可以从返回值中判断是否成功,如果删除多个key,则只能得到删除成功的数量
exists
- exists命令用于判断一个或多个key是否存在,判断多个key时,key之间用空格分隔,exists的返回值为整数,表示当前判断有多少个key是存在的。
expire/pexpire
- expire设置key在多少秒之后过期,pexpire设置key在多少毫秒之后过期,成功返回1,失败返回0。
ttl/pttl
ttl和pttl命令用于获取key的过期时间,其返回值为整型
当key不存在或过期时间,返回-2。
当key存在且永久有效时,返回-1。
当key有设置过期时间时,返回为剩下的秒数(pttl为毫秒数)
expireat/pexpireat
- 设置key在某个时间戳过期,expreat参数时间戳用秒表示,而pexpireat则用毫秒表示,与expire和pexpire功能类似,返回1表示成功,0表示失败。
persist
- 移除key的过期时间,将key设置为永久有效,当key设置了过期时间,使用persist命令移除后返回1,如果key不存在或本身就是永久有效的,则返回0
type
- 判断key是什么类型的数据结构,返回值为string,list,set,hash,zset,分别表示我们前面介绍的Redis的5种基础数据结构。
geo,hyperloglog,bitmaps等复杂的数据结构,都是在这五种基础数据结构上实现,比如geo是zset类型,hyperloglog和bitmaps都为string。
auth
- Redis认证命令,执行其他命令前,必须先进行认证
ping
- 测试客户端和服务器之间的联通,返回值为PONG,表示联通
config get *
- 获取所有配置参数
config set config_name config_value
- 设置配置参数值
info
- 返回服务器信息
select
- 切换数据库,redis默认的数据库是0-15,共16个数据库
move
- 将当前库的键移动到其他数据库
dbsize
- 获取当前库中所有键的数量
flushdb
- 删除当前库中的所有key
flushall
- 删除所有库中的所有key
save
- 创建当前库的备份
bgsave
- 同save,但是是后台备份,不阻塞主进程
eval
- 执行lua脚本
string
set
- 为一个 key 设置 value,可以配合 EX/PX 参数指定 key 的有效期
get
- 获取某个 key 对应的 value
getset
- 为一个 key 设置 value,并返回该 key 的原 value
incr/decr
- 自增/自减(前提是键值是整型)
incrby/decrby
- 指定步长增加减少(q前提是键值是整型)
mset
- 为多个 key 设置 value
msetnx
- 同 MSET,如果指定的 key 中有任意一个已存在,则不进行任何操作
mget
- 获取多个 key 对应的 value
strlen
- 获取键的长度
append
- 向指定键追加值,返回字符串长度
setnx
- 判断键是否存在,存在返回0,否则返回1,不会覆盖原来值
getrange
- 根据指定下标获取键的值
list
lpush
- 向指定 List 的左侧(即头部)插入 1 个或多个元素,返回插入后的 List 长度
rpush
- 同 lpush,向指定 List 的右侧(即尾部)插入 1 或多个元素
lpushx/rpushx
- 与 lpush/rpush类似,区别在于,lpushx/rpushx操作的 key 如果不存在,则不会进行任何操作
lrange
- 返回指定 List 中指定范围的元素(双端包含,即 lrange key 0 10 会返回 11 个元素),时间复杂度 O(N)。应尽可能控制一次获取的元素数量,一次获取过大范围的 List 元素会导致延迟,同时对长度不可预知的 List,避免使用 lrange key 0 -1 这样的完整遍历操作
lindex
- 返回指定 List 指定 index 上的元素,如果 index 越界,返回 nil。index 数值是回环的,即 - 1 代表 List 最后一个位置,-2 代表 List 倒数第二个位置。
linsert
- 向指定 List 中指定元素之前 / 之后插入一个新元素,并返回操作后的 List 长度。如果指定的元素不存在,返回 - 1。如果指定 key 不存在,不会进行任何操作
lset
将指定 List 指定 index 上的元素设置为 value
- 如果 index 越界则返回错误,时间复杂度 O(N),
- 如果操作的是头 / 尾部的元素,则时间复杂度为 O(1)
lpop
- 从指定 List 的左侧(即头部)移除一个元素并返回
rpop
- 同 lpop,从指定 List 的右侧(即尾部)移除 1 个元素并返回
llen
- 返回指定 List 的长度
hash
hset
- 将 key 对应的 Hash 中的 field 设置为 value。如果该 Hash 不存在,会自动创建一个。
hget
- 返回指定 Hash 中 field 字段的值
hsetnx
- 同 HSET,但如 field 已经存在,HSETNX 不会进行任何操作
hexists
- 判断指定 Hash 中 field 是否存在,存在返回 1,不存在返回 0
hincrby
- 同 incrby命令,对指定 Hash 中的一个 field 进行 incrby
hmset/hmget
- 同 HSET 和 HGET,可以批量操作同一个 key 下的多个 field
hdel
- 删除指定 Hash 中的 field(1 个或多个)
hgetall
- 返回指定 Hash 中所有的 field-value 对。返回结果为数组,数组中 field 和 value 交替出现
hkeys/hvals
- 返回指定 Hash 中所有的 field/value
hlen
- 返回指定hash 表中field中的数量
set
scard
- 返回指定 Set 中的 member 个数
sismember
- 判断指定的 value 是否存在于指定 Set 中
smove
- 将指定 member 从一个 Set 移至另一个 Set
sadd
- 向指定 Set 中添加 1 个或多个 member,如果指定 Set 不存在,会自动创建一个。
srem
- 从指定 Set 中移除 1 个或多个 member
srandmember
- 从指定 Set 中随机返回 1 个或多个 member
spop
- 从指定 Set 中随机移除并返回 count 个 member
smembers
- 返回指定 Hash 中所有的 member
sunion/sunionstore
- 计算多个 Set 的并集并返回 / 存储至另一个 Set 中
sinter/sinterstore
- 计算多个 Set 的交集并返回 / 存储至另一个 Set 中
sdiff/sinterstore
- 计算 1 个 Set 与 1 或多个 Set 的差集并返回 / 存储至另一个 Set 中
zset
- zadd
- zrem
- zcard
- zcount
- zscore
- zrank/zrevrank
- zincrby
- zrange/zrevrange
- zrangebyscore/zrevragebyscore
- zremrangebyrank/zremrangebyscore
事物
multi
- 开启一个事务
exec
- 执行事务
discard
- 撤销事务
watch
- 监视数据库键,若发生改变,返回空
复制
info replication
- 获取复制信息
slaveof
- 建立复制关系
sync
- 同步
订阅发布
subscribe
- 订阅一个或多个频道
publish
- 向某一频道发送信息
性能调优
避免存储 bigkey
使用 pipelining 将连续执行的命令组合执行
操作系统的 Transparent huge pages 功能必须关闭
echo never > /sys/kernel/mm/transparent_hugepage/enabled
使用物理机部署 Redis
- Redis 在做数据持久化时,采用创建子进程的方式进行。
而创建子进程会调用操作系统的 fork 系统调用,这个系统调用的执行耗时,与系统环境有关。
虚拟机环境执行 fork 的耗时,要比物理机慢得多,所以你的 Redis 应该尽可能部署在物理机上
- Redis 在做数据持久化时,采用创建子进程的方式进行。
检查数据持久化策略
考虑引入读写分离机制
开启 lazy-free 机制
- 如果你无法避免存储 bigkey,那么我建议你开启 Redis 的 lazy-free 机制。(4.0+版本支持)
当开启这个机制后,Redis 在删除一个 bigkey 时,释放内存的耗时操作,将会放到后台线程中去执行,这样可以在最大程度上,避免对主线程的影响
- 如果你无法避免存储 bigkey,那么我建议你开启 Redis 的 lazy-free 机制。(4.0+版本支持)
不使用复杂度过高的命令
- 避免执行例如 SORT、SINTER、SINTERSTORE、ZUNIONSTORE、ZINTERSTORE 等聚合类命令。
对于这种聚合类操作,我建议你把它放到客户端来执行,不要让 Redis 承担太多的计算工作
- 避免执行例如 SORT、SINTER、SINTERSTORE、ZUNIONSTORE、ZINTERSTORE 等聚合类命令。
执行 O(N) 命令时,关注 N 的大
查询数据时,遵循原则
- 先查询数据元素的数量(LLEN/HLEN/SCARD/ZCARD)
- 元素数量较少,可一次性查询全量数据
- 元素数量非常多,分批查询数据(LRANGE/HASCAN/SSCAN/ZSCAN)
关注 DEL 时间复杂度
删除一个 key,其元素数量越多,执行 DEL 也就越慢
- List类型:执行多次 LPOP/RPOP,直到所有元素都删除完成
- Hash/Set/ZSet类型:先执行 HSCAN/SSCAN/SCAN 查询元素,再执行 HDEL/SREM/ZREM 依次删除每个元素
批量命令代替单个命令
批量操作相比于多次单个操作的优势在于,可以显著减少客户端、服务端的来回网络 IO 次数
String / Hash 使用 MGET/MSET 替代 GET/SET,HMGET/HMSET 替代 HGET/HSET
- 其它数据类型使用 Pipeline,打包一次性发送多个命令到服务端执行
避免集中过期 key
如果你的业务存在大量 key 集中过期的情况,那么 Redis 在清理过期 key 时,也会有阻塞主线程的风险
在设置过期时间时,增加一个随机时间,把这些 key 的过期时间打散,从而降低集中过期对主线程的影响
使用长连接操作 Redis,合理配置连接池
- 你的业务应该使用长连接操作 Redis,避免短连接
- 当使用短连接操作 Redis 时,每次都需要经过 TCP 三次握手、四次挥手,这个过程也会增加操作耗时
- 同时,你的客户端应该使用连接池的方式访问 Redis,并设置合理的参数,长时间不操作 Redis 时,需及时释放连接资源
只使用 db0
- 在一个连接上操作多个 db 数据时,每次都需要先执行 SELECT,这会给 Redis 带来额外的压力
- 使用多个 db 的目的是,按不同业务线存储数据,那为何不拆分多个实例存储呢?拆分多个实例部署,多个业务线不会互相影响,还能提高 Redis 的访问性能
- Redis Cluster 只支持 db0,如果后期你想要迁移到 Redis Cluster,迁移成本高
使用读写分离 + 分片集群
- 如果你的业务读请求量很大,那么可以采用部署多个从库的方式,实现读写分离,让 Redis 的从库分担读压力,进而提升性能
- 如果你的业务写请求量很大,单个 Redis 实例已无法支撑这么大的写流量,那么此时你需要使用分片集群,分担写压力
不开启 AOF 或 AOF 配置为每秒刷盘
- 如果对于丢失数据不敏感的业务,我建议你不开启 AOF,避免 AOF 写磁盘拖慢 Redis 的性能
- 如果确实需要开启 AOF,那么我建议你配置为 appendfsync everysec,把数据持久化的刷盘操作,放到后台线程中去执行,尽量降低 Redis 写磁盘对性能的影响
长耗时命令
避免在使用这些 O(N) 命令
- 不要把 List 当做列表使用,仅当做队列来使用
- 通过机制严格控制 Hash、Set、Sorted Set 的大小
- 可能的话,将排序、并集、交集等操作放在客户端执行
- 绝对禁止使用 keys 命令
- 避免一次性遍历集合类型的所有成员,而应使用 scan 类的命令进行分批的,游标式的遍历
Slow Log 功能,自动记录耗时较长的命令
- slowlog-log-slower-than xxxms #执行时间慢于xxx毫秒的命令计入 。 Slow Logslowlog-max-len xxx #Slow Log的长度,即最大纪录多少条Slow Log
- 使用 slowlog get [number] 命令,可以输出最近进入 Slow Log 的 number 条命令。
使用 slowlog reset 命令,可以重置 Slow Log
网络引发的延迟
- 尽可能使用长连接或连接池,避免频繁创建销毁连接
- 客户端进行的批量数据操作,应使用 Pipeline 特性在一次交互中完成。
数据持久化引发的延迟
要根据数据的安全级别和性能要求制定合理的持久化策略
- AOF + fsync always 的设置虽然能够绝对确保数据安全,但每个操作都会触发一次 fsync,会对 Redis 的性能有比较明显的影响
- AOF + fsync every second 是比较好的折中方案,每秒 fsync 一次
- AOF + fsync never 会提供 AOF 持久化方案下的最优性能
使用 RDB 持久化通常会提供比使用 AOF 更高的性能,但需要注意 RDB 的策略配置 - 每一次 RDB 快照和 AOF Rewrite 都需要 Redis 主进程进行 fork 操作。fork 操作本身可能会产生较高的耗时,与 CPU 和 Redis 占用的内存大小有关。根据具体的情况合理配置 RDB 快照和 AOF Rewrite 时机,避免过于频繁的 fork 带来的延迟
Swap 引发的延迟
当 Linux 将 Redis 所用的内存分页移至 swap 空间时,将会阻塞 Redis 进程,导致 Redis 出现不正常的延迟。Swap 通常在物理内存不足或一些进程在进行大量 I/O 操作时发生,应尽可能避免上述两种情况的出现。
/proc//smaps 文件中会保存进程的 swap 记录,通过查看这个文件,能够判断 Redis 的延迟是否由 Swap 产生。如果这个文件中记录了较大的 Swap size,则说明延迟很有可能是 Swap 造成的。数据淘汰引发的延迟
当同一秒内有大量 key 过期时,也会引发 Redis 的延迟。在使用时应尽量将 key 的失效时间错开。
主从复制与集群分片
主从复制
Redis 支持一主多从的主从复制架构。一个 Master 实例负责处理所有的写请求,Master 将写操作同步至所有 Slave。
借助 Redis 的主从复制,可以实现读写分离和高可用
- 实时性要求不是特别高的读请求,可以在 Slave 上完成,提升效率。特别是一些周期性执行的统计任务,这些任务可能需要执行一些长耗时的 Redis 命令,可以专门规划出 1 个或几个 Slave 用于服务这些统计任务
- 借助 Redis Sentinel 可以实现高可用,当 Master crash 后,Redis Sentinel 能够自动将一个 Slave 晋升为 Master,继续提供服务
Sentinel 做自动 failover
Redis 的主从复制功能本身只是做数据同步,并不提供监控和自动 failover 能力,要通过主从复制功能来实现 Redis 的高可用,还需要引入一个组件:Redis Sentinel
Redis Sentinel 是 Redis 官方开发的监控组件,可以监控 Redis 实例的状态,通过 Master 节点自动发现 Slave 节点,并在监测到 Master 节点失效时选举出一个新的 Master,并向所有 Redis 实例推送新的主从配置- sentinel monitor mymaster 127.0.0.1 6379 2 #Master实例的IP、端口,以及选举需要的赞成票数
- sentinel down-after-milliseconds mymaster 60000 #多长时间没有响应视为Master失效
- sentinel failover-timeout mymaster 180000 #两次failover尝试间的间隔时长
- sentinel parallel-syncs mymaster 1 #如果有多个Slave,可以通过此配置指定同时从新Master进行数据同步的Slave数,避免所有Slave同时进行数据同步导致查询服务也不可用
集群分片
- Redis 中存储的数据量大,一台主机的物理内存已经无法容纳
- Redis 的写请求并发量大,一个 Redis 实例以无法承载
缺点
缓存和数据库双写一致性问题
- 降低不一致发生的概率,无法完全避免
- 只能保证最终一致性
首先,采取正确更新策略,先更新数据库,再删缓存。其次,因为可能存在删除缓存失败的问题,提供一个补偿措施即可,例如利用消息队列。
缓存穿透
- 缓存穿透,即黑客故意去请求缓存中不存在的数据,导致所有的请求都怼到数据库上,从而数据库连接异常
缓存击穿问题
- 在第一个查询数据的请求上使用一个互斥锁来锁住它。其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。后面的线程进来发现已经有缓存了,就直接走缓存。
优点
纯内存操作
- Redis将所有数据放在内存中,非数据同步正常工作中,是不需要从磁盘读取数据的,0次IO。内存响应时间大约为100纳秒
单线程操作,避免了频繁的上下文切换
- 第一,单线程简化算法的实现,并发的数据结构实现不但困难且测试也麻烦。第二,单线程避免了线程切换以及加锁释放锁带来的消耗,对于服务端开发来说,锁和线程切换通常是性能杀手。当然了,单线程也会有它的缺点,也是Redis的噩梦:阻塞。如果执行一个命令过长,那么会造成其他命令的阻塞,对于Redis是十分致命的,所以Redis是面向快速执行场景的数据库。
采用了非阻塞I/O多路复用机制
- 当使用read或者write对某一文件描述符(File Descriptor FD)进行读写的时候,如果数据没有收到,那么该线程会被挂起,直到收到数据
I/O多路复用实际上是指多个连接的管理可以在同一进程。多路是指网络连接,复用只是同一个线程
- Redis使用epoll作为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll的read、write、close等都转换成事件,不在网络I/O上浪费过多的时间。实现对多个FD读写的监控,提高性能。
策略以及内存淘汰机制
删除机制
定期删除
- 用一个定时器来负责监视key,过期则自动删除。虽然内存及时释放,但是十分消耗CPU资源。在大并发请求下,CPU要将时间应用在处理请求,而不是删除key
惰性删除策略
- 所谓惰性策略就是在客户端访问这个 key 的时候,redis 对 key 的过期时间进行检查,如果过期了就立即删除,不会给你返回任何东西。
内存淘汰策略
noeviction
- 当内存不足以容纳新写入数据时,新写入操作会报错
allkeys-lru
- 当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key
allkeys-random
- 当内存不足以容纳新写入数据时,在键空间中,随机移除某个key
volatile-lru
- 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。不推荐
volatile-random
- 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。不推荐
volatile-ttl
- 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。不推荐
持久化策略
快照(RDB)
- 快照是内存数据的二进制序列化形式,在存储上非常紧凑
- RDB是通过Redis主进程fork子进程,让子进程执行磁盘 IO 操作来进行 RDB 持久化。RDB记录的是数据
日志追加(AOF)
- AOF 日志是连续的增量备份,在长期的运行过程中会变的无比庞大,数据库重启时需要加载 AOF 日志进行指令重放,这个时间就会无比漫长
- AOF 日志存储的是 Redis 服务器的顺序指令序列,AOF 日志只记录对内存进行修改的指令记录。AOF记录的是指令
节省内存
把 Redis 当作缓存使用
实例设置 maxmemory + 淘汰策略
- volatile-lru / allkeys-lru:优先保留最近访问过的数据
- volatile-lfu / allkeys-lfu:优先保留访问次数最频繁的数据(4.0+版本支持)
- volatile-ttl :优先淘汰即将过期的数据
- volatile-random / allkeys-random:随机淘汰数据
可靠性
按业务线部署实例
- 提升可靠性的第一步,就是「资源隔离」。
你最好按不同的业务线来部署 Redis 实例,这样当其中一个实例发生故障时,不会影响到其它业务。
这种资源隔离的方案,实施成本是最低的,但成效却是非常大的
- 提升可靠性的第一步,就是「资源隔离」。
部署主从集群
- 如果你只使用单机版 Redis,那么就会存在机器宕机服务不可用的风险。
所以,你需要部署「多副本」实例,即主从集群,这样当主库宕机后,依旧有从库可以使用,避免了数据丢失的风险,也降低了服务不可用的时间。
在部署主从集群时,你还需要注意,主从库需要分布在不同机器上,避免交叉部署。
这么做的原因在于,通常情况下,Redis 的主库会承担所有的读写流量,所以我们一定要优先保证主库的稳定性,即使从库机器异常,也不要对主库造成影响。
而且,有时我们需要对 Redis 做日常维护,例如数据定时备份等操作,这时你就可以只在从库上进行,这只会消耗从库机器的资源,也避免了对主库的影响
- 如果你只使用单机版 Redis,那么就会存在机器宕机服务不可用的风险。
合理配置主从复制参数
不合理
- 主从复制中断
- 从库发起全量复制,主库性能受到影响
合理
- 设置合理的 repl-backlog 参数:过小的 repl-backlog 在写流量比较大的场景下,主从复制中断会引发全量复制数据的风险
- 设置合理的 slave client-output-buffer-limit:当从库复制发生问题时,过小的 buffer 会导致从库缓冲区溢出,从而导致复制中断
部署哨兵集群,实现故障自动切换
- 只部署了主从节点,但故障发生时是无法自动切换的,所以,你还需要部署哨兵集群,实现故障的「自动切换」。
而且,多个哨兵节点需要分布在不同机器上,实例为奇数个,防止哨兵选举失败,影响切换时间
- 只部署了主从节点,但故障发生时是无法自动切换的,所以,你还需要部署哨兵集群,实现故障的「自动切换」。
日常运维
禁止使用 KEYS/FLUSHALL/FLUSHDB 命令
执行这些命令,会长时间阻塞 Redis 主线程,危害极大
- SCAN 替换 KEYS
- 4.0+版本可使用 FLUSHALL/FLUSHDB ASYNC,清空数据的操作放在后台线程执行
扫描线上实例时,设置休眠时间
- 不管你是使用 SCAN 扫描线上实例,还是对实例做 bigkey 统计分析,我建议你在扫描时一定记得设置休眠时间。
防止在扫描过程中,实例 OPS 过高对 Redis 产生性能抖动
- 不管你是使用 SCAN 扫描线上实例,还是对实例做 bigkey 统计分析,我建议你在扫描时一定记得设置休眠时间。
慎用 MONITOR 命令
- 有时在排查 Redis 问题时,你会使用 MONITOR 查看 Redis 正在执行的命令。
但如果你的 Redis OPS 比较高,那么在执行 MONITOR 会导致 Redis 输出缓冲区的内存持续增长,这会严重消耗 Redis 的内存资源,甚至会导致实例内存超过 maxmemory,引发数据淘汰,这种情况你需要格外注意
- 有时在排查 Redis 问题时,你会使用 MONITOR 查看 Redis 正在执行的命令。
从库必须设置为 slave-read-only
- 你的从库必须设置为 slave-read-only 状态,避免从库写入数据,导致主从数据不一致。
除此之外,从库如果是非 read-only 状态,如果你使用的是 4.0 以下的 Redis,它存在这样的 Bug:
从库写入了有过期时间的数据,不会做定时清理和释放内存。
这会造成从库的内存泄露!这个问题直到 4.0 版本才修复,你在配置从库时需要格外注意
- 你的从库必须设置为 slave-read-only 状态,避免从库写入数据,导致主从数据不一致。
合理配置 timeout 和 tcp-keepalive 参数
- 如果因为网络原因,导致你的大量客户端连接与 Redis 意外中断,恰好你的 Redis 配置的 maxclients 参数比较小,此时有可能导致客户端无法与服务端建立新的连接(服务端认为超过了 maxclients)。
造成这个问题原因在于,客户端与服务端每建立一个连接,Redis 都会给这个客户端分配了一个 client fd。
当客户端与服务端网络发生问题时,服务端并不会立即释放这个 client fd。
什么时候释放呢?
Redis 内部有一个定时任务,会定时检测所有 client 的空闲时间是否超过配置的 timeout 值。
如果 Redis 没有开启 tcp-keepalive 的话,服务端直到配置的 timeout 时间后,才会清理释放这个 client fd。在没有清理之前,如果还有大量新连接进来,就有可能导致 Redis 服务端内部持有的 client fd 超过了 maxclients,这时新连接就会被拒绝。
针对这种情况,我给你的优化建议是:
不要配置过高的 timeout:让服务端尽快把无效的 client fd 清理掉
Redis 开启 tcp-keepalive:这样服务端会定时给客户端发送 TCP 心跳包,检测连接连通性,当网络异常时,可以尽快清理僵尸 client fd- 如果因为网络原因,导致你的大量客户端连接与 Redis 意外中断,恰好你的 Redis 配置的 maxclients 参数比较小,此时有可能导致客户端无法与服务端建立新的连接(服务端认为超过了 maxclients)。
调整 maxmemory 时,注意主从库的调整顺序
- 从库内存如果超过了 maxmemory,也会触发数据淘汰。
在某些场景下,从库是可能优先主库达到 maxmemory 的(例如在从库执行 MONITOR 命令,输出缓冲区占用大量内存),那么此时从库开始淘汰数据,主从库就会产生不一致。
要想避免此问题,在调整 maxmemory 时,一定要注意主从库的修改顺序:
调大 maxmemory:先修改从库,再修改主库
调小 maxmemory:先修改主库,再修改从库
直到 Redis 5.0,Redis 才增加了一个配置 replica-ignore-maxmemory,默认从库超过 maxmemory 不会淘汰数据,才解决了此问题
- 从库内存如果超过了 maxmemory,也会触发数据淘汰。
预防 Redis 问题
合理的资源规划
- 保证机器有足够的 CPU、内存、带宽、磁盘资源
- 提前做好容量规划,主库机器预留一半内存资源,防止主从机器网络故障,引发大面积全量同步,导致主库机器内存不足的问题
- 单个实例内存建议控制在 10G 以下,大实例在主从全量同步、RDB 备份时有阻塞风险
完善的监控预警
- 做好机器 CPU、内存、带宽、磁盘监控,资源不足时及时报警,任意资源不足都会影响 Redis 性能
- 设置合理的 slowlog 阈值,并对其进行监控,slowlog 过多及时报警
- 监控组件采集 Redis INFO 信息时,采用长连接,避免频繁的短连接
- 做好实例运行时监控,重点关注 expired_keys、evicted_keys、latest_fork_usec 指标,这些指标短时突增可能会有阻塞风险
以上命令,应尽量避免传递 [0 -1] 或 [-inf +inf] 这样的参数,来对 Sorted Set 做一次性的完整遍历,特别是在 Sorted Set 的尺寸不可预知的情况下。可以通过 ZSCAN 命令来进行游标式的遍历,或通过 LIMIT 参数来限制返回 member 的数量(适用于 ZRANGEBYSCORE 和 ZREVRANGEBYSCORE 命令),以实现游标式的遍历
时间复杂度为O(N),N随着redis中key的数量增加而增加,因此redis有大量的key,keys命令会执行很长时间,而由于Redis是单线程,某个命令耗费过长时间,则会导致后面的的所有请求无法得到响应
本文链接: https://erik.xyz/2021/04/07/redis-about-all/
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!