内存数据库之redis

1,752 阅读10分钟

性能测试不比功能测试,功能测试只要完成既定的测试用例,做好进度把控,测试通过即可,性能则不然。压测过程中的环境排障,问题定位分析及调优都需要较深的知识储备及经验。压测执行只是最基本的一项技能,优秀的性能测试工程师都是上百个系统的性能测试和多轮次的大型活动喂出来的,就像电商的双11和保险业的开门红等。前段时间结合实践,学习整理了JVM部分知识点,这次再深入浅出地了解下redis

redis学习分为4个模块

  • redis内存模型
  • redis持久化方案与选择
  • redis的高可用性
  • redis常见性能问题分析及优化

redis内存模型

了解redis内存模型可以帮助你做哪些事情? 
1、估算redis内存使用量,合理利用资源
2、优化数据类型,节约内存占用 
3、分析定位及优化redis问题

redis内存如何划分

  • 存储数据

       作为内存型数据库,数据是最主要的部分,存储数据的占用大小会体现在used_memory中。redis使用的key-value存储数据,包含了:字符串,哈希,列表,集合,有序集合这5中类型。当然,redis在存储对象时并不是直接将数据放到内存中,它会对对象进行包装,后文介绍。

  • redis进程运行所占用内存

       这部分内存占用很小,约有几兆,可以忽略不计,且不会统计在used_memory中。 

  • 缓冲内存

**      客户端缓存**指的是所有连接到redis的TCP连接的输入/输出缓存。输入缓存区用于临时保存客户端发来的命令,同时服务端从输入缓存区拉取执行,单个客户端缓存空间最大不超过1G,可以使用client list 命令查看。

**      复制积压缓**冲一个可复用且大小固定的缓冲区, 用于实现增量复制的功能,默认1MB,repl_backlog_size参数控制。缓冲区策略为FIFO,主节点有一个,从节点共享一个。主节点没有连接过从节点或者与从节点断开时,此缓冲区会释放。

**      AOF重写缓**冲区在AOF重写期间保存写入命令,等AOF重写完成后会将缓冲区的数据写入AOF文件。此缓冲区大小取决于AOF执行的时间以及AOF重写期间写命令的数量。

  • 内存碎片

       内存碎片主要是在redis分配及回收物理内存时候产生的。例如:一个5KB的的对象会存到8KB的内存块中,剩下3KB就变成了内存碎片,不可再分配。内存碎片不会统计在used_memory中,可以通过mem_fragmentation_ration查看内存碎片比率,值越大碎片比例越大。

redis中数据如何存储

涉及知识点:jemalloc内存分配器,简单动态字符串(SDS)、5种对象类型及内部编码、RedisObject

         

             图片来源:searchdatabase.techtarget.com.cn/7-20218/ 

dictEntry:one key one value,存储了指向key与value的指针,next指向了下一个dictEntry。 

key:存储在SDS结构中 RedisObject:value不是直接以字符串形式存储,而是存储在redisObject结构中。type是value的类型,ptr指向对象所在的地址。

** jemalloc**:上述存储结构都是由jemalloc分配内存进行存储,redis默认内存分配器,其他暂不介绍。jemalloc将内存空间划分为小、大、巨大三个类型,每个类型又划分了许多小的内存块单位。如图: 例如161字节的对象会存储在192字节的内存单元中

redisObject:RedisObject不仅仅有存储对象的功能,对象的类型,内部编码,内存回收,共享对象等功能都是RedisObject支持完成的。

RedisObject的定义:

type:对象类型,4bit。(type “key”查看)
encoding:对象内部编码,4bit(object encoding “key”查看)
lru:对象最后一次被访问的时间。(object idletime “key”)如果redis打开了maxmemory选项,同时内存回收算法是volatile/allkeys-lru,redis内存占用>maxmemory值时,闲置时间长的对象会被释放。 
refcount:记录对象被引用的次数。用于内存回收。当创建新对象时值为1,新程序使用时+1,后面不再被新程序使用-1,值为0时对象会被释放。 
ptr:指向具体的数据所在的SDS

一个RedisObject对象的大小为16字节。

Simple Dynamic String(SDS) 结构: 

len:数据已使用长度。 
free:数据未使用长度。 
buf:字节数组。

一个SDS结构占用空间为free+len+9字节。

redis的五种对象类型和内部编码

redis支持五种对象类型,每种类型又支持至少两种编码。

多种编码好处:

  • 接口与实现分离,需要增加或者改变编码方式时用户不收影响。
  • 根据不同的业务场景切换编码,提高redis效率。

五种字符串的详解与业务使用选择,掘金上已经有很多介绍,建议阅读官方文档《Redis设计与实现》

redis持久化方案与选择

话不多上也上官方文档,总结还是比较全的,自己就不在重复造车了。

redis高可用性

依旧官方文档,主从模式哨兵模式

重点来了!!!

redis常见性能问题分析及优化

主要从3个方面梳理下:内存,延迟,大key

内存

内存容量预估

曾经费劲巴脑地查询各种资料计算每个数据格式的存储结构所占用的内存大小,后来发现官网的Redis容量预估直接打脸。

内存使用:

通过info memory查看内存使用情况,关注参数:

used_memory:即Redis分配器分配的内存总量(单位是字节),等价于used_memory_human

sed_memory_rss:即Redis进程占据操作系统的内存(单位是字节),与top及ps命令看到的值是一致的。

前者是从Redis角度得到的量,后者是从操作系统角度得到的量

mem_fragmentation_ratio:内存碎片比率

used_memory_rss / used_memory >1,且该值越大,内存碎片比例越大。 <1,说明Redis使用了虚拟内存。

内存碎片率微大于1是可以的,这个值表示内存碎片率比较低,也说明redis没有发生内存交换。但如果内存碎片率超过2,那就说明Redis消耗了实际需要物理内存的200%,其中100%是内存碎片率。若是内存碎片率低于1的话,说明Redis内存分配超出了物理内存,操作系统正在进行内存交换(swap)

内存碎片比率较高如何解决?

  1. 重启Redis服务器(建议),但是重启又涉及到快照数据恢复的问题,请看redis持久化方案与选择部分
  2. 增加可用物理内存

如何缓解?

  1. 利用jemalloc特性进行优化。例:如果key的长度如果是8个字节,则SDS为17字节,jemalloc分配32字节;此时将key长度缩减为7个字节,则SDS为16字节,jemalloc分配16字节;则每个key所占用的空间都可以缩小一半。
  2. 使用合理的数据类型。如果是整型,Redis会使用int类型(8字节)存储来代替字符串,可以节省更多空间。
  3. 共享对象。利用共享对象,可以减少对象的创建(同时减少了RedisObject的创建),节省内存空间。可以通过调整REDIS_SHARED_INTEGERS参数提高共享对象的个数。

内存使用率

如果Redis实例的内存使用率超过可用最大内存,即 used_memory > 可用最大内存,那么操作系统开始进行内存与swap空间交换,把内存中旧的或不再使用的内容写入硬盘上,以便留出新的物理内存给新页或活动页使用。那么Redis和依赖Redis上数据的应用会受到严重的性能影响。

当开启并触发快照功能时,Redis会fork一个子进程把当前内存中的数据复制一份写入到硬盘上。因此若是当前使用内存超过可用内存的45%时触发快照功能,那么此时进行的内存可能会丢失数据。若在这个时候实例上有大量频繁的更新操作,问题会变得更加严重。减少Redis的内存占用率,来避免这样的问题,应该设置“maxmemory”值为系统可使用内存的45%,因为快照时需要一倍的内存来复制整个数据集,也就是说如果当前已使用45%,在快照期间会变成95%(45%+45%+5%),其中5%是预留给其他的开销。 如果没开启快照功能,maxmemory最高能设置为系统可用内存的95%。

延迟

当发现访问延迟突然增大时,可以通过Redis慢日志查看 

设置方法一   config命令设置: 

# 命令执行超过5毫秒记录慢日志 
CONFIG SET slowlog-log-slower-than 5000 
# 只保留最近1000条慢日志 
CONFIG SET slowlog-max-len 1000 

设置方法二    redis.conf文件设置: 

# 执行时间大于多少微秒(1秒 = 1,000,000 微秒)的查询进行记录 
slowlog-log-lower-than 1000 
#最多能保存多少条日志 
slowlog-max-len 1000

查看慢日志:

#查看慢日志
SLOWLOG GET  
#查看日志数量
SHOW LEN  
#清空日志
SLOWLOG RESET  

如果服务请求量并不大,但Redis实例的CPU使用率很高,很有可能是使用了复杂度高的命令导致的可以通过 info stats --> total_commands_processed可以查看

如果Redis在某个时间点突然出现一波延时,而且报慢的时间点很有规律,就需要考虑是否存在大量key集中过期的情况。根据业务特点合理设置key的过期时间 

  • volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰 (推荐)
  • volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰 
  • volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰 
  • allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰 (推荐)
  • allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰 
  • no-enviction(驱逐):禁止驱逐数据

Redis开启了自动生成RDB和AOF重写功能,那么有可能在后台生成RDB和AOF重写时导致Redis的访问延迟增大,而等这些任务执行完毕后,延迟情况消失。 遇到这种情况,一般就是执行生成RDB和AOF重写任务导致的。执行info命令,查看最后一次fork执行的耗时latest_fork_usec,单位微秒。

避免这种情况,我们需要规划好数据备份的周期,建议在从节点上执行备份,而且最好放在低峰期执行。使用Redis时建议部署在物理机上,降低fork的影响。 除了这个之外,如果开启AOF机制,设置的策略不合理,也会导致性能问题。

AOF为了保证文件写入磁盘的安全性,提供了3种刷盘机制:

  1. appendfsync always:每次写入都刷盘,对性能影响最大,占用磁盘IO比较高,数据安全性最高
  2. appendfsync everysec:1秒刷一次盘,对性能影响相对较小,节点宕机时最多丢失1秒的数据(推荐)
  3. appendfsync no:按照操作系统的机制刷盘,对性能影响最小,数据安全性低,节点宕机丢失数据取决于操作系统刷盘机制

大key

大key方面借用之前整理的一些知识点,还未实践,待有了优化经验之后再整理上来