大厂Redis性能优化指南

举报
JavaEdge 发表于 2021/06/04 00:38:48 2021/06/04
【摘要】 1 客户端优化 pipeline 批量操作 连接池应用 2 合理的淘汰机制 设置合理的内存大小设置合理的过期时间选择合适的淘汰策略 3 内存优化 https://redis.io/topics/memory-optimization 不同大小,底层数据结构不同: hash-max-ziplist-value 64 zset-max-ziplist-val...

1 客户端优化

pipeline 批量操作

连接池应用

2 合理的淘汰机制

  • 设置合理的内存大小
  • 设置合理的过期时间
  • 选择合适的淘汰策略

3 内存优化

https://redis.io/topics/memory-optimization
不同大小,底层数据结构不同:

hash-max-ziplist-value 64 
zset-max-ziplist-value 64

  
 
  • 1
  • 2

4 CPU优化

不要阻塞,特别是 lua 脚本,不要有长时间睡眠操作,不然其它操作全部阻塞!
谨慎使用范围操作
SLOWLOG get 10 默认10ms,默认只保留最后的128条

5 key设计

可读性和可管理性

以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id

简洁性

保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视

不要含特殊字符

比如:包含空格、换行、单双引号以及其他转义字符

6 value设计

  • 拒绝bigkey(防止网卡流量、慢查询), string类型控制在10KB以内, hash、 list. set. zset元素 个数不要超过5000
  • 选择适合的数据类型
  • 控制key的生命周期,redis不是垃圾桶

7 分区

多个业务系统,共用一个redis,还是应该分开。规划好key,特别是前缀,a.b.c.d

如果缓存数据变大了,就可以分区,但注意按业务垂直拆分,避免 key 的冲突。
一般小公司研发团队都是需要申请缓存资源直接得到对应的 ip 地址,但大公司最多只能得到key/token。

8 Big Key

那么什么样的Key才算是Big Key呢?
一般key的值大于10KB时可以算是Big Key了。
如下场景都有可能遇到Big Key,比如:

  • 粉丝列表
  • 统计数据,比如PV或者UV统计
  • 使用不当的数据缓存,比如通过String保存序列化后的用户数据等

出现了Big Key你也不用非常紧张,因为某些场景下不得不使用,而不应该是你的使用不当造成的。下面我们看一下如何进行发现与优化

查询 Big Key

可以使用脚本进行查询,大概思路就是使用 scan 游标查询 key,然后使用 memory usage key 获取这个 key 与 value 的字节数,这样就能很方便的得出结论进行优化。比如

import sys
import redis

def check_big_key(r, k):
  bigKey = False
  length = 0 try: type = r.type(k) if type == "string": length = r.strlen(k) elif type == "hash": length = r.hlen(k) elif type == "list": length = r.llen(k) elif type == "set": length = r.scard(k) elif type == "zset": length = r.zcard(k)
  except: return
  if length > 10240: bigKey = True
  if bigKey : print db,k,type,length

def find_big_key_normal(db_host, db_port, db_password, db_num):
  r = redis.StrictRedis(host=db_host, port=db_port, password=db_password, db=db_num)
  for k in r.scan_iter(count=1000): check_big_key(r, k)

def find_big_key_sharding(db_host, db_port, db_password, db_num, nodecount):
  r = redis.StrictRedis(host=db_host, port=db_port, password=db_password, db=db_num)
  cursor = 0
  for node in range(0, nodecount) : while True: iscan = r.execute_command("iscan",str(node), str(cursor), "count", "1000") for k in iscan[1]: check_big_key(r, k) cursor = iscan[0] print cursor, db, node, len(iscan[1]) if cursor == "0": break; if __name__ == '__main__':
  if len(sys.argv) != 4: print 'Usage: python ', sys.argv[0], ' host port password ' exit(1)
  db_host = sys.argv[1]
  db_port = sys.argv[2]
  db_password = sys.argv[3]
  r = redis.StrictRedis(host=db_host, port=int(db_port), password=db_password)
  nodecount = r.info()['nodecount']
  keyspace_info = r.info("keyspace")
  for db in keyspace_info: print 'check ', db, ' ', keyspace_info[db] if nodecount > 1: find_big_key_sharding(db_host, db_port, db_password, db.replace("db",""), nodecount) else: find_big_key_normal(db_host, db_port, db_password, db.replace("db", ""))

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

