关于redis:面试必备一线大厂Redis设计规范与性能优化

5次阅读

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

说在后面

你是否在应用 Redis 时,不分明 Redis 应该遵循的设计规范而苦恼?

你是否在 Redis 呈现性能问题时,不晓得该如何优化而发愁?

你是否被面试官拷问过 Redis 的设计规范和性能优化而答复不进去

别慌,看这篇文章就行了

本文,已收录于,我的技术网站 aijiangsir.com,有大厂残缺面经,工作技术,架构师成长之路,等教训分享

注释

一、Redis Key-Value 设计规范 & 性能优化

1. key 名设计规范

(1)【倡议】: 可读性和可管理性

以业务名 (或数据库名) 为前缀(避免 key 抵触),用冒号分隔,比方业务名: 表名:id

(2)【倡议】:简洁性

保障语义的前提下,管制 key 的长度,当 key 较多时,内存占用也不容忽视,例如:

(3)【强制】:不要蕴含特殊字符

反例:蕴含空格、换行、单双引号以及其余转义字符

2. Value 设计规范

(1)【强制】:回绝 bigkey(避免网卡流量、慢查问)

在 Redis 中,一个字符串最大 512MB,一个二级数据结构(例如 hash、list、set、zset)能够存储大概40 亿 个(2^32-1)个元素,但理论中如果上面两种状况,我就会认为它是 bigkey。

  1. 字符串类型:它的 big 体现在单个 value 值很大,个别认为超过 10KB 就是 bigkey。
  2. 非字符串类型:哈希、列表、汇合、有序汇合,它们的 big 体现在元素个数太多。

一般来说,string 类型管制在 10KB 以内;

hash、list、set、zset 元素个数不要超过 5000。

反例:一个蕴含 200 万个元素的 list。

3. bigkey 性能优化

bigkey 的危害:
  1. 导致 redis 阻塞
  2. 网络拥塞

bigkey 也就意味着每次获取要产生的网络流量较大;

假如一个 bigkey 为 1MB,客户端每秒访问量为 1000,那么每秒产生 1000MB 的流量,对于一般的千兆网卡 (依照字节算是 128MB/s) 的服务器来说几乎是灭顶之灾,而且个别服务器会采纳单机多实例的形式来部署,也就是说一个 bigkey 可能会对其余实例也造成影响,其结果不堪设想。

  1. 过期删除

有个 bigkey,它奉公守法(只执行简略的命令,例如 hget、lpop、zscore 等),但它设置了过期工夫,当它过期后,会被删除,如果没有应用 Redis 4.0 的过期异步删除(lazyfree-lazy- expire yes),就会存在阻塞 Redis 的可能性。

bigkey 的产生:

一般来说,bigkey 的产生都是因为程序设计不当,或者对于数据规模意料不分明造成的,来看几个例子:

  1.  社交类: 粉丝列表,如果某些明星或者大 v 不精心设计下,必是 bigkey。
  2. 统计类: 例如按天存储某项性能或者网站的用户汇合,除非没几个人用,否则必是 bigkey。
  3. 缓存类: 将数据从数据库 load 进去序列化放到 Redis 里,这个形式十分罕用,但有两个中央须要留神,第一,是不是有必要把所有字段都缓存;第二,有没有相干关联的数据,有的同学为了图不便把相干数据都存一个 key 下,产生 bigkey。
如何优化 bigkey

1、拆

  1. 如果是大 List(big list),那么就能够拆成多个 List:

比方拆成:list1、list2、…listN

  1. 如果是一个大的哈希表(big hash), 能够将数据分段存储:

比方一个大的 key,假如存了 1 百万的用户数据,能够拆分成 200 个 key,每个 key 上面寄存 5000 个用户数据

如果 bigkey 不可避免,也要思考一下要不要每次把所有元素都取出来(例如有时候仅仅须要 hmget,而不是 hgetall),删除也是一样,尽量应用优雅的形式来解决。

2、抉择适合的数据类型【举荐】

