0%

Redis学习笔记--Redis一些问题详解

redis

Redis的同步机制

1
Redis可以使用主从同步,从从同步。第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将RDB文件全量同步到复制节点,复制节点接受完成后将RDB镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。后续的增量数据通过AOF日志同步即可,有点类似数据库的binlog。

Redis有哪些数据结构

1
2
String、Hash、List、Set、SortedSet、HyperLogLog、Geo、Pub/Sub、Redis Module,像BloomFilter,RedisSearch,Redis-ML

Redis是怎么持久化的?服务主从数据怎么交互的?

1
2
3
RDB做镜像全量持久化,AOF做增量持久化。因为RDB会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据,所以需要AOF来配合使用。在redis实例重启时,会使用RDB持久化文件重新构建内存,再使用AOF重放近期的操作指令来实现完整恢复重启之前的状态。
这里很好理解,把RDB理解为一整个表全量的数据,AOF理解为每次操作的日志就好了,服务器重启的时候先把表的数据全部搞进去,但是他可能不完整,你再回放一下日志,数据不就完整了嘛。不过Redis本身的机制是 AOF持久化开启且存在AOF文件时,优先加载AOF文件;AOF关闭或者AOF文件不存在时,加载RDB文件;加载AOF/RDB文件城后,Redis启动成功; AOF/RDB文件存在错误时,Redis启动失败并打印错误信息

如果突然机器掉电会怎样

1
取决于AOF日志sync属性的配置,如果不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据。但是在高性能的要求下每次都sync是不现实的,一般都使用定时sync,比如1s1次,这个时候最多就会丢失1s的数据。

RDB的原理

1
fork是指redis通过创建子进程来进行RDB操作,cow指的是copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。

为啥Redis那么快

1
2
3
4
5
6
7
8
9
10
11
Redis采用的是基于内存的采用的是单进程单线程模型的 KV 数据库,由C语言编写,官方提供的数据是可以达到100000+的QPS(每秒内查询次数)。

完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。它的,数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);

数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;

采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;

使用多路I/O复用模型,非阻塞IO;

使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;

啥是上下文切换

1
2
3
就好比你看一本英文书,你看到第十页发现有个单词不会读,你加了个书签,然后去查字典,过了一会你又回来继续从书签那里读,ok到目前为止没啥问题。

如果是你一个人读肯定没啥问题,但是你去查的时候,别的小伙伴好奇你在看啥他就翻了一下你的书,然后溜了,哦豁,你再看的时候就发现书不是你看的那一页了。不知道到这里为止我有没有解释清楚,以及为啥会线程不安全,就是因为你一个人怎么看都没事,但是人多了换来换去的操作一本书数据就乱了。可能我的解释很粗糙,但是道理应该是一样的。

主从之间的数据怎么同步

1
2
3
4
5
启动一台slave 的时候,他会发送一个psync命令给master ,如果是这个slave第一次连接到master,他会触发一个全量复制。master就会启动一个线程,生成RDB快照,还会把新的写请求都缓存在内存中,RDB文件生成后,master会将这个RDB发送给slave的,slave拿到之后做的第一件事情就是写进本地的磁盘,然后加载进内存,然后master会把内存里面缓存的那些新命名都发给slave。

###数据传输的时候断网了或者服务器挂了怎么办啊?

传输过程中有什么网络问题啥的,会自动重连的,并且连接之后会把缺少的数据补上的。

内存淘汰机制

1
2
3
4
5
6
7
8
9
10
11
noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)

allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。

volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。

allkeys-random: 回收随机的键使得新添加的数据有空间存放。

volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。

volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。

Redis 和 Memcached 有啥区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Memcache
注意后面会把 Memcache 简称为 MC。

先来看看 MC 的特点:

- MC 处理请求时使用多线程异步 IO 的方式,可以合理利用 CPU 多核的优势,性能非常优秀;
- MC 功能简单,使用内存存储数据;
- MC 的内存结构以及钙化问题我就不细说了,大家可以查看官网了解下;
- MC 对缓存的数据可以设置失效期,过期后的数据会被清除;
失效的策略采用延迟失效,就是当再次使用数据时检查是否失效;
当容量存满时,会对缓存中的数据进行剔除,剔除时除了会对过期 key 进行清理,还会按 LRU 策略对数据进行剔除。
另外,使用 MC 有一些限制,这些限制在现在的互联网场景下很致命,成为大家选择Redis、MongoDB的重要原因:

- key 不能超过 250 个字节;
- value 不能超过 1M 字节;
- key 的最大失效时间是 30 天;
- 只支持 K-V 结构,不提供持久化和主从同步功能。
Redis
先简单说一下 Redis 的特点,方便和 MC 比较。

与 MC 不同的是,Redis 采用单线程模式处理请求。这样做的原因有 2 个:一个是因为采用了非阻塞的异步事件处理机制;另一个是缓存数据都是内存操作 IO 时间不会太长,单线程可以避免线程上下文切换产生的代价。
Redis 支持持久化,所以 Redis 不仅仅可以用作缓存,也可以用作 NoSQL 数据库。
相比 MC,Redis 还有一个非常大的优势,就是除了 K-V 之外,还支持多种数据格式,例如 list、set、sorted set、hash 等。
Redis 提供主从同步机制,以及 Cluster 集群部署能力,能够提供高可用服务。

