又是一个周末下班前发现线上接口报错,出错原因进过排查是使用了redis相关的接口,立马联想到redis可能出了问题,果不其然在redis上看到内存已经超出了设置的值,因此接口除了相关的问题。由此带来两个问题,也是我之所以也写这篇文章原因。
问题一 当redis内存满了会怎么样?线上因为为什么会报错。
首先redis是基于内存的key-value数据库,因为系统的内存大小有限,所以我们在使用Redis的时候可以配置Redis能使用的最大的内存大小。配置方法如下:
通过配置文件配置
//设置Redis最大占用内存大小为100M
maxmemory 100mb
通过命令修改
//设置Redis最大占用内存大小为100M
127.0.0.1:6379> config set maxmemory 100mb
//获取设置的Redis能使用的最大内存大小
127.0.0.1:6379> config get maxmemory
如果不设置最大内存大小或者设置最大内存大小为0,在64位操作系统下不限制内存大小,在32位操作系统下最多使用3GB内存
那么当redis内存满了时候究竟会怎么样呢,线上接口为啥会出错?这个问题就要涉及Redis的内存淘汰策略
Redis的内存淘汰
1) noeviction(默认策略):对于写请求不再提供服务,直接返回错误(DEL请求和部分特殊请求除外)
2) allkeys-lru:从所有key中使用LRU算法进行淘汰
3) volatile-lru:从设置了过期时间的key中使用LRU算法进行淘汰
4)allkeys-random:从所有key中随机淘汰数据
5)volatile-random:从设置了过期时间的key中随机淘汰
6)volatile-ttl:在设置了过期时间的key中,根据key的过期时间进行淘汰,越早过期的越优先被淘汰
当使用volatile-lru、volatile-random、volatile-ttl这三种策略时,如果没有key可以被淘汰,则和noeviction一样返回错误。
如何获取及设置内存淘汰策略
获取当前内存淘汰策略
127.0.0.1:6379> config get maxmemory-policy
通过配置文件设置淘汰策略(修改redis.conf文件):
maxmemory-policy allkeys-lru
通过命令修改淘汰策略:
127.0.0.1:6379> config set maxmemory-policy allkeys-lru
由于线上服务器没有设置过自然是默认的策略,其实这也是符合目前业务上通过持久化将reids做为存储数据的需求的。当然数据库中也做了相应的同步。因此线上写的接口报错而查询的接口正常。
问题二 如何分析redis内存使用的问题?如何查找为什么会使用这么多内存?
当redis内存满后需要分析内存使用的问题应该怎么做?其实通过redis-rdb-tools结合sqlite3就可以达到了。方法如下
1)备份redis的rdb文件。
confg get dir //显示数据所在目录
savebg 备份数据到rdb文件
以上执行完毕后可以将rdb文件下载到本机通过redis-rdb-tools来进行分析了。
2)redis-rdb-tools用法
redis-rdb-tools是一个基于Python的RDB文件解析工具,主要有以下三个功能:
a)生成内存快照;
b)将RDB文件中的数据转换为JSON格式;
c) 对比两个RDB文件,发现差异。
redis-rdb-tools安装
a)通过pip安装
pip install rdbtools
b)使用源码安装
git clone https://github.com/sripathikrishnan/redis-rdb-tools
cd redis-rdb-tools
sudo python setup.py install
使用redis-rdb-tools生成快照
rdb -c memory dump.rdb > memory.csv
生成的快照包含如下几列的数据:
数据库ID;
数据类型;
key;
内存使用量(Byte),包含key、value和其它值的容量;
编码。
生成的CSV文件示例如下:
$head memory.csv
database,type,key,size_in_bytes,encoding,num_elements,len_largest_element
0,string,"orderAt:377671748",96,string,8,8,
0,string,"orderAt:413052773",96,string,8,8,
0,sortedset,"Artical:Comments:7386",81740,skiplist,479,41,
0,sortedset,"pay:id:18029",2443,ziplist,84,16,
0,string,"orderAt:452389458",96,string,8,8
3)使用sqlite来分析
sqlite作为一个嵌入式数据库可以方便的分析数据,当然导入文件到mysql中也是可以的,这里主要通过sqlite来做。在装有Sqlite的机器上执行如下命令:
sqlite3 memory.db
sqlite> create table memory(database int,type varchar(128),key varchar(128),size_in_bytes int,encoding varchar(128),num_elements int,len_largest_element varchar(128));
sqlite>.mode csv memory
sqlite>.import memory.csv memory
将数据导入SQLite数据库后,可根据需要使用SQL语句进行分析,部分分析方式的示例如下。
查询内存中的key总数:
sqlite>select count(*) from memory;
查询内存占用总量:
sqlite>select sum(size_in_bytes) from memory;
查询内存占用量最高的10个key:
sqlite>select * from memory order by size_in_bytes desc limit 10;
查询元素数量1000以上的list:
sqlite>select * from memory where type='list' and num_elements > 1000;
通过分析后发现我们大量string的key占用7k-32k甚至64k的数据,结合线上业务和接口对key的使用。联想到是bitmap造成的,外加线上突然增加了一批业务数据大概几万条,和几万条数据均需要使用redis存储这些数据对应的点赞和收藏,这就是这次内存暴涨的原因。对于bitmap的使用和选择上需要慎重。
看下bitmap内存的消耗。
执行如下操作
SETBIT test 100 1
get test
显示:
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\b"
把key增加到100w
SETBIT test2 1000000 1
get test2
显示:
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\。。。"
限于篇幅就不写了,反正很大,明显位数越多越大,虽然bitmap用为来存储数据,但是如果在数据个数不多且位数较大时候不一定比hash更节省内存,单论100w位 hash可以{UID1000000: 1}一定会比bitmap节省内存,但是如果hash中字段数很多就不一样了,究竟如何需要业务上权衡结合具体业务场景分析。
关于redis的合理使用和内存策略问题下篇文章详细总计。就先写到这里了。
本文为Lokie.Wang原创文章,转载无需和我联系,但请注明来自lokie博客http://lokie.wang