关于redis:Redis系列之如何高效使用

29次阅读

共计 3992 个字符,预计需要花费 10 分钟才能阅读完成。

本文将从避免阻塞和内存节约两个方面介绍如和高效应用 Reids。

应用 Redis 时,咱们须要联合具体业务和 Redis 个性两方面来思考如何设计应用计划。须要两个从两个方面思考:

  1. 避免阻塞
  2. 节约内存

上面,咱们将就下面两个点开展阐明如何高效正当应用 Redis。

避免阻塞

阻塞 章节咱们晓得,引起 Redis 阻塞可能的起因有 内因 外因 两方面。

内因躲避

缩小简单命令的应用,或者有节制的应用 。上面这些命令能够看做简单命令(工夫复杂度为 O(N) 或者更高):SETRANGE, GETRANGE, MSET, MGET, MSETNX, HMSET, HMGET, HKEYS,HVALS, HGETALL, HSCAN, LTRIM, LINDEX, SMEMBERS, SUNION, SUNIONSTORE, SDIFF, SDIFFSTORE, ZUNIONSTORE, ZINTERSTORE, SINTER, SINTERSTORE。这些命令当操作的 key 或者 field 过多时将会导致 Redis 过程阻塞。
举例来说,对一个蕴含上十万甚至百万个 fieldhash执行 hgetall 操作,hgetall命令的工夫复杂度为 O(N),此时 N 页特地大(上十万甚至百万)必然耗时很长。
从这个例子,咱们能够发现至多两个不合理的中央:

  1. 这种有大量元素的数据不应该存在,因为,咱们并不能确定什么时候咱们对它执行了简单命令。
  2. 如果真的不可躲避超多元素的状况,在获取多个元素或者全量元素时,务必应用 scan 之类命令,且确保每次获取元素数量在肯定范畴,比方 50 等。

防止频繁生成 RDB 和 AOF 重写,尤其是高峰期。失常状况下,Redis 比拟时候缓存类型数据,当然为了保证数据不失落,能够进行导出 RDB 和从新 AOF。但须要确保一下几点:

  1. 不要执行 save 等同步命令;
  2. 尽量不要在高峰期进行长久化操作;
  3. 尽量在从实例上做长久化操作;

如果必须频繁长久化,须要确保如下几点:

  1. 保障 CPU、内存短缺,倡议 CPU 和内存留出肯定的 buffer
  2. 不要绑定 CPU
  3. 防止和 CPU 密集型服务混布
  4. 如果多个 Redis 实例部署在同一台机器,留神布局好系统资源,能够思考错峰长久化,防止同时长久化导致系统资源开销霎时突增
  5. 零碎尽量不要开启HugePage,避免复制内存页过大而拖慢执行工夫,且会导致长久化期间内存耗费增长

防止单 Redis 实例负载过高。Redis 是单线程服务,当负载过大必然影响整体性能,能够通过如下计划进步读写能力:

  1. 能够通过读写拆散,从实例承接局部读申请,来升高主实例压力;
  2. 如果读写压力都很大的话,须要思考集群计划。

外因躲避

通常,引起服务的外因无外乎 CPU、内存和网络,导致 Redis 阻塞的起因同样也须要从这几方面去思考。
CPU 竞争 导致 Redis 阻塞的问题起因在 阻塞 章节曾经具体介绍过,对于解决方案,能够通过以下伎俩来躲避:

  1. 过程 CPU 资源竞争,倡议不要和其余多线程 CPU 密集型服务混布,尤其是线上环境。另外,如果流量趋势有稳定的服务,比方有早晚顶峰,倡议不要把流量稳定统一的服务混布。
  2. 绑定 CPU,绑定 CPU(设置 CPU 亲和力 affinity)是为了升高 Redis 过程在不同 CPU 来回切换导致缓存命中率降落等引起的性能问题,然而,过程的 CPU 亲和力会继承给子过程,Redis 过程 fork 出的子过程也共享该 CPU。因而,如果须要频繁长久化的 Redis 不倡议绑定 CPU。

节约内存

系统优化

缩小内存碎片,失常的碎片率(mem_fragmentation_ratio)在 1.03 左右。然而当存储的数据长短差别较大时,以下场景容易呈现高内存碎片问题:

  1. 频繁做更新操作,例如频繁对已存在的键执行 appendsetrange 等更新操作。
  2. 大量过期键删除,键对象过期删除后,开释的空间无奈失去充分利用,导致碎片率回升。

呈现高内存碎片问题时常见的解决形式如下:

  1. 数据对齐:在条件容许的状况下尽量做数据对齐,比方数据尽量采纳数字类型或者固定长度字符串等,然而这要视具体的业务而定,有些场景无奈做到。
  2. 平安重启:重启节点能够做到内存碎片重新整理,因而能够利用高可用架构,如 Sentinel 或 Cluster,将碎片率过高的主节点转换为从节点,进行平安重启。