Redis 的线程模型

1
2
3
4
5
6
7
8
Redis 内部使用文件事件处理器 file event handler,这个文件事件处理器是单线程的,所以 Redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 Socket,根据 Socket 上的事件来选择对应的事件处理器进行处理。

文件事件处理器的结构包含 4 个部分:
- 多个 Socket
- IO 多路复用程序
- 文件事件分派器
事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
多个 Socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 Socket,会将 Socket 产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。

扫描大 KEY

1
./redis-cli --bigkeys -i 0.01

采样服务器指令

1
redis-cli --host 192.168.x.x --port 6379 monitor

诊断服务器时延

1
2
3
redis-cli --host 192.168.x.x --port 6379 --latency
时延单位是 ms。redis-cli 还能显示时延的分布情况,而且是图形化输出。
redis-cli --latency-dist

远程 rdb 备份

1
2
远程服务器会执行一次bgsave操作,然后将 rdb 文件传输到客户端
./redis-cli --host 192.168.x.x --port 6379 --rdb ./user.rdb

模拟从库

1
2
./redis-cli --host 192.168.x.x --port 6379 --slave
从库连上主库的第一件事是全量同步,所以看到上面的指令卡顿这很正常,待首次全量同步完成后,就会输出增量的 aof 日志。

aof 导入方式

1
2
3
4
5
6
7
8
# 清空上文目标实例全部数据
redis-cli -h 目标RedisIP -a password flushall
# 源实例开启 aof 功能,将在 dir 目录下生成 appendonly.aof 文件
redis-cli -h 源RedisIP -a password config set appendonly yes
# 将 appendonly.aof 文件放在当前路径下
redis-cli -h 目标RedisIp -a password --pipe < appendonly.aof
# 源实例关闭 aof 功能
redis-cli -h 源RedisIp -a password config set appendonly no

redis-dump 工具

Redis-Dump 是一个用于 Redis 数据导入 / 导出的工具,是基于 Ruby 实现的,可以方便的进行 redis 的数据备份。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 没安装 ruby 的话,先安装 ruby
brew install ruby
# 移除 gem 自带源
gem sources --remove https://rubygems.org/
# 添加淘宝源
gem sources -a https://ruby.taobao.org/
# 安装 redis-dump
gem install redis-dump -V
# 替换镜像地址
gem sources --add http://gems.ruby-china.org/ --remove http://rubygems.org/
# 确认镜像地址是否替换成功
gem sources -l
# 替换成功后再安装 redis-dump
gem install redis-dump -V
# redis-dump 导出
redis-dump -u :password@源RedisIp:6379 > 源Redis数据文件.json
# redis-load 导入
cat 源Redis数据文件.json | redis-load -u :password@目标RedisIp:6379

redis清理某个前缀的key

需要快速的清理掉某种前缀的key出现了脏数据key

安装 rdb 解析工具

1
2
3
pip install rdbtools python-lzf   # python2.7 下 一行命令即可完成安装

rdb -c memory dump-6379.rdb > memory.csv # 用这个命令将rdb进行分析

过滤出符合条件的key

1
2
3
4
5
6
7
8
9
10
11
12
13
awk  -F ',' '{print $3 ,  $NF }'  memory.csv > keys.txt    # 过滤出key的名称和过期时间

egrep key_ keys.txt > /root/key_.txt # 将 key_ 前缀的key 过滤出来

cat /root/key_.txt | sort -k 2 -r > /root/sort_keys # 对key按照日期进行倒序排序

egrep 2019-09-10 /root/sort_keys > /root/match_keys # 注意:我这里紧急处理,只过滤出 2019-09-10 过期的key(这是最新的数据,也是目前业务最常访问的key,也就是最需要紧急处理的)

awk '{print $1}' /root/match_keys > /root/filter_keys # 将最终需要处理的key重定向到一个文件

mkdir /root/test/

split -2000 /root/filter_keys /root/test/ # 将 filter_keys 这个文件 按照每个2k行切分成多个文件,便于后续并行处理

批量处理下

1
2
3
4
5
6
for i in `ls /root/test/`; do
echo "while read line;do
echo \"del \$line\" | redis-cli -h 127.0.0.1 -p 6379
done < /root/test/${i}" > /root/run_${i}.sh
chmod +x /root/run_${i}.sh
done

批量执行下

1
2
3
4
#!/bin/bash
for i in `ls run*.sh`; do
nohup sh $i > /dev/null &
done

redis-server 大量key过期不释放空间

使用 rdb工具 (git地址:https://github.com/sripathikrishnan/redis-rdb-tools) 分析下rdb文件后,发现内存中有很多的key,过期时间早到了,但是实际上还存在。原因: 因为redis的key清理策略是懒惰删除(lazy free),我们可以尝试调大,这样每秒钟执行的redis的内部cronjob次数将增大,也就可以加快key的淘汰。

1
2
1、config get hz   看到当前redis-server 默认值是10 
2、config set hz 50 我们这里将hz设置为50,然后观察段时间看看(注意hz的设置值可以以10为步长逐步增加,但是一般不要超过100)