Redis开发与运维笔记_总结

网友投稿 871 2022-10-02

本站部分文章、图片属于网络上可搜索到的公开信息,均用于学习和交流用途,不能代表睿象云的观点、立场或意见。我们接受网民的监督,如发现任何违法内容或侵犯了您的权益,请第一时间联系小编邮箱jiasou666@gmail.com 处理。

Redis开发与运维笔记_总结

Redis总结

初识

Redis的特性

1.速度快,纯内存操作,单线程架构,代码优雅2.基于键值对的数据结构服务器3.丰富的功能,键过期,Pub/Sub,Lua及Pipeline等4.稳定简单,源码简短而不简单5.客户端支持好6.RDB和AOF持久化策略7.主从复制功能8.Redis Sentinel和Redis Cluster实现高可用

Redis的使用场景

1.缓存2.排行榜3.计数器应用4.社交网络(标签)5.简单的消息队列系统

API的理解和使用

五种数据结构

字符串(String)、哈希(hash)、列表(list)、集合(set)、有序集合(zset)

Redis中每种数据结构对应不同的内部实现。具体对应关系如下

数据结构内部编码条件
字符串(String)int
embstr
raw
int:8个字节的长整形
embstr:≤39个字节的字符串
raw:>39个字节的字符串
哈希(hash)ziplist
hashtable
ziplist
hashtable
列表(list)ziplist
linkedlist
ziplist:元素个数<list-max-ziplist-entries
(512)个,元素大小<list-max-ziplist-value
(64字节)
linkedlist:不满足ziplist时
集合(set)intset
hashtable
intset:元素为整数,个数<set-max-intset-entries
(512)
hashtable:不满足intset条件时
有序集合(zset)ziplist
skiplist
ziplist
skiplist

命令

官方文档:https://redis.io/commands客户端内可以通过help 查看具体命令的说明,或者通过help @查看不同组内的命令说明,help命令可以通过Tab命令进行自动补全。

•了解每个命令的复杂度对于日常的使用有很大的帮助,可以根据数据规模预估复杂度高的命令阻塞Redis等问题•批量操作可以减少不必要的网络时间,有效的提高查询效率•scan命令可以解决keys命令带来的阻塞问题,同时Redis还提供了hscan、sscan、zscan渐进式地遍历hash、set、zset

应用场景

•String:缓存、计数、共享数据存储、限速•list:栈、队列、消息队列•set:标签,随机数抽奖等•zset:排行榜

小功能大用处

慢查询

redis的慢查询日志放在list中,通过slowlog get [n]获取列表,slowlog len查看列表长度。showlog reset重置。

慢查询由两个参数控制:

•slowlog-log-slower-than:慢查询阈值,默认10毫秒,命令超过1毫秒时QPS不足1000,生产建议调到1ms.•slowlog-max-len:慢查询队列长度,根据业务情况适当调大,避免日志丢失,同时可以采用定时同步到存储层的方法避免日志丢失。

Redis Shell

•redis-server:主要用于启动Redis实例•redis-cli:Redis客户端工具,可用于测试延迟,发送键命令,发送集群命令等完成客户端功能•redis-benchmark:Redis基准测试工具,主要用于基准性能测试。

Pipeline

将多条命令封装为一条pipeline发送到服务器,节省发送多次造成的网络耗时。

原生批量命令Pipeline
原生批量命令是原子的非原子
原生批量命令支持多个Key支持多条命令
原生批量命令是Redis服务端支持的Pipeline需要服务端和客户端共同支持

事务与Lua

multi代表事务开始,exec代表事务结束。可以通过watch key命令检查key是否被修改。

通过help @script查看脚本相关命令。

•SCRIPT LOAD script:加载Lua脚本到内存•SCRIPT EXISTS sha1 [sha1 ...]:检查Lua脚本是否加载•EVAL script numkeys key [key ...] arg [arg ...]:执行脚本•EVALSHA sha1 numkeys key [key ...] arg [arg ...]:执行已经加载的脚本•SCRIPT DEBUG YES|SYNC|NO:调试Lua脚本•SCRIPT FLUSH [ASYNC|SYNC]:清除已经加载的Lua脚本•SCRIPT KILL:停止正在执行的Lua命令,当Lua执行写入时无法停止,只能Kill掉Redis进程

Bitmaps

位操作,主要操作如下:

1.SETBIT key offset value:设置值2.GETBIT key offset:获取值3.BITCOUNT key [start end]:统计范围内为1的个数4.BITPOS key bit [start] [end]:统计范围内第一个值为对应bit的位置5.BITOP operation destkey key [key ...]:对多个String进行按位操作,可选操作如下:

•and(交集)、or(并 集)、not(非)、xor(异或)

HyperLoglog

HyperLogLog不是一种新的数据结构,而是一种基数算法,通过HyperLoglog可以使用极小的内存空间完成独立总数的统计。

•添加:PFADD key element [element ...]•  计算独立总数PFCOUNT key [key ...]•  合并: PFMERGE destkey sourcekey [sourcekey ...]

使用该结构时需要注意:1)只是为了获取独立总数,不需要获取单条记录;2)允许一定的误差

发布订阅(Pub/Sub)

Redis提供了基于“发布/订阅”模式的消息机制,此种模式下,消息发布者和订阅者不进行直接通信,发布者客户端向指定的频道(channel)发布消息,订阅该频道的每个客户端都可以收到该消息。

•发布消息到频道:PUBLISH channel message•订阅频道:SUBSCRIBE channel [channel ...]•取消订阅:UNSUBSCRIBE [channel [channel ...]]•按照模式进行订阅及取消:PSUBSCRIBE(PUNSUBSCRIBE) pattern [pattern ...]•查询订阅:PUBSUB subcommand [argument [argument ...]]

•查看活跃的频道:PUBSUB CHANNELS [pattern]•查看频道订阅数:PUBSUB NUMSUB [channels]•查看模式订阅数:PUBSUB NUMPAT

Geo

地理信息定位功能,目前用到的场景不多,此处略过

客户端

Redis通过Redis定制的RESP(Redis Serialization Protocol,Redis序列化协议)实现客户端与服务端的交互

命令格式:

*<参数数量> CRLF$<参数1的字节数量> CRLF<参数1> CRLF...$<参数N的字节数量> CRLF<参数N> CRLF

Redis支持的客户端可以查看Redis- Clients[1]

客户端的输入缓冲区最大为1G,不可配置,输出缓冲区支持普通客户端、发布订阅客户端、复制客户端配置,收到maxmemory限制。

info clients用于查看客户端统计信息,用于发现问题。

Redis的timeout配置可以自动关闭闲置客户端,tcp-keepalive参数可以周期性检查关闭无效TCP连接。

持久化

Redis提供了两种持久化方式:RDB和AOF

RDB

RDB一次性生产内存快照,产生的文件紧凑,读取RDB的速度更快。但是生成RDB文件的开销较大,无法做到实时持久化,一般用作冷备和复制传输。

主动生成rdb文件可以使用save或bgsave,save会阻塞主线程,一般不建议使用。bgsave通过fork创建子线程生成rdb文件。

rdb文件的存储位置和文件名称可以通过配置更改,也可以在运行期间通过config set进行动态修改。

AOF

AOF通过追加命令的方式记录操作日志。AOF主要解决了数据持久化的实时性。

通过appendonl yes开启AOF持久化,开启后,所有的写入命令会写入到aof_buf缓冲区,之后根据appendfsync设置的策略(一般为everysec,后台定时每秒fsync一次)刷到磁盘。

AOF重写,随着服务的运行,AOF文件会越来越大,执行bgrewriteaof手动进行AOF重写,或当文件大小超过auto-aof-rewrite-min-size(64MB)且文件增量超过auto-aof-rewrite-percentage时自动进行重写。AOF重写时,会fork子进程进行重写,运用Copy On Write技术,父进程在重写期间仍然可以响应命令。子进程根据内存数据移除无效命令、合并命令达到缩减AOF文件体积的目的。

copy-on-write:fork子线程期间,父子进程共用相同的页,当有写入命令时,父进程拷贝出页进行写入。同时,AOF重写期间,父进程将命令写入到重写缓冲区中,重写完成后将缓存区中内容写入到AOF以保证数据的不丢失。

AOF持久化可能出现追加阻塞问题,当硬盘资源紧张时,子线程无法完成写入磁盘,无法返回完成信号给主线程,主线程发现上次fsync超过2s时会阻塞,保证数据的一致性。

AOF重写期间,如果父进程收到大量请求可能导致输出缓冲区占用内存过多,严重时直接导致系统崩溃,Redis提供参数client-output-buffer-limit slave 256MB 64MB 60(如果60秒内输出缓冲区消耗持续大于64MB或者直接超过256MB,主节点将直接关闭客户端连接)。其默认限制如下:

client-output-buffer-limit normal 0 0 0client-output-buffer-limit slave 256mb 64mb 60client-output-buffer-limit pubsub 8mb 2mb 60

复制

通过配置文件或命令方式设置客户端为从节点。通过slave of one可以断开从节点的复制。默认情况,从节点为只读模式slave-read-only=yes,线上建议不要修改。repl-disable-tcp-nodelay用于合并多个小的请求定时发送(默认40ms发送一次),跨机房等网络带宽紧张环境建议开启。

复制分为全量复制和部分复制。主从复制时,主从节点都会保存自身的偏移量。主节点除了将命令发送给从节点,还会将命令写入复制积压缓冲区(repl_backlog_size默认为1MB)。从节点故障重新上线后,会将自身节点报给主节点,主节点根据偏移量计算,如果命中缓冲区则会将缓冲区内数据发送给从节点,进而避免了全量复制的资源消耗。

全量复制流程见:Redis开发与运维笔记_持久化#全量复制[2]

复制期间主从节点存在可能存在数据不一致现象。使用复制时应避免全量复制操作,避免主节点下挂载过多从节点,避免在他同一机器上部署过多主节点(规避机器故障恢复后的全量复制)。

阻塞

通过慢查询和监控指标发现阻塞问题。可能的原因分为两种:内在原因和外在原因

内在原因

根据慢查询日志定位慢查询,合理使用数据结构及命令。根据redis-cli bigkeys发现大对象,根据业务场景进行拆分或优化。

通过reids-cli --stat查看redis使用情况,利用top命令查看CPU利用率信息。根据info commandstats统计信息进行分析。

持久化相关的阻塞,Fork子进程阻塞、AOF刷盘阻塞、HugePage写操作阻塞(重写期间copy-on-write机制)

外在原因

CPU竞争,避免和IO密集型服务部署在同一台机器上。开启持久化功能的节点不要绑定单核,避免持久化期间子进程与父进程的CPU抢占。

操作系统会根据设置,当内存不足时,将内存中的数据交换到磁盘,如果Redis使用的内存被交换到硬盘,会造成性能的下降。

连接拒绝,操作系统连接数的限制(文件句柄限制ulimit -n,TCP连接队列backlog溢出),Redis最大连接数都会导致连接拒绝

网络延迟,跨机房部署或者网络抖动时,网络带宽资源紧张时,会造成Redis网络时间变长,发生阻塞

理解内存

内存消耗

使用info memory检查Redis的内存消耗状态

Redis进程内存消耗主要包括:自身内存+对象内存+缓存内存+内存碎片

•自身内存:redis自身占用内存,很小•对象内存:占用最大,存储用户的所有数据•缓存内存:客户端缓冲(输入最大1G,无法控制,输出由client-output-buffer-limit控制)、复制积压缓冲区(用于实现部分复制功能,建议调大)、AOF缓冲区(保存AOF重写期间保存的命令)•内存碎片:频繁执行更新(append、setrange)、大量过期键删除可能导致碎片率的提高。尽量做到数据对其和安全重启可以减缓此类问题

子进程内存消耗。redis通过Fork子进程完成持久化动作,由于copy-on-write机制,并不需要一倍的内存。

通过maxmemory设置Redis的最大内存。可以通过config set maxmemory 2GB动态调整内存上限。

内存溢出控制策略,通过maxmemory-policy参数控制,策略有如下几种:

•noeviction:默认策略,不删除数据。拒绝所有写入操作并返回客户端错误信息(error)OOM command not allowed when used memory,此时Redis只响应读操作•vloatile-lru:根据LRU算法删除设置了超时的键,直到腾出足够空间。如果没有可删除的键,回退到noeviction•allkeys-lru:根据LRU算法删除键。直到腾出足够空间•allkeys-random:随机删除所有键,直到腾出足够空间•volatile-ttl:根据键值对象的ttl,删除最近要过期的数据,如果没有,回退到noeviction策略•设置了maxmemory时,当used_memory>maxmemory的状态下,会触发回收内存操作。应设置足够大的maxmemory避免长期处于这种状态下。

可以通过以下方式优化内存使用:

1.精简键值对大小,使用高效二进制序列化工具2.使用对象共享池优化小整数对象3.数据优先使用整数4.优化字符串的使用,避免预分配造成的内存浪费5.使用ziplist压缩编码优化hash、list等结构,注重效率和空间的平衡6.使用intset编码优化整数集合

哨兵

哨兵主要用于保证Redis主从复制的高可用。以1主2从3哨兵节点为例:

部署

按照上述结构部署Redis:

角色ipport
master127.0.0.16379
slave-1127.0.0.16380
slave-2127.0.0.16381
sentinel-1127.0.0.126379
sentinel-2127.0.0.126380
sentinel-3127.0.0.126381

配置文件列表:

################### 主节点 ###################port 6379daemonize yeslogfile "6379.log" dbfilename "dump-6379.rdb" dir "./data/"################### 从节点 ###################port 6380daemonize yeslogfile "6380.log" dbfilename "dump-6380.rdb" dir "./data/"# 从节点对应的主节点slaveof 127.0.0.1 6379################### Sentinel节点 ###################port 26379daemonize yeslogfile "26379.log"dir ./data/# 别名:mymaster,监控节点 127.0.0.1 6379,2:判断下线至少需要2个节点同意sentinel monitor mymaster 127.0.0.1 6379 2sentinel down-after-milliseconds mymaster 30000sentinel parallel-syncs mymaster 1sentinel failover-timeout mymaster 180000

•可以监控多个主节点,通过多个 sentinel 配置即可

故障转移

当主节点出现故障时,从节点复制失败,故障转移流程如下:

1.主节点故障,从节点复制失败2.每个Sentinel节点通过定时监控发现主节点故障,进行客观下线判定3.多个Sentinel节点对主节点故障达成一致,选举出一个Sentinel作为领导者负责故障转移

领导者故障转移流程:

1.在从节点列表中选出一个节点作为主节点。

•过滤:主观下线、5秒无ping响应、与主节点失联超过down-after-milliseconds*10的节点,•选择slave-priority优先级最高的,存在则返回,不存在则继续•选择复制偏移量最大的从节点,存在则返回,不存在则继续•选择runId最小的从节点2.对选中的从节点执行slave of one使其晋升主节点3.复制新主节点,Sentinel领导者向其余节点发送命令,使其复制新的主节点。4.将原主节点更新为从节点,等待其恢复后使其复制新的主节点

集群

数据分布

Redis通过算法CRC16(key)%16383将key映射到不同的槽,每一个节点负责不同的槽。由于不同的键可能在不同的节点上,集群对批量和事务操作的支持有限。同时,集群的复制结构只支持主从复制,不再支持多层级结构。

集群搭建

搭建集群可以参考:Redis集群搭建5.0版[3]

节点通信

Redis采用Goosip协议通信,节点通过ping、pong彼此不断通信交换信息。

请求路由

集群节点收到请求时会先计算是否是自己的key,如果不是返回Move重定向响应供客户端使用。当键包含{hash_tag}时,槽会根据hash_tag进行计算,相同hash_tag的键会分布到同一个节点上。

当集群进行数据迁移时,客户端根据本地缓存发送命令,如果命令对应的键不在本节点,则可能在迁移目标节点中,此时会返回ASK重定向响应。

故障转移

当集群内主节点故障时,会进行故障恢复流程:

1.资格检查:从节点与主节点断线时间应在cluster-node-time*cluster-slave-validity-factor内,否则无资格2.准备选举时间:从节点根据偏移量进行延迟选举,偏移量大的从节点延迟时间更小3.进行选举:更新配置纪元,广播选举消息4.选举投票:只有主节点有 投票资格,当从节点收到N/2+1票时,从节点可以执行替换主节点操作。N为故障前主节点个数,所以要将集群主节点部署到不同机器,避免多个主节点故障导致投票无法成功5.替换主节点:当前从节点取消复制变为主节点,撤销原主节点负责的槽,分配槽给当前节点,广播pong消息通知该从节点晋升为主且负责对应的槽

故障发现与转移通过集群内的Gossip协议通讯自动完成,故障转移的时间分布如下:

1.主观下线识别时间cluster-node-timeout2.主观下线传播时间<=cluster-node-timeout/2,消息机制会对超过cluster-node-timeout/2未通信的节点发起ping,消息体中会优先包含主观下线节点信息,通常在这段时间内就可以收集到半数以上pfail节点的下线报告而完成故障发现3.从节点转移时间<=1000ms,由于延迟选举机制,偏移量最大的节点会在1000ms以内发起选举,通常一次成功