可以通过 python find_bigkey host 6379 password 来执行,默认大 key 的阈值为 10240(10KB),也就是对于 string 类型的 value 大于 10240 的认为是大 key,对于 list 的话如果 list 长度大于 10240 认为是大 key,对于 hash 的话如果 field 的数目大于 10240 认为是大 key。另外默认该脚本每次搜索 1000 个 key,对业务的影响比较低,不过最好在业务低峰期进行操作,避免 scan 命令对业务的影响。

删除 Big Key

Redis4.0 新特性 - Lazy Free
当删除键的时候, redis 提供异步延时释放 key 内存的功能,把 key 释放操作放在 bio(Background I/O) 单独的子线程处理中,减少删除 big key 对 redis 主线程的阻塞。有效地避免删除 big key 带来的性能和可用性问题。因此删除 Big Key 时使用 unlink 操作。

9 复制

9.1 避免全量复制

Redis 复制分为全量复制和部分复制,但全量复制的开销很大。如何尽量避免全量复制呢?

解决方案

  • 当某个 Replica 第一次去挂到 master 上时,无法避免要做一次全量复制,那又该如何尽量降低开销呢?
    既然第一次无法避免,那就选在集群低峰时间(凌晨)挂载 Replica
  • 选举Replica为主节点
    如果有某个Replica已经有了主节点数据了,那就将其选举为 master。通过人为或者哨兵实现选举。

复制积压缓冲区不足

master 生成 RDB 同步到 Replica,Replica 加载 RDB 这段时间里,master 的所有写命令都会保存到一个复制缓冲队列(如果主从之间网络抖动,进行部分复制也是执行该逻辑),待 Replica 加载完 RDB,拿 offset 值到该队列判断,若在该队列,则把该队列从 offset 到末尾全部同步过来,这个队列的默认值为 1M。若发现 offset 不在队列,就会发起全量复制。
所以增大复制缓冲区的配置 repl_backlog_size 默认是 1M,可以设置大一些,从而增大 offset 的命中率。那么如何估算该值呢?一般我们网络故障时间一般是min级别,所以如下公式:

理想值 = 根据当前QPS估算每分钟可写入多少字节 ✖️ 可能发生故障的min

  
 
  • 1

9.2 避免复制风暴

9.2.1 单主节点复制风暴

一般发生在 Master 挂在多个 Replica 场景
当 Master 重启恢复后,其 master 下所有 Replica 检测到 RunID 变化,导致所有 Replica 向 master 做全量复制。此时 Master 会为 Replica 创建 RDB 快照,若在快照创建完毕前,有多个 Replica 尝试与 Master 进行全量同步,那么其他的 Replica 将共享这份 RDB 快照。尽管 Redis 对这个问题做了优化,即只生成一份 RDB,但仍需多次传输,开销很大。同时给多个 Replica 发送快照,可能会使 Master 的网络带宽消耗严重,造成 Master 延迟变大,极端情况会出现主从断开,导致复制失败。

解决方案

  • 采用树状复制结构(一传二,二传四,分摊网络压力)
  • 当 Master 宕机,选举一台 Replica 晋升为 Master,减少全量复制的产生

9.2.2 单机多主复制风暴

一台机器,多个主节点(树状复制结构)。
因为Redis是单线程结构,所以发挥不了 CPU多核的优势,于是有的人为了省钱就在同一机器部署多个 redis 主节点了。

解决方案

  • 应当把主节点尽量分散在多台机器上,避免在单台机器上部署过多的主节点
  • 当主节点所在机器故障后提供故障恢复转移机制,避免机器恢复后进行密集的全量复制

禁止使用

使用经验

性能

  • 线程数(4~8)与连接数(redis 服务端 10000)
  • 监控系统读写比( 至少9:1)和缓存命中率

容量

  1. 做好容量评估,合理使用缓存资源

资源管理和分配

  • 尽量每个业务集群单独使用自己的Redis,不混用;

  • 控制Redis资源的申请与使用,规范环境和Key的管理(以一线互联网为例);

  • 监控CPU 100%(单线程),所以此时基本不响应了,需要优化高延迟操作。

文章来源: javaedge.blog.csdn.net,作者:JavaEdge.,版权归原作者所有,如需转载,请联系作者。

原文链接:javaedge.blog.csdn.net/article/details/113727088

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。