最好的优化计划其实是在设计阶段,所以咱们在应用 Redis 时,在设计阶段就应该尽量避免 bigkey,所以抉择适合的数据类型尤为重要。

例如:实体类型(要正当管制和应用数据结构,但也要留神节俭内存和性能质检的均衡)

谬误的做法:

1set user:1:name tom
2set user:1:age 19
3set user:1:favor football

正确的做法:

hmset user:1 name tom age 19 favor football

3、管制 key 的生命周期,redis 不是垃圾桶,当不须要应用的数据,及时过期清理【举荐】

倡议应用 Expire 设置过期工夫

条件容许能够打散过期工夫,避免几种过期

比方:设置 key 的过期工夫时,采纳固定过期工夫 + 肯定范畴内的随机数

二、Redis 命令的应用标准 & 性能优化

1、应用 O(N)类型的命令要留神关注 N 的数量【举荐】

比方 hgetall、lrange、smembers、zrange、sinter 等并非不能应用。

然而在应用的时候肯定要明确 N 的值,不然就可能因为查问数据太大导致 redis 阻塞。

倡议:有遍历的需要时能够应用 hscan、sscan、zscan 代替

2、生产环境禁用局部高危命令【举荐】

禁止线上应用 keys、flushall、flushdb 等,通过 redis 的 rename 机制禁掉命令。

当有须要扫描的须要时,倡议应用 scan 形式渐进式解决

3、正当应用 select【举荐】

redis 的多数据库较弱,应用数字进行辨别,很多客户端反对较差,同时多业务用多数据库理论还是单线程解决,会有烦扰

所以倡议 redis 应用数据库只用序号 0 的数据库即可,在 0 数据库里采纳 key 前缀辨别业务即可

4、应用批量操作提高效率【举荐】

当咱们要插入多个 key 时,能够采纳一些批量命令代替单个命令,进步查问效率,例如:

 1 原生命令:例如 mget、mset。2 非原生命令:能够应用 pipeline 提高效率。

但要留神管制一次批量操作的元素个数(例如 500 以内,理论也和元素字节数无关)。

留神两者不同:

11. 原生命令是原子操作,pipeline 是非原子操作。22. pipeline 能够打包不同的命令,原生命令做不到
33. pipeline 须要客户端和服务端同时反对。

5、redis 事务性能较弱,不倡议过多应用 redis 的事务命令

如果业务上有须要,能够应用 lua 代替【倡议】

三、客户端应用标准 & 性能优化

1、防止多个利用应用同一个 Redis 实例【举荐】

谬误的做法:

多个业务线专用同一个 redis 实例,比方订单、库存、权限都用同一个 redis 实例,只有有一块业务有阻塞,所有业务都会受影响。

正确的做法:

不相干的业务拆分为独立的 redis 实例,比方订单、库存、权限拆分为 3 个 redis 实例。

2、客户端连贯应用带有连接池的连贯,能够无效管制连贯,同时提高效率:

Jedis 应用连接池形式:

JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(5);
jedisPoolConfig.setMaxIdle(2);
jedisPoolConfig.setTestOnBorrow(true);

JedisPool jedisPool = new JedisPool(jedisPoolConfig, "192.168.0.60", 6379, 3000, null);
Jedis jedis = null;

应用连接池执行命令:

try {jedis = jedisPool.getResource();
    // 执行具体的命令
    jedis.executeCommand()} catch (Exception e) {logger.error("op key {} error:" + e.getMessage(), key, e);
} finally {
// 留神这里不是敞开连贯,在 JedisPool 模式下,Jedis 会被归还给资源池。if (jedis != null)
    jedis.close();}

3、连接池配置参数优化倡议

1、maxTotal 优化:最大连接数(晚期版本叫 maxActive)【倡议】

实际上最大连接数该如何优化,是一个很难答复的问题,思考的因素有很多:

比方:

  1. 业务心愿的 Redis 并发量
  2. 客户端执行命令工夫
  3. Redis 资源:例如 nodes(实例利用个数)* maxTotal 是不能超过 Redis 的最大连接数 maxClients
  4. 资源开销:例如尽管心愿管制闲暇连贯(连接池此刻可马上应用的连贯),然而又不心愿因为连接池的频繁开释、创立连贯造成不必要的开销。

以一个例子阐明,假如:

一次命令工夫(borrow|return resource + Jedis 执行命令(含网络))的均匀耗时约为 1ms,一个连贯的 QPS 大概是 1000

业务冀望的 QPS 是 50000

那么实践上须要的资源池大小是 50000 / 1000 = 50 个。但事实上这是个理论值,还要思考到要比理论值预留一些资源,通常来讲 maxTotal 能够比理论值大一些。

但这个值不是越大越好,一方面连贯太多占用客户端和服务端资源,另一方面对于 Redis 这种高 QPS 的服务器,一个大命令的阻塞即便设置再大资源池依然会杯水车薪。

2、maxldle 和 minldle 优化:(资源池容许最大闲暇连接数和资源池确保起码闲暇连接数)【倡议】

maxldle(最大闲暇连接数):

maxIdle 实际上才是业务须要的最大连接数,maxTotal 是为了给出余量,所以 maxIdle 不要设置过小,否则会有 new Jedis(新连贯)开销。

连接池的最佳性能是 maxTotal = maxIdle,这样就防止连接池伸缩带来的性能烦扰。然而如果并发量不大或者 maxTotal 设置过高,会导致不必要的连贯资源节约。个别举荐 maxIdle 能够设置为按下面的业务冀望 QPS 计算出来的实践连贯,maxTotal 能够再放大一倍。

minIdle(最小闲暇连接数):

minIdle 与其说是最小闲暇连接数,不如说是 ” 至多须要放弃的闲暇连接数 ”,在应用连贯的过程中,如果连接数超过了 minIdle,那么持续建设连贯,如果超过了 maxIdle,当超过的连贯执行完业务后会缓缓被移出连接池开释掉

所以最小闲暇连接数须要依据本人的业务规模和客户端规模自行评估配置

【倡议】:

如果你的零碎 QPS 很高,系统启动完马上就会有很多的申请过去,那么能够给 redis 连接池做预热,比方疾速的创立一些 redis 连贯,执行简略命令,相似 ping(),疾速的将连接池里的闲暇连贯晋升到 minIdle 的数量。

连接池预热示例代码:

List<Jedis> minIdleJedisList = new ArrayList<Jedis>(jedisPoolConfig.getMinIdle());
for (int i = 0; i < jedisPoolConfig.getMinIdle(); i++) {
    Jedis jedis = null;
    try {jedis = pool.getResource();
            minIdleJedisList.add(jedis);
            jedis.ping();} catch (Exception e) {logger.error(e.getMessage(), e);
        } finally {
        // 留神,这里不能马上 close 将连贯还回连接池,否则最初连接池里只会建设 1 个连贯。。//jedis.close();}
    }
    // 对立将预热的连贯还回连接池
for (int i = 0; i < jedisPoolConfig.getMinIdle(); i++) {
    Jedis jedis = null;
    try {jedis = minIdleJedisList.get(i);
        // 将连贯偿还回连接池
        jedis.close();} catch (Exception e) {logger.error(e.getMessage(), e);
      } finally {}}

总之,要依据理论零碎的 QPS 和调用 redis 客户端的规模整体评估每个节点所应用的连接池大小

3、【倡议】高并发下,倡议客户端增加熔断性能

(例如接入 sentinel、hystrix)

4、【举荐】设置正当的明码

有必要能够应用 SSL 加密拜访

5、【倡议】设置适合的缓存淘汰策略

LRU 算法(Least Recently Used,最近起码应用)

淘汰很久没被拜访过的数据,以最近一次拜访工夫作为参考。

LFU 算法(Least Frequently Used,最不常常应用)

淘汰最近一段时间被拜访次数起码的数据,以次数作为参考。

当存在热点数据时,LRU 的效率很好,但偶发性的、周期性的批量操作会导致 LRU 命中率急剧下降,缓存净化状况比较严重。这时应用 LFU 可能更好点。

依据本身业务类型,配置好 maxmemory-policy(默认是 noeviction),举荐应用 volatile-lru。如果不设置最大内存,当 Redis 内存超出物理内存限度时,内存的数据会开始和磁盘产生频繁的替换 (swap),会让 Redis 的性能急剧下降。

当 Redis 运行在主从模式时,只有主结点才会执行过期删除策略,而后把删除操作”del key”同

步到从结点删除数据。

四、零碎内核参数优化

1、vm.swapiness 配置,依据 linux 版本抉择配置(默认 0)

swap 对于操作系统来说比拟重要,当物理内存不足时,能够将一部分内存页进行 swap 到硬盘上,以解当务之急。

但世界上没有收费午餐,swap 空间由硬盘提供,对于须要高并发、高吞吐的利用来说,磁盘 IO 通常会成为零碎瓶颈。

在 Linux 中,并不是要等到所有物理内存都应用完才会应用到 swap,零碎参数 swppiness 会决定操作系统应用 swap 的偏向水平。swappiness 的取值范畴是 0~100,swappiness 的值越大,阐明操作系统可能应用 swap 的概率越高,swappiness 值越低,示意操作系统更加偏向于应用物理内存。

swappiness 的取值越大,阐明操作系统可能应用 swap 的概率越高,越低则越偏向于应用物理内存。

如果 linux 内核版本 <3.5,那么 swapiness 设置为 0,这样零碎宁愿 swap 也不会 oom kille(杀掉过程)

如果 linux 内核版本 >=3.5,那么 swapiness 设置为 1,这样零碎宁愿 swap 也不会 oom killer

个别须要保障 redis 不会被 kill 掉:

cat /proc/version #查看 linux 内核版本
echo 1 > /proc/sys/vm/swappiness
echo vm.swapiness=1 >> /etc/sysctl.conf

PS:OOM killer 机制是指 Linux 操作系统发现可用内存有余时,强制杀死一些用户过程(非内核过程),来保证系统有足够的可用内存进行调配。

2、vm.overcommit_memory 配置改为 1(默认 0)

0:示意内核将查看是否有足够的可用物理内存 (理论不肯定用满) 供给用过程应用;

  • 如果有足够的可用物理内存,内存申请容许;
  • 否则,内存申请失败,并把谬误返回给利用过程

1:示意内核容许调配所有的物理内存,而不论以后的内存状态如何;

如果是 0 的话,可能导致相似 fork 等操作执行失败,申请不到足够的内存空间

Redis 倡议把这个值设置为 1,就是为了让 fork 操作可能在低内存下也执行胜利。

cat /proc/sys/vm/overcommit_memory
echo "vm.overcommit_memory=1" >> /etc/sysctl.conf
sysctl vm.overcommit_memory=1

3、正当设置文件句柄数

操作系统过程试图关上一个文件(或者叫句柄),然而当初过程关上的句柄数曾经达到了下限,持续关上会报错:“Too many open files”

ulimit ‐a #查看系统文件句柄数,看 open files 那项
ulimit ‐n 65535 #设置系统文件句柄数

总结

本文梳理了在应用 Redis 过程须要遵循的一些最佳实际,包含针对架构维度的一些深刻性能优化的常识,如果面试官问你:” 说下在应用 Redis 的过程中,须要留神哪些标准?”,如果你依照本文的思路答复,必定能让面试官眼前一亮,offer 天然就到手了。

说在最初

求一键三连:点赞、分享、珍藏

点赞对我真的十分重要!在线求赞,加个关注我会非常感激!

最近无意间取得一份阿里大佬写的刷题笔记,一下子买通了我的任督二脉,进大厂原来没那么难。
这是大佬写的,7701 页的 BAT 大佬写的刷题笔记,让我 offer 拿到手软
本文,已收录于,我的技术网站 aijiangsir.com,有大厂残缺面经,工作技术,架构师成长之路,等教训分享

正文完
 0