故障转移计算公式:

failover-time(毫秒) ≤ cluster-node-timeout + cluster-node-timeout/2 + 1000

由上述公式可知:故障转移的时间和cluster-node-timeout息息相关,可根据服务器性能及业务场景配置

集群运维

cluster-require-full-coverage配置用于配置槽未全部分配时集群是否可用。根据业务场景进行选择,注意的是,当配置为false,持有槽的主节点故障也会导致集群不可用。

Gossip协议不断通信会造成带宽消耗严重,可以根据业务适当调大cluster-node-timeout,将节点均匀的部署到不同机器上。

集群内进行消息发布时,所有节点的订阅客户端都会收到消息,Pub/Sub命令应尽量避免在大的集群中使用,否则会严重消耗带宽。

根据redis-cli --cluster info 127.0.0.1:6379 # 查看集群槽分配情况检查槽分配情况,使用redis-cli --cluster rebalance 127.0.0.1:6379 # 重新平衡槽重新分配槽。根据redis-cli --bigkeys命令发现大的键,然后根据业务场景拆分并进行转移。规避将热点key作为hash_tag将其分配到同一节点上。以上问题会造成集群的数据倾斜。

通过使用cluster failover可以手动发起故障转移流程,该命令含有两个附加参数

•force:用于主节点宕机的场景,从节点不需要确认偏移量•takeover:从节点直接执行替换主节点,用于无法完成选举的情况

数据迁移建议使用唯品会开源的redis-migrate-tool,更多详情请参考:https://github.com/vipshop/redis-migrate-tool

缓存设计

引入缓存

缓存解决了数据访问速度慢的问题,降低了数据库的压力,但同时也引入了新的问题:缓存层与存储层的数据不一致,编码的复杂度,降低了系统的稳定性等等。

缓存的更新策略主要分为主动更新和自动触发

•自动:通过Redis的过期和剔除策略,可以自动的清除不需要的缓存•手动:更新存储层数据时,同步更新缓存内容(或者清除缓存内容,再次查询时增加缓存)

缓存穿透

缓存穿透是指查询一个不存在的数据,处于容错的考虑,通常不会对此次查询进行缓存。解决方法是缓存空对象,添加一个短时间的过期时间避免内存资源浪费。

无底洞优化

批量操作的key分布在多个节点上时,操作时间为n*网络时间+n*命令时间,常见优化手段:

1.串行命令,执行n次get2.串行IO,对映射到相同槽的键执行mget或者pipeline操作,时间为 node*网络时间+n次命令时间3.并行IO,多线程执行2中方案,时间为:max(node网络时间)+n次命令时间,编程复杂4.利用hash_tag:时间为1次网络时间+N次命令时间。但是可能造成数据倾斜

雪崩优化

缓存层由于故障或其他原因不能提供服务,大量请求命中储存层,造成级联崩溃的情况。解决方案如下:

1.使用Redis Sentinel 或 Redis Cluster 保证缓存高可用2.使用熔断组件如Hystrix[4]等进行资源隔离并熔断处理。3.上线前演练,尽量模拟极端情况,避免线上问题

热点key重建优化

当一个热点key的并发量非常大,并且更新缓存不能在短时间内完成时,缓存失效到重建期间,大量请求的话,会导致大量线程重建缓存,增加后端负载。要解决这个问题可以减少重建缓存的次数。

1.互斥锁,缓存失效期间,只允许一个或少量线程去进行缓存重建,其他线程进行阻塞等待或其他熔断措施2.设置不过期,过期由业务系统维护,当业务系统发现数据过期时使用单独的线程去进行缓存重建。注:会导致数据不一致情况。

Redis的配置

详情请参考:Redis configuration – Redis[5]

References

[1] Redis- Clients: https://redis.io/clients[2] Redis开发与运维笔记_持久化#全量复制: https://leithda.gitee.io/posts/3460365957.html#%E5%85%A8%E9%87%8F%E5%A4%8D%E5%88%B6[3] Redis集群搭建5.0版: https://leithda.gitee.io/posts/1044596185[4] Hystrix: https://github.com/netflix/hystrix[5] Redis configuration – Redis: https://redis.io/topics/config

上一篇:PG运维常用知识点小结
下一篇:聊聊数据库:SQL运维~先导篇
相关文章

 发表评论

暂时没有评论,来抢沙发吧~