
正文
redis存入几条就断了,redis会内存溢出吗
提示:扫一扫查出行【扫一扫了解最新限行尾号】
复制提示
redis 写入失败 为什么
检查redis是否启动成功,看下日志
看看你的脚本的客户端连接redis是否成功,可以用redis命令ping一下
看下redis的log,看看报错
调低redis的log级别,看下你这个命令的执行过程有什么异常
相关问答
Q1: Redis内存满了怎么办?
长期把Redis做缓存用,总有一天Redis内存会满的,怎么处理呢?
在Redis的配置文件 redis.conf 文件中,配置 maxmemory 的大小参数如下所示:
倘若实际的存储中超出了Redis的配置参数的大小时,Redis中有 淘汰策略 ,把 需要淘汰的key给淘汰掉,整理出干净的一块内存给新的key值使用 。
Redis提供了 6种的淘汰策略 ,其中默认的是 noeviction ,这6中淘汰策略如下:
LRU(Least Recently Used) 即表示最近最少使用,也就是在最近的时间内最少被访问的key,算法根据数据的历史访问记录来进行淘汰数据。
它的核心的思想就是: 假如一个key值在最近很少被使用到,那么在将来也很少会被访问 。
实际上Redis实现的LRU并不是真正的LRU算法,也就是名义上我们使用LRU算法淘汰键,但是实际上被淘汰的键并不一定是真正的最久没用的。
Redis使用的是近似的LRU算法, 通过随机采集法淘汰key,每次都会随机选出5个key,然后淘汰里面最近最少使用的key 。
这里的5个key只是默认的个数,具体的个数也可以在配置文件中进行配置,在配置文件中的配置如下图所示:
当近似LRU算法取值越大的时候就会越接近真实的LRU算法,可以这样理解,因为 取值越大那么获取的数据就越全,淘汰中的数据的就越接近最近最少使用的数据 。
那么为了实现根据时间实现LRU算法,Redis必须为每个key中额外的增加一个内存空间用于存储每个key的时间,大小是3字节。
在Redis 3.0中对近似的LRU算法做了一些优化,Redis中会维护大小是 16 的一个候选池的内存。
当第一次随机选取的采样数据,数据都会被放进候选池中,并且候选池中的数据会根据时间进行排序。
当第二次以后选取的数据,只有 小于候选池内的最小时间 的才会被放进候选池中。
当某一时刻候选池的数据满了,那么时间最大的key就会被挤出候选池。当执行淘汰时,直接从候选池中选取最近访问时间最小的key进行淘汰。
这样做的目的就是选取出最近似符合最近最少被访问的key值,能够正确的淘汰key值,因为随机选取的样本中的最小时间可能不是真正意义上的最小时间。
但是LRU算法有一个弊端:就是假如一个key值在以前都没有被访问到,然而最近一次被访问到了,那么就会认为它是热点数据,不会被淘汰。
然而有些数据以前经常被访问到,只是最近的时间内没有被访问到,这样就导致这些数据很可能被淘汰掉,这样一来就会出现误判而淘汰热点数据。
于是在Redis 4.0的时候除了LRU算法,新加了一种LFU算法, 那么什么是LFU算法算法呢?
LFU(Least Frequently Used) 即表示最近频繁被使用,也就是最近的时间段内,频繁被访问的key,它以最近的时间段的被访问次数的频率作为一种判断标准。
它的核心思想就是:根据key最近被访问的频率进行淘汰,比较少被访问的key优先淘汰,反之则优先保留。
LFU算法反映了一个key的热度情况,不会因为LRU算法的偶尔一次被访问被认为是热点数据。
在LFU算法中支持 volatile-lfu 策略和 allkeys-lfu 策略。
在Redis种有三种删除的操作此策略,分别是:
在Redis中持久化的方式有两种 RDB 和 AOF
在RDB中是以快照的形式获取内存中某一时间点的数据副本,在创建RDB文件的时候可以通过 save 和 bgsave 命令执行创建RDB文件。
这两个命令都不会把过期的key保存到RDB文件中 ,这样也能达到删除过期key的效果。
当在启动Redis载入RDB文件的时候, Master 不会把过期的key载入,而 Slave 会把过期的key载入。
在AOF模式下,Redis提供了Rewite的优化措施,执行的命令分别是 REWRITEAOF 和 BGREWRITEAOF , 这两个命令都不会把过期的key写入到AOF文件中,也能删除过期key 。
RDB 是一种快照存储持久化方式,具体就是将 Redis 某一时刻的内存数据保存到硬盘的文件当中,默认保存的文件名为 dump.rdb ,而在 Redis 服务器启动时,会重新加载 dump.rdb 文件的数据到内存当中恢复数据。
开启RBD持久化方式
开启 RDB 持久化方式很简单,客户端可以通过向 Redis 服务器发送 save 或 bgsave 命令让服务器生成 rdb 文件,或者通过服务器配置文件指定触发 RDB 条件。
save 命令是一个同步操作。
当客户端向服务器发送 save 命令请求进行持久化时,服务器会阻塞 save 命令之后的其他客户端的请求,直到数据同步完成。
与 save 命令不同, bgsave 命令是一个异步操作。
当客户端发服务发出 bgsave 命令时, Redis 服务器主进程会 forks 一个子进程来数据同步问题,在将数据保存到rdb文件之后,子进程会退出。
所以,与 save 命令相比, Redis 服务器在处理 bgsave 采用子线程进行IO写入,而主进程仍然可以接收其他请求,但 forks 子进程是同步的,所以 forks 子进程时,一样不能接收其他请求,这意味着,如果forks一个子进程花费的时间太久(一般是很快的),bgsave命令仍然有阻塞其他客户的请求的情况发生。
除了通过客户端发送命令外,还有一种方式,就是在 Redis 配置文件中的 save 指定到达触发RDB持久化的条件,比如【多少秒内至少达到多少写操作】就开启 RDB 数据同步。
例如我们可以在配置文件redis.conf指定如下的选项:
之后在启动服务器时加载配置文件。
这种通过服务器配置文件触发RDB的方式,与bgsave命令类似,达到触发条件时,会forks一个子进程进行数据同步,不过最好不要通过这方式来触发RDB持久化,因为设置触发的时间太短,则容易频繁写入rdb文件,影响服务器性能,时间设置太长则会造成数据丢失。
介绍了三种让服务器生成rdb文件的方式,无论是由主进程生成还是子进程来生成,其过程如下:
Redis 的另外一个持久化方式: AOF(Append-only file) 。
与 RDB 存储某个时刻的快照不同, AOF 持久化方式会记录客户端对服务器的每一次写操作命令,并将这些写操作以 Redis 协议追加保存到以后缀为 aof 文件末尾,在Redis服务器重启时,会加载并运行 aof 文件的命令,以达到恢复数据的目的。
Redis默认不开启AOF持久化方式,我们可以在配置文件中开启并进行更加详细的配置,如下面的redis.conf文件:
在上面的配置文件中,我们可以通过 appendfsync 选项指定写入策略,有三个选项
客户端的每一个写操作都保存到 aof 文件当,这种策略很安全,但是每个写请注都有IO操作,所以也很慢。
appendfsync 的默认写入策略,每秒写入一次 aof 文件,因此,最多可能会丢失1s的数据。
Redis 服务器不负责写入 aof ,而是交由操作系统来处理什么时候写入 aof 文件。更快,但也是最不安全的选择,不推荐使用。
AOF将客户端的每一个写操作都追加到 aof 文件末尾,比如对一个key多次执行incr命令,这时候, aof 保存每一次命令到aof文件中,aof文件会变得非常大。
aof文件太大,加载aof文件恢复数据时,就会非常慢,为了解决这个问题,Redis支持aof文件重写,通过重写aof,可以生成一个恢复当前数据的最少命令集,比如上面的例子中那么多条命令,可以重写为:
通过在redis.conf配置文件中的选项no-appendfsync-on-rewrite可以设置是否开启重写,这种方式会在每次fsync时都重写,影响服务器性能,因此默认值为no,不推荐使用。
客户端向服务器发送bgrewriteaof命令,也可以让服务器进行AOF重写。
AOF重写方式也是异步操作,即如果要写入aof文件,则Redis主进程会forks一个子进程来处理,如下所示:
在写入aof日志文件时,如果Redis服务器宕机,则aof日志文件文件会出格式错误,在重启Redis服务器时,Redis服务器会拒绝载入这个aof文件,可以通过以下步骤修复aof并恢复数据。
AOF只是追加日志文件,因此对服务器性能影响较小,速度比RDB要快,消耗的内存较少。
我们可以从几个方面对比一下RDB与AOF,在应用时,要根本自己的实际需求,选择RDB或者AOF,其实,如果想要数据足够安全,可以两种方式都开启,但两种持久化方式同时进行IO操作,会严重影响服务器性能,因此有时候不得不做出选择。
当RDB与AOF两种方式都开启时,Redis会优先使用AOF日志来恢复数据,因为AOF保存的文件比RDB文件更完整。
Q2: 为什么往Redis写入的数据会突然消失了?
可能有人会遇到,Redis 经常会丢掉一些数据,写进去了,过一会儿可能就没了。那么你可能是将Redis当成存储了而没有当作缓存。啥叫缓存?用内存当缓存。内存是无限的吗,内存是很宝贵而且是有限的,磁盘是廉价而且是大量的。可能一台机器就几十个 G 的内存,但是可以有几个 T 的硬盘空间。Redis 主要是基于内存来进行高性能、高并发的读写操作的。
那既然内存是有限的,比如 Redis 就只能用 10G,你要是往里面写了 20G 的数据,会咋办?当然会干掉 10G 的数据,然后就保留 10G 的数据了。那干掉哪些数据?保留哪些数据?当然是干掉不常用的数据,保留常用的数据了。
还有一个问题就是数据明明过期了,怎么还占用着内存?这些问题主要是因为Redis内部的一些机制所导致的,接下来我将展开说说Redis的两个最基础机制。
Redis 过期策略
Redis 过期策略是: 定期删除+惰性删除 。
所谓 定期删除 ,指的是 Redis 默认是每隔 100ms 就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除。
假设 Redis 里放了 10w 个 key,都设置了过期时间,你每隔几百毫秒,就检查 10w 个 key,那 Redis 基本上就死了,cpu 负载会很高的,消耗在你的检查过期 key 上了。注意,这里可不是每隔 100ms 就遍历所有的设置过期时间的 key,那样就是一场性能上的 灾难 。实际上 Redis 是每隔 100ms 随机抽取 一些 key 来检查和删除的。
但是问题是,定期删除可能会导致很多过期 key 到了时间并没有被删除掉,那咋整呢?所以就是惰性删除了。这就是说,在你获取某个 key 的时候,Redis 会检查一下 ,这个 key 如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。
获取 key 的时候,如果此时 key 已经过期,就删除,不会返回任何东西。
但是实际上这还是有问题的,如果定期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期 key 堆积在内存里,导致 Redis 内存块耗尽了,咋整?
答案是: 走内存淘汰机制 。
内存淘汰机制
Redis 内存淘汰机制有以下几个:
noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。
allkeys-lru :当内存不足以容纳新写入数据时,在 键空间 中,移除最近最少使用的 key(这个是 最常用 的)。
allkeys-random:当内存不足以容纳新写入数据时,在 键空间 中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。
volatile-lru:当内存不足以容纳新写入数据时,在 设置了过期时间的键空间 中,移除最近最少使用的 key(这个一般不太合适)。
volatile-random:当内存不足以容纳新写入数据时,在 设置了过期时间的键空间 中, 随机移除 某个 key。
volatile-ttl:当内存不足以容纳新写入数据时,在 设置了过期时间的键空间 中,有 更早过期时间 的 key 优先移除。
链接:
Q3: 往redis存数据的时候不设置过期时间 是不是这条数据就永久
不会过期。 但是这样说有点绝对。一般情况是这样,当你配置中开启了超出最大内存限制就写磁盘的话,那么这些没有设置过期时间的key可能会被写到磁盘上。 假如没设置。那么REDIS将使用LRU机制,将内存中的老数据删除,并写入新数据。
可以用sorted set,把要过期的member和key的信息放在sorted set的member里,把过期时间放在score中。跑个任务用zrangebyscore遍历就行了。用sorted set好处是只需要遍历过期的member,不用扫描整个过期member集合。
Q4: typescript监听redis断开


