Redis内存使用和大内存的排查方法

又是一个周末下班前发现线上接口报错,出错原因进过排查是使用了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博客
请先登录后发表评论
  • 最新评论
  • 总共0条评论
  • 本博客使用免费开源的 laravel-bjyblog v5.5.1.1 搭建 © 2014-2018 lokie.wang 版权所有 ICP证:沪ICP备18016993号
  • 联系邮箱:kitche1985@hotmail.com