Redis Q&A
1.redis db所在机器挂了 恢复数据?
如果开启了AOF,可以基于AOF恢复 ,如果 AOF 文件破损,那么用
redis-check-aof fix
如果redis 当前最新的 AOF 和 RDB 文件出现了丢失/损坏那么可以尝试基于该机器上当前的某个最新的 RDB 数据副本进行数据恢复
模拟数据恢复-错误的做法:停止 redis之后,先删除 appendonly.aof,然后将我们的 dump.rdb 拷贝过去,然后再重启 redis,这个时候其实不会恢复 dump.rdb 的数据,因为我们开启了 aof,当 aof 不存在的时候,也不会主动去用 dump.rdb 去恢复数据
正确的做法:停止 redis,关闭 aof,拷贝 rdb 备份,重启 redis,确认数据恢复, 直接在命令行热修改 redis 配置,打开 aof,这个 redis 就会将内存中的数据对应的日志,写入 aof 文件中
2.redis cluster节点迁移机器?
有时候机器挂了或者有问题, 想把集群某台机器的节点迁移到另一台机器上. 这个时候可以把nodes.conf文件拷贝到新机器上, 改掉nodes.conf中的ip, 把原节点关掉, 把新节点拉起来, 加进去集群里面. 这利用了只要节点的node id一样, Redis就会把新节点替换掉原节点, 并且自动更新ip和port.
3.redis复制流程?
复制流程:
- slave发送psync runid offset给master,请求同步数据
- master检查slave发来的runid和offset参数,决定是发送全量数据还是部分数据
- 如果slave是第一次与master同步,或者master-slave断开复制太久,则进行全量同步
- master在后台生成RDB快照文件,通过网络发给slave,slave接收到RDB文件后,清空自己本地数据库
slave加载RDB数据到内存中 - 如果master-slave之前已经建立过数据同步,只是因为某些原因断开了复制,此时只同步部分数据
- master根据slave发来的数据位置offset,只发送这个位置之后的数据给slave,slave接收这些差异数据,更新自己的数据,与maser保持一致,之后master产生的写入,都会传播一份给slave,slave与master保持实时同步
- runid:master节点的唯一标识
- offset:slave需要从哪个位置开始同步数据
复制同步期间产生的增量数据会写入到复制缓冲区 repl_baklog 它是一个固定大小的队列 默认1mb 可以修改, 待slave加载RDB文件完成之后,master会把复制缓冲区的这些增量数据发送给slave,slave依次执行这些命令
slave与master建立连接后,slave就属于master的一个client,master会为每个client分配一个client output buffer,master和每个client通信都会先把数据写入到这个内存buffer中,再通过网络发送给这个client。
master定时向slave发送ping repl-ping-slave-period 默认10s repl-timeout默认60s未收到则断开
检查slave是否正常: slave定时发送 replconf ack $offset告诉master自己的复制位置
4.redis使用场景or功能?
- 基于键过期(TTL)的特性,可以提供缓存服务:
- 缓存session会话
- 缓存用户信息、找不到再去MySQL中查、将查询结果获取然后写到Redis中
基于列表与有序集合,可以做排行榜: - 热度排名排行榜
- 发布时间排行榜
基于字符串的incr与decr,可以做计数器: - 帖子浏览数
- 视频播放数
- 商品浏览数
基于集合,可以做社交网络相关的功能: - 共同好友、共同喜好
- 基于共同喜好进行内容推送
5.redis如何存储键值对
Redis 使用「dict」结构来保存所有的键值对(key-value)数据,这是一个全局哈希表,所以对 key 的查询能以 O(1) 时间得到
- dictType:存储了hash函数,key和value的复制等函数;
- ht_table:长度为 2 的 数组,正常情况使用 ht_table[0] 存储数据,当执行 rehash 的时候,使用 ht_table[1] 配合完成 。
哈希桶的每个元素的结构由 dictEntry 定义:
typedef struct dictEntry {
// 指向 key 的指针
void *key;
union {
// 指向实际 value 的指针
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
// 哈希冲突拉出的链表
struct dictEntry *next;
} dictEntry;
- key 指向键值对的键的指针,key 都是 string 类型。
- value 是个 union(联合体)当它的值是 uint64_t、int64_t 或 double 类型时,就不再需要额外的存储,这有利于减少内存碎片。(为了节省内存操碎了心)当然,val 也可以是 void 指针,指向值的指针,以便能存储任何类型的数据。
- next 指向另一个 dictEntry 结构, 多个 dictEntry 可以通过 next 指针串连成链表, 从这里可以看出, ht_table 使用链地址法来处理键碰撞:当多个不同的键拥有相同的哈希值时,哈希表用一个链表将这些键连接起来。
哈希桶并没有保存值本身,而是指向具体值的指针,从而实现了哈希桶能存不同数据类型的需求。
6.redis怎么优化内存使用?
- key 的命名使用「业务模块名:表名:数据唯一id」对于 key 的优化:使用单词简写方式优化内存占用
- 过滤不必要的数据:不要大而全的一股脑将所有信息保存,想办法去掉一些不必要的属性,比如缓存登录用户的信息,通常只需要存储昵称、性别、账号等。
- 精简数据:比如用户的会员类型:0 表示「屌丝」、1 表示 「VIP」、2表示「VVIP」。而不是存储 VIP 这个字符串。
- 数据压缩:对数据的内容进行压缩,比如使用 GZIP、Snappy。
- 使用性能好,内存占用小的序列化方式。比如 Java 内置的序列化不管是速度还是压缩比都不行,我们可以选择 protostuff,kryo等方式。如下图 Java 常见的序列化工具空间压缩比:
- 使用 32 位的 Redis
使用32位的redis,对于每一个key,将使用更少的内存,因为32位程序,指针占用的字节数更少。
但是32的Redis整个实例使用的内存将被限制在4G以下。我们可以通过 cluster 模式将多个小内存节点构成一个集群,从而保存更多的数据。
另外小内存的节点 fork 生成 rdb 的速度也更快。
RDB和AOF文件是不区分32位和64位的(包括字节顺序),所以你可以使用64位的Redis 恢复32位的RDB备份文件,相反亦然。 - 通过“info memory”命令查看mem_fragmentation_ratio(内存碎片率),当mem_fragmentation_ratio > 1.5 时,建议开始清理内存碎片。当然,也可以通过分析调整activedefrag的参数配置从而达到自动清理效果
- memory purge:手动整理内存碎片,会阻塞主进程,生产环境慎用,清理效果和activedefrag并不相同。
activedefrag:自动整理内存碎片,其原理是通过scan迭代整个Redis数据,通过一系列的内存复制、转移操作完成内存碎片整理,由于此操作使用的是主线程,故会影响Redis对其他请求的响应。
Redis中常用缓冲区介绍
在Redis中,常见的缓冲区有下面两类:
1、C-S架构中的输入输出缓冲区
2、主从复制架构中的缓冲区
其中:
C-S架构中的缓冲区主要分为客户端输入缓冲区和客户端输出缓冲区;
主从复制架构中的缓冲区主要指复制缓冲区和复制积压缓冲区
案例描述
问题现象:
Redis主从复制的过程中,主从关系迟迟不能建立起来,主库频繁进行bgsave。
日志信息:
主库错误日志(红色部分)
61:C 28 Sep 13:14:56.494 * DB saved on disk
61:C 28 Sep 13:14:57.309 * RDB: 7319 MB of memory used by copy-on-write
24:M 28 Sep 13:15:00.028 * Background saving terminated with success
24:M 28 Sep 13:15:00.028 * Starting BGSAVE for SYNC with target: disk
24:M 28 Sep 13:15:00.513 * Background saving started by pid 62
24:M 28 Sep 13:18:09.322 # Client id=891379 addr=10.xx.xx.150:51523 fd=70 name= age=330 idle=330 flags=S db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=15003 oll=70225 omem=1073742241 events=r cmd=psync
# scheduled to be closed ASAP for overcoming of output buffer limits.
24:M 28 Sep 13:18:09.329 # Connection with slave 10.xx.xx.150:33048 lost.
24:M 28 Sep 13:19:10.693 * Slave 10.13.16.150:33048 asks for synchronization
24:M 28 Sep 13:19:10.698 * Full resync requested by slave 10.13.16.150:33048
24:M 28 Sep 13:19:10.698 * Can't attach the slave to the current BGSAVE. Waiting for next BGSAVE for SYNC
可以看到,主库在进行了bgsave的时候,发生了中断,和从库之间的连接被断开了,原因也很清楚,就是超过了output buffer的值
从库错误日志:
主要看三行蓝色的字体,全量复制—中断—重新全量复制
26:S 28 Sep 11:44:09.485 * MASTER <-> SLAVE sync started
26:S 28 Sep 11:44:09.486 * Non blocking connect for SYNC fired the event.
26:S 28 Sep 11:44:09.488 * Master replied to PING, replication can continue...
26:S 28 Sep 11:44:09.490 * Partial resynchronization not possible (no cached master)
26:S 28 Sep 11:44:09.492 * Full resync from master: dce8c7c4b46d4ee1c581cbcc157fdd38c6f9e199:48879999224307
26:S 28 Sep 11:50:43.097 * MASTER <-> SLAVE sync: receiving 9830853612 bytes from master
26:S 28 Sep 11:52:40.550 * MASTER <-> SLAVE sync: Flushing old data
26:S 28 Sep 11:52:40.550 * MASTER <-> SLAVE sync: Loading DB in memory
26:S 28 Sep 11:54:30.700 * MASTER <-> SLAVE sync: Finished with success
26:S 28 Sep 11:54:31.084 * Background append only file rewriting started by pid 30
26:S 28 Sep 11:54:31.263 # Connection with master lost.
26:S 28 Sep 11:54:31.263 * Caching the disconnected master state.
26:S 28 Sep 11:54:31.659 * Connecting to MASTER 10.xx.xx.65:33048
26:S 28 Sep 11:54:31.659 * MASTER <-> SLAVE sync started
26:S 28 Sep 11:54:31.660 * Non blocking connect for SYNC fired the event.
26:S 28 Sep 11:54:31.661 * Master replied to PING, replication can continue...
26:S 28 Sep 11:54:31.663 * Trying a partial resynchronization (request dce8c7c4b46d4ee1c581cbcc157fdd38c6f9e199:48880001572670).
26:S 28 Sep 11:54:32.182 * Full resync from master: dce8c7c4b46d4ee1c581cbcc157fdd38c6f9e199:48882189455771
26:S 28 Sep 11:54:32.182 * Discarding previously cached master state.
26:S 28 Sep 11:55:43.241 * AOF rewrite child asks to stop sending diffs.
分析:
这个全量复制期间的缓冲区示意图如下:
如果在全量复制时,从节点接收和加载RDB较慢,同时主节点接收到了大量的写命令,写命令在复制缓冲区中就会越积越多,最终导致溢出。主节点上的复制缓冲区,本质上也是一个用于和从节点连接的客户端,使用的输出缓冲区。复制缓冲区一旦发生溢出,主节点也会直接关闭和从节点进行复制操作的连接,导致全量复制失败。
如何解决?
在Redis中,可以通过配置client-output-buffer-limit来解决这个问题。顾名思义,它的作用就是设置client的输出缓冲区限制的。
这个参数,可以设置三类客户端的输出缓冲区,分别是普通客户端、从库客户端、订阅客户端,对应的参数设置为:
normal 0 0 0
slave 256mb 64mb 60
pubsub 8mb 2mb 60
其中:
normal代表普通客户端,后面第1个0设置的是缓冲区大小限制,第2个0和第3个0分别表示缓冲区持续写入量限制和持续写入时间限制,通常把普通客户端的缓冲区大小限制,以及持续写入量限制、持续写入时间限制都设置为0,也就是不做限制,因为如果不是读取体量特别大的bigkey,服务器端的输出缓冲区一般不会被阻塞的。
slave参数代表该配置项是针对复制缓冲区的。256mb代表将缓冲区大小的上限设置为256MB;64mb和60表示如果连续60秒内的写入量超过64MB的话,就会触发缓冲区溢出,全量复制连接被关闭,全量复制失败
pubsub代表订阅客户端,对于订阅客户端来说,一旦订阅的Redis频道有消息了,服务器端都会通过输出缓冲区把消息发给客户端,如果频道消息较多的话,也会占用较多的输出缓冲区空间。设置的数值含义和slave的设置雷同,不再赘述。
有了上述参数的含义,我们可以利用当前的写入速度和写入key的大小来粗略估计这个参数的合理值。一般而言,将这个参数设置到GB级别,即可解决问题,如果你的Redis数据量比较多,写入比较频繁,可以适量上调。
举个简单例子,假设我们每个命令写入1k数据,而我们的client-output-buffer-limit参数设置的是:slave 128m 64m 60,那么上限只能使用128m的内存,128m/1k/60s=2133条命令每秒,也就是写入的QPS最多只能到达2000左右。
还需要注意,主节点上的复制缓冲区的内存开销,是针对每一个从库都有的,如果有多个从节点同时发起主从同步,主节点的复制缓冲区开销就会很大,容易造成OOM,因此我们需要控制从节点的数量来避免复制缓冲区占用过多内存。
实际解决方案:
调整参数
redis> config set client-output-buffer-limit "normal 0 0 0 slave 5073741824 2073435456 120 pubsub 33554432 8388608 60"
redis> config rewrite
最终,全量复制成功,日志如下
主库复制正确日志:
67:C 28 Sep 14:18:33.901 * DB saved on disk
67:C 28 Sep 14:18:34.422 * RDB: 6929 MB of memory used by copy-on-write
24:M 28 Sep 14:18:36.793 * Background saving terminated with success
24:M 28 Sep 14:20:02.757 * Synchronization with slave 10.xx.16.150:33048 succeeded
24:M 28 Sep 14:25:41.242 * Slave 10.xxx.5.201:33048 asks for synchronization
24:M 28 Sep 14:25:41.242 * Partial resynchronization not accepted: Replication ID mismatch (Slave asked for 'ff7e489603b8e643f01bda73aceb7d0fa818b955', my replication IDs are 'dce8c7c4b46d4ee1c581cbcc157fdd38c6f9e199' and '0000000000000000000000000000000000000000')
24:M 28 Sep 14:25:41.242 * Starting BGSAVE for SYNC with target: disk
24:M 28 Sep 14:25:41.732 * Background saving started by pid 68
68:C 28 Sep 14:32:30.411 * DB saved on disk
68:C 28 Sep 14:32:31.107 * RDB: 78 MB of memory used by copy-on-write
24:M 28 Sep 14:32:31.781 * Background saving terminated with success
24:M 28 Sep 14:33:55.890 * Synchronization with slave 10.xxx.5.201:33048 succeeded