打开APP



AaronWang94
关注
redis监听断开后,重新监听的方案 原创
2020-03-12 13:36:56

AaronWang94 
码龄9年
关注
之前搭建的springcloud gateway框架,出现了问题,一旦redis重启后监听就会失效。我的思路是,捕捉到redis断开的异常,并尝试重新监听。写了一个心跳机制,一旦redis断开连接,便等待redis重连,一旦重连后就重新开启监听,调用KeyspaceEventMessageListener的init方法,下面是代码。
@Slf4j
@Component
public class RedisMessageListener extends KeyspaceEventMessageListener {
@Resource
private RedisTemplateString,Object redisTemplate;
@Resource
private ApplicationEventPublisher publisher;
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private int redisPort;
@Value("${spring.redis.password}")
private String redisPassword;
public RedisMessageListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
@Override
public void onMessage(Message message, byte[] pattern) {
//监听redis的改变,若改变的值为GATEWAY_ROUTES,则更新路由map
String changeKey = message.toString();
try {
if (changeKey.contains(RouteConfig.REDIS_ROUTE_SOURCE)) {
//先清空路由
log.info("清空路由");
RouteConfig.clearRoute();
//从redis中获取路由
MapObject, Object redisRouteMap=redisTemplate.opsForHash().entries(RouteConfig.REDIS_ROUTE_SOURCE);
//将路由存入map中
redisRouteMap.forEach((key, routeDefinition) - {
RouteConfig.addRoute(key.toString(), JSON.parseObject(routeDefinition.toString(), RouteDefinition.class));
});
this.publisher.publishEvent(new RefreshRoutesEvent(this));
log.info("路由重置成功");
}
}
catch (Exception ex)
{
log.error("异常信息:",ex);
}
}
/*每隔10s定时心跳获取长连接*/
@Scheduled(cron = "0/10 * * * * *")
public void timer() throws InterruptedException {
boolean isOK=true;
//获取连接状态
isOK=checkRedisIsOk(redisHost,redisPort,redisPassword);
//若连接异常,则一直循环等待连接,直到连接成功后,重新开启监听
if(isOK==false)
{
//连接异常,则隔1s尝试连接
while (!checkRedisIsOk(redisHost,redisPort,redisPassword))
{
Thread.sleep(1000);
}
//若重新连接成功后,重新开启redis监听
super.init();
}
}
/**
* 检查redis是否存活
* @param url
* @param port
* @param password
* @return
*/
public boolean checkRedisIsOk(String url, int port, String password) {
boolean result=false;
try {
//连接本地Redis服务
Jedis jedis = new Jedis(url, port);
if (!password.isEmpty()) {
jedis.auth(password);
}
String ping = jedis.ping();
if (ping.equalsIgnoreCase("PONG")) {
log.info(new Date()+"redis连接正常!" + ping);
result=true;
}
} catch (Exception e) {
log.info(new Date()+"redis连接异常!");
result=false;
}
return result;
}
@Override
protected void doHandleMessage(Message message) {
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
采用定时器保持心跳,用jedis的ping方法判断连接是否异常。@schedule在上一个任务未结束前不会执行新的任务,不用担心任务会积压。
Q5: Redis常见延迟问题排查手册!附33条优化建议
Redis作为内存数据库,拥有非常高的性能,单个实例的QPS能够达到10W左右。但我们在使用Redis时,经常时不时会出现访问延迟很大的情况,如果你不知道Redis的内部实现原理,在排查问题时就会一头雾水。
很多时候,Redis出现访问延迟变大,都与我们的使用不当或运维不合理导致的。
下面我们就来分析一下Redis在使用过程中,经常会遇到的延迟问题以及如何定位和分析。
如果在使用Redis时,发现访问延迟突然增大,如何进行排查?
首先,第一步,建议你去查看一下Redis的慢日志。Redis提供了慢日志命令的统计功能,我们通过以下设置,就可以查看有哪些命令在执行时延迟比较大。
首先设置Redis的慢日志阈值,只有超过阈值的命令才会被记录,这里的单位是微妙,例如设置慢日志的阈值为5毫秒,同时设置只保留最近1000条慢日志记录:
# 命令执行超过5毫秒记录慢日志
CONFIG SET slowlog-log-slower-than 5000
# 只保留最近1000条慢日志
CONFIG SET slowlog-max-len 1000
设置完成之后,所有执行的命令如果延迟大于5毫秒,都会被Redis记录下来,我们执行SLOWLOG get 5查询最近5条慢日志:
127.0.0.1:6379 SLOWLOG get 5
1) 1) (integer) 32693 # 慢日志ID
2) (integer) 1593763337 # 执行时间
3) (integer) 5299 # 执行耗时(微妙)
4) 1) 'LRANGE' # 具体执行的命令和参数
2) 'user_list_2000'
3) '0'
4) '-1'
2) 1) (integer) 32692
2) (integer) 1593763337
3) (integer) 5044
4) 1) 'GET'
2) 'book_price_1000'
...
通过查看慢日志记录,我们就可以知道在什么时间执行哪些命令比较耗时, 如果你的业务经常使用O(n)以上复杂度的命令, 例如sort、sunion、zunionstore,或者在执行O(n)命令时操作的数据量比较大,这些情况下Redis处理数据时就会很耗时。
如果你的服务请求量并不大,但Redis实例的CPU使用率很高,很有可能是使用了复杂度高的命令导致的。
解决方案就是,不使用这些复杂度较高的命令,并且一次不要获取太多的数据,每次尽量操作少量的数据,让Redis可以及时处理返回。
如果查询慢日志发现,并不是复杂度较高的命令导致的,例如都是SET、DELETE操作出现在慢日志记录中,那么你就要怀疑是否存在Redis写入了大key的情况。
Redis在写入数据时,需要为新的数据分配内存,当从Redis中删除数据时,它会释放对应的内存空间。
如果一个key写入的数据非常大,Redis 在分配内存时也会比较耗时。 同样的,当删除这个key的数据时, 释放内存也会耗时比较久。
你需要检查你的业务代码,是否存在写入大key的情况,需要评估写入数据量的大小,业务层应该避免一个key存入过大的数据量。
那么有没有什么办法可以扫描现在Redis中是否存在大key的数据吗?
Redis也提供了扫描大key的方法:
redis-cli -h $host -p $port --bigkeys -i 0.01
使用上面的命令就可以扫描出整个实例key大小的分布情况,它是以类型维度来展示的。
需要注意的是当我们在线上实例进行大key扫描时,Redis的QPS会突增,为了降低扫描过程中对Redis的影响,我们需要控制扫描的频率,使用-i参数控制即可,它表示扫描过程中每次扫描的时间间隔,单位是秒。
使用这个命令的原理,其实就是Redis在内部执行scan命令,遍历所有key,然后针对不同类型的key执行strlen、llen、hlen、scard、zcard来获取字符串的长度以及容器类型(list/dict/set/zset)的元素个数。
而对于容器类型的key,只能扫描出元素最多的key,但元素最多的key不一定占用内存最多,这一点需要我们注意下。不过使用这个命令一般我们是可以对整个实例中key的分布情况有比较清晰的了解。
针对大key的问题,Redis官方在4.0版本推出了lazy-free的机制,用于异步释放大key的内存,降低对Redis性能的影响。即使这样,我们也不建议使用大key,大key在集群的迁移过程中,也会影响到迁移的性能,这个后面在介绍集群相关的文章时,会再详细介绍到。
有时你会发现,平时在使用Redis时没有延时比较大的情况,但在某个时间点突然出现一波延时,而且 报慢的时间点很有规律,例如某个整点,或者间隔多久就会发生一次。
如果出现这种情况,就需要考虑是否存在大量key集中过期的情况。
如果有大量的key在某个固定时间点集中过期,在这个时间点访问Redis时,就有可能导致延迟增加。
Redis的过期策略采用主动过期+懒惰过期两种策略:
注意, Redis的主动过期的定时任务,也是在Redis主线程中执行的 ,也就是说如果在执行主动过期的过程中,出现了需要大量删除过期key的情况,那么在业务访问时,必须等这个过期任务执行结束,才可以处理业务请求。此时就会出现,业务访问延时增大的问题,最大延迟为25毫秒。
而且这个访问延迟的情况, 不会记录在慢日志里。 慢日志中 只记录真正执行某个命令的耗时 ,Redis主动过期策略执行在操作命令之前,如果操作命令耗时达不到慢日志阈值,它是不会计算在慢日志统计中的,但我们的业务却感到了延迟增大。
此时你需要检查你的业务,是否真的存在集中过期的代码,一般集中过期使用的命令是expireat或pexpireat命令,在代码中搜索这个关键字就可以了。
如果你的业务确实需要集中过期掉某些key,又不想导致Redis发生抖动,有什么优化方案?
解决方案是, 在集中过期时增加一个随机时间,把这些需要过期的key的时间打散即可。
伪代码可以这么写:
# 在过期时间点之后的5分钟内随机过期掉
redis.expireat(key, expire_time + random(300))
这样Redis在处理过期时,不会因为集中删除key导致压力过大,阻塞主线程。
另外,除了业务使用需要注意此问题之外,还可以通过运维手段来及时发现这种情况。
我们需要对这个指标监控,当在 很短时间内这个指标出现突增 时,需要及时报警出来,然后与业务报慢的时间点对比分析,确认时间是否一致,如果一致,则可以认为确实是因为这个原因导致的延迟增大。
有时我们把Redis当做纯缓存使用,就会给实例设置一个内存上限maxmemory,然后开启LRU淘汰策略。
当实例的内存达到了maxmemory后,你会发现之后的每次写入新的数据,有可能变慢了。
导致变慢的原因是,当Redis内存达到maxmemory后,每次写入新的数据之前,必须先踢出一部分数据,让内存维持在maxmemory之下。
这个踢出旧数据的逻辑也是需要消耗时间的,而具体耗时的长短,要取决于配置的淘汰策略:
具体使用哪种策略,需要根据业务场景来决定。
我们最常使用的一般是allkeys-lru或volatile-lru策略,它们的处理逻辑是,每次从实例中随机取出一批key(可配置),然后淘汰一个最少访问的key,之后把剩下的key暂存到一个池子中,继续随机取出一批key,并与之前池子中的key比较,再淘汰一个最少访问的key。以此循环,直到内存降到maxmemory之下。
如果使用的是allkeys-random或volatile-random策略,那么就会快很多,因为是随机淘汰,那么就少了比较key访问频率时间的消耗了,随机拿出一批key后直接淘汰即可,因此这个策略要比上面的LRU策略执行快一些。
但以上这些逻辑都是在访问Redis时,真正命令执行之前执行的,也就是它会影响我们访问Redis时执行的命令。
另外,如果此时Redis实例中有存储大key,那么在淘汰大key释放内存时,这个耗时会更加久,延迟更大,这需要我们格外注意。
如果你的业务访问量非常大,并且必须设置maxmemory限制实例的内存上限,同时面临淘汰key导致延迟增大的的情况,要想缓解这种情况,除了上面说的避免存储大key、使用随机淘汰策略之外,也可以考虑拆分实例的方法来缓解,拆分实例可以把一个实例淘汰key的压力分摊到多个实例上,可以在一定程度降低延迟。
如果你的Redis开启了自动生成RDB和AOF重写功能,那么有可能在后台生成RDB和AOF重写时导致Redis的访问延迟增大,而等这些任务执行完毕后,延迟情况消失。
遇到这种情况,一般就是执行生成RDB和AOF重写任务导致的。
生成RDB和AOF都需要父进程fork出一个子进程进行数据的持久化,在fork执行过程中,父进程需要拷贝内存页表给子进程,如果整个实例内存占用很大,那么需要拷贝的内存页表会比较耗时,此过程会消耗大量的CPU资源,在完成fork之前,整个实例会被阻塞住,无法处理任何请求,如果此时CPU资源紧张,那么fork的时间会更长,甚至达到秒级。这会严重影响Redis的性能。
具体原理也可以参考我之前写的文章:Redis持久化是如何做的?RDB和AOF对比分析。
我们可以执行info命令,查看最后一次fork执行的耗时latest_fork_usec,单位微妙。这个时间就是整个实例阻塞无法处理请求的时间。
除了因为备份的原因生成RDB之外,在 主从节点第一次建立数据同步时 ,主节点也会生成RDB文件给从节点进行一次全量同步,这时也会对Redis产生性能影响。
要想避免这种情况,我们需要规划好数据备份的周期,建议 在从节点上执行备份,而且最好放在低峰期执行。 如果对于丢失数据不敏感的业务,那么不建议开启AOF和AOF重写功能。
另外,fork的耗时也与系统有关,如果把Redis部署在虚拟机上,那么这个时间也会增大。所以使用Redis时建议部署在物理机上,降低fork的影响。
很多时候,我们在部署服务时,为了提高性能,降低程序在使用多个CPU时上下文切换的性能损耗,一般会采用进程绑定CPU的操作。
但在使用Redis时,我们不建议这么干,原因如下。
绑定CPU的Redis,在进行数据持久化时,fork出的子进程,子进程会继承父进程的CPU使用偏好,而此时子进程会消耗大量的CPU资源进行数据持久化,子进程会与主进程发生CPU争抢,这也会导致主进程的CPU资源不足访问延迟增大。
所以在部署Redis进程时,如果需要开启RDB和AOF重写机制,一定不能进行CPU绑定操作!
上面提到了,当执行AOF文件重写时会因为fork执行耗时导致Redis延迟增大,除了这个之外,如果开启AOF机制,设置的策略不合理,也会导致性能问题。
开启AOF后,Redis会把写入的命令实时写入到文件中,但写入文件的过程是先写入内存,等内存中的数据超过一定阈值或达到一定时间后,内存中的内容才会被真正写入到磁盘中。
AOF为了保证文件写入磁盘的安全性,提供了3种刷盘机制:
当使用第一种机制appendfsync always时,Redis每处理一次写命令,都会把这个命令写入磁盘,而且 这个操作是在主线程中执行的。
内存中的的数据写入磁盘,这个会加重磁盘的IO负担,操作磁盘成本要比操作内存的代价大得多。如果写入量很大,那么每次更新都会写入磁盘,此时机器的磁盘IO就会非常高,拖慢Redis的性能,因此我们不建议使用这种机制。
与第一种机制对比,appendfsync everysec会每隔1秒刷盘,而appendfsync no取决于操作系统的刷盘时间,安全性不高。因此我们推荐使用appendfsync everysec这种方式,在最坏的情况下,只会丢失1秒的数据,但它能保持较好的访问性能。
当然,对于有些业务场景,对丢失数据并不敏感,也可以不开启AOF。
如果你发现Redis突然变得非常慢, 每次访问的耗时都达到了几百毫秒甚至秒级 ,那此时就检查Redis是否使用到了Swap,这种情况下Redis基本上已经无法提供高性能的服务。
我们知道,操作系统提供了Swap机制,目的是为了当内存不足时,可以把一部分内存中的数据换到磁盘上,以达到对内存使用的缓冲。
但当内存中的数据被换到磁盘上后,访问这些数据就需要从磁盘中读取,这个速度要比内存慢太多!
尤其是针对Redis这种高性能的内存数据库来说,如果Redis中的内存被换到磁盘上,对于Redis这种性能极其敏感的数据库,这个操作时间是无法接受的。
我们需要检查机器的内存使用情况,确认是否确实是因为内存不足导致使用到了Swap。
如果确实使用到了Swap,要及时整理内存空间,释放出足够的内存供Redis使用,然后释放Redis的Swap,让Redis重新使用内存。
释放Redis的Swap过程通常要重启实例,为了避免重启实例对业务的影响,一般先进行主从切换,然后释放旧主节点的Swap,重新启动服务,待数据同步完成后,再切换回主节点即可。
可见,当Redis使用到Swap后,此时的Redis的高性能基本被废掉,所以我们需要提前预防这种情况。
我们需要对Redis机器的内存和Swap使用情况进行监控,在内存不足和使用到Swap时及时报警出来,及时进行相应的处理。
如果以上产生性能问题的场景,你都规避掉了,而且Redis也稳定运行了很长时间,但在某个时间点之后开始,访问Redis开始变慢了,而且一直持续到现在,这种情况是什么原因导致的?
之前我们就遇到这种问题, 特点就是从某个时间点之后就开始变慢,并且一直持续。 这时你需要检查一下机器的网卡流量,是否存在网卡流量被跑满的情况。
网卡负载过高,在网络层和TCP层就会出现数据发送延迟、数据丢包等情况。Redis的高性能除了内存之外,就在于网络IO,请求量突增会导致网卡负载变高。
如果出现这种情况,你需要排查这个机器上的哪个Redis实例的流量过大占满了网络带宽,然后确认流量突增是否属于业务正常情况,如果属于那就需要及时扩容或迁移实例,避免这个机器的其他实例受到影响。
运维层面,我们需要对机器的各项指标增加监控,包括网络流量,在达到阈值时提前报警,及时与业务确认并扩容。
以上我们总结了Redis中常见的可能导致延迟增大甚至阻塞的场景,这其中既涉及到了业务的使用问题,也涉及到Redis的运维问题。
可见,要想保证Redis高性能的运行,其中涉及到CPU、内存、网络,甚至磁盘的方方面面,其中还包括操作系统的相关特性的使用。
作为开发人员,我们需要了解Redis的运行机制,例如各个命令的执行时间复杂度、数据过期策略、数据淘汰策略等,使用合理的命令,并结合业务场景进行优化。
作为DBA运维人员,需要了解数据持久化、操作系统fork原理、Swap机制等,并对Redis的容量进行合理规划,预留足够的机器资源,对机器做好完善的监控,才能保证Redis的稳定运行。
在上文中,主要讲解了 Redis 常见的导致变慢的场景以及问题定位和分析,主要是由业务使用不合理和运维不当导致的。
redis存入几条就断了的介绍就聊到这里吧,感谢你花时间阅读本站内容,更多关于redis会内存溢出吗、redis存入几条就断了的信息别忘了在本站进行查找喔。