RDB 生成和 AOF 重写会 fork 子过程,进而导致内存耗费。总结如下:

  1. 失常状况下 Redis 产生的子过程并不需要耗费 1 倍的父过程内存,理论耗费依据期间写入命令量决定,然而仍然要预留出一些内存避免溢出。
  2. 须要设置 sysctl vm.overcommit_memory= 1 容许内核能够调配所有的物理内存,避免 Redis 过程执行 fork 时因零碎残余内存不足而失败。
  3. 排查以后零碎是否反对并开启 THP,如果开启倡议敞开,避免 copy-onwrite 期间内存适度耗费。

用户优化

减小键值字符串长度

  1. key 能够通过字符串缩减来缩小长度
  2. value 能够通过序列化和压缩来缩小存储,也能够能够通过业务侧优化缩小不必要的字段

尽量应用 set 而非append

因为字符串(SDS)存在预分配机制,日常开发中要小心预调配带来的内存节约。

表 -2 set & append 比照测试

操作 数据量 key 大小 value 大小 used_memory_human used_memory_rss_human mem_fragmentation_ratio 阐明
set 100w 20B 100B 176.66M 180.19M 1.02
set 100w 20B 200B 283.47M 287.66M 1.01
set && append 100w 20B 100B+100B 497.10M 503.19M 1.01 先 set,value 大小为 100B,随后 append 大小 100B 的数据

从下面的试验能够看出,同样存储 100w 条 key 大小为 20B,value 大小为 200B 的数据,通过 setappend操作实现的和间接应用 set 实现多了近75% 的存储耗费。

字符串重构

字符串重构:指不肯定把每份数据作为字符串整体存储,像 json 这样的数据能够应用 hash 构造,这样做有如下收益:

  1. 应用二级构造存储也能帮咱们节俭内存。
  2. 同时能够应用 hmget、hmset 命令反对字段的局部读取批改,而不必每次整体存取。

留神,这样样做的一个前提是 json key-value对中 value 绝对较小,上面是一个测试例子。

{
"id" : "12345678",
"title" : "redis-memory-optimization",
"chinese_url" : "http://www.redis.cn/topics/memory-optimization.html",
"english_url" : "https://redis.io/topics/memory-optimization"
}

代码 -2 一个 json 实例

表 -3 hash 优化测试

数据量 数据结构 编码 key value 配置 used_memory_human used_memory_rss_human mem_fragmentation_ratio 阐明
100w string raw 20B json 字符串 默认 252.95M 258.04M 1.02
100w hash hashtbale 20B key-value hash-max-ziplist-value 50 474.21M 484.27M 1.02
100w hash ziplist 20B key-value hash-max-ziplist-value 64 252.95M 258.09M 1.02

依据测试构造,hash-max-ziplist-value 50配置下应用 hash 类型,内存耗费岂但没有升高反而比字符串存储多出 2 倍,而调整 hash-max-ziplist-value 64 之后内存升高为 252.95M。因为 json 的 chinese_url 属性长度是 51,调整配置后 hash 类型外部编码方式变为 ziplist,相比字符串在内存应用上至多持平且反对属性的局部操作。
intset 编码:intset 编码是汇合(set)类型编码的一种,外部体现为存储有序、不反复的整数集。当汇合只蕴含整数且长度不超过 set-max-intset-entries 配置时被启用。

typedef struct intset {
uint32_t encoding;
uint32_t length;
int8_t contents[];} intset;

代码 -3 intset 构造

encoding:整数示意类型,依据汇合内最长整数值确定类型,整数类型划分为三种:int-16、int-32、int-64。intset 保留的整数类型依据长度划分,当保留的整数超出以后类型时,将会触发主动降级操作且降级后不再做回退。降级操作将会导致从新申请内存空间,把原有数据按转换类型后拷贝到新数组。
应用 intset 编码的汇合时,尽量放弃整数范畴统一,如都在 int-16 范畴内。避免个别大整数触发汇合降级操作,产生内存节约。

控制键的数量

通过在客户端预估键规模,把大量键分组映射到多个 hash 构造中升高键的数量。简略的说就是 复用 key 前缀

总结

内存是绝对贵重的资源,通过正当的优化能够无效地升高内存的使用量,内存优化的思路包含:

  1. 精简键值对大小,键值字面量精简,应用高效二进制序列化工具。
  2. 应用对象共享池优化小整数对象。
  3. 数据优先应用整数,比字符串类型更节俭空间。
  4. 优化字符串应用,防止预调配造成的内存节约。
  5. 应用 ziplist 压缩编码优化 hash、list 等构造,重视效率和空间的均衡。
  6. 应用 intset 编码优化整数汇合。
  7. 应用 ziplist 编码的 hash 构造升高小对象链规模。

reference

Redis 官网

Redis 开发与运维

How Twitter Uses Redis To Scale – 105TB RAM, 39MM QPS, 10,000+ Instances

Latency Numbers Every Programmer Should Know

正文完
 0