1. 引言
在现代应用中,数据存储和管理是至关重要的。Redis作为一个高性能的内存数据库,提供了多种数据结构和灵活的淘汰策略,使其在处理大规模数据时表现出色。本文将展示如何在CentOS 7环境下,使用Redis 7.0进行千万级数据的测试,并验证其淘汰策略的强大。
2. 准备工作
2.1 环境配置
- 操作系统:CentOS 7
- Redis版本:Redis 7.0
- 内存:16GB
2.2 Redis启动配置
在Redis的配置文件中,我们需要设置最大内存大小,以便测试其淘汰策略。以下是配置步骤:
-
打开Redis配置文件(通常位于
/etc/redis.conf
):
vi /etc/redis.conf
设置最大内存大小为3GB,默认的淘汰策略,保存并退出编辑器。
# 设置最大内存大小为3GB
maxmemory 3gb
# 默认的淘汰策略是 不淘汰任何键
maxmemory-policy noeviction
3. 开始测试
3.1 lua脚本随机生成数据测试
生成5千万的随机数据(小伙伴们请在测试环境模仿),key为memory-test进行插入操作,耗时22.49s
EVAL "for i=1,50000000 do local key = 'key-' .. math.random(1, 100000000) redis.call('SET', key, 'value-' .. math.random(1, 100)) end" 0
3.1 内存占用4.24G,键三千六百万
由于Redis默认的淘汰策略是不淘汰任何键的,已经超过启动配置文件配置的3GB
4. 更改淘汰策略(从所有键中选择最近最少使用(LRU)的键进行淘汰)
127.0.0.1:6379> CONFIG SET maxmemory-policy allkeys-lru
此时。。过了大概10秒钟。。。然后直接内存占用变成阈值的3G
清理能力是相当可观的
5.更改淘汰策略noeviction追加数据,触发拒绝插入
再生成千万的随机数据(小伙伴们请在测试环境模仿),key为memory-test进行插入操作
127.0.0.1:6379> EVAL "for i=1,40000000 do local key = 'key-' .. math.random(1, 100000000) redis.call('SET', key, 'value-' .. math.random(1, 100)) end" 0
(error) OOM command not allowed when used memory > 'maxmemory'. script: dcd03f2a5173001fd0c77681844bc05262124649, on @user_script:1.
插入的数据都是这种随机的:
5.1内存占用变化
>>>
追加的过程中是无法进行操作的比如info memory会阻塞,因为Redis本身在单线程执行,此时我们可以看到Redis使用单核CPU占用率约100%,时而打到103%,大概是Redis底层处理其他事情的线程同时在搞
写入内存过程持续变化
系统可用内存逐渐变小直到10秒出现了这样的情况
没错!我们的Redis淘汰策略在发威,约1G的数据10秒被干掉
6.持续Lua脚本原子性大批量插入过程中会遇到的问题
阻塞其他命令(如下Java服务)
如果其他命令在等待 Lua 脚本完成,可能导致连接池中的连接被占用,最终导致连接池耗尽,进而引发 RedisConnectionFailureException
Node source: NodeSource [slot=0, addr=null, redisClient=null, redirect=null, entry=null],
connection: RedisConnection@1920052236 [redisClient=[addr=redis://IP:6379], channel=[id: 0x3123abcd, L:/IP:49722 ! R:IP/IP:6379], currentCommand=null, usage=1],
command: (DEL), params: [[1,12,3,4,5,6,7, ...]] after 3 retry attempts;
nested exception is org.redisson.client.WriteRedisConnectionException: Unable to write command into connection! Increase connection pool size.
Node source: NodeSource [slot=0, addr=null, redisClient=null, redirect=null, entry=null],
connection: RedisConnection@1921005696 [redisClient=[addr=redis://IP:6379], channel=[id: 0x3357adg, L:/IP:10086 ! R:IP/IP:6379], currentCommand=null, usage=1],
command: (DEL), params: [[1,12,3,4,5,6,7, ...]] after 3 retry attempts
at org.redisson.spring.data.connection.RedissonExceptionConverter.convert(RedissonExceptionConverter.java:40)
at org.redisson.spring.data.connection.RedissonExceptionConverter.convert(RedissonExceptionConverter.java:35)
at org.springframework.data.redis.PassThroughExceptionTranslationStrategy.translate(PassThroughExceptionTranslationStrategy.java:44)
at org.redisson.spring.data.connection.RedissonConnection.transform(RedissonConnection.java:195)
at org.redisson.spring.data.connection.RedissonConnection.syncFuture(RedissonConnection.java:190)
at org.redisson.spring.data.connection.RedissonConnection.sync(RedissonConnection.java:356)
at org.redisson.spring.data.connection.RedissonConnection.write(RedissonConnection.java:722)
at org.redisson.spring.data.connection.RedissonConnection.del(RedissonConnection.java:209)
at org.springframework.data.redis.core.RedisTemplate.lambda$delete$2(RedisTemplate.java:713)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:228)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:188)
at org.springframework.data.redis.core.RedisTemplate.delete(RedisTemplate.java:713)
at sun.reflect.GeneratedMethodAccessor661.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.scheduling.support.ScheduledMethodRunnable.$sw$original$run$c8tpsq2(ScheduledMethodRunnable.java:84)
at org.springframework.scheduling.support.ScheduledMethodRunnable.$sw$auxiliary$ac1ucb1.call(Unknown Source)
at org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstMethodsInter.intercept(InstMethodsInter.java:86)
at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:90)
at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:93)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
内存使用触发淘汰策略,导致数据会被删除
大规模操作应拆分为多个小批次异步写入,避免一次性操作过多数据,以减少对主线程的阻塞
执行长时间运行的 Lua 脚本可能会导致 Redis 的性能下降和阻塞其他命令。建议使用分批处理和异步操作来减轻这种影响。
7.更多种淘汰策略,自行选择
- noeviction:不淘汰任何键,达到限制时返回错误。
- allkeys-lru:从所有键中选择最少使用的键进行淘汰。
- volatile-lru:只从设置了过期时间的键中选择最少使用的键进行淘汰。
- allkeys-random:随机选择键进行淘汰。
- volatile-random:随机选择设置了过期时间的键进行淘汰。
- volatile-lfu:从设置了过期时间的键中选择使用频率最低的键进行淘汰。
- allkeys-lfu:从所有键中选择使用频率最低的键进行淘汰。
以上Blog用到的命令有:
#查看当前的内存阈值配置
127.0.0.1:6379> CONFIG GET maxmemory
#设置最大内存阈值
127.0.0.1:6379> CONFIG SET maxmemory 3221225472
#查看redis内存使用情况的命令
127.0.0.1:6379> info memory
#设置 maxmemory-policy 内存阈值淘汰策略
127.0.0.1:6379> CONFIG SET maxmemory-policy 策略
#查看当前的内存阈值淘汰策略
127.0.0.1:6379> CONFIG GET maxmemory-policy
#生成随机 n 条数据的Lua命令
127.0.0.1:6379> EVAL "for i=1,n do redis.call('LPUSH', 'memory-test', 'value-' .. math.random(1, 100)) end" 0
以上命令需要在redis-cli中进行操作,直接登陆命令附这:
redis-cli -h IP地址 -p 端口 -a 密码
在测试过程中,我们观察到Redis在达到最大内存限制后,成功地按照设定的淘汰策略移除了最少使用的数据。这证明了Redis在处理千万级数据时的高效性和灵活性。
通过本次测试,我们验证了Redis的淘汰策略在处理大规模数据时的强大能力。Redis不仅能够高效存储数据,还能在内存达到限制时智能地管理数据,确保系统的稳定性和性能。