22 Redis 的常见问题 2

22 Redis 的常见问题 2


前言

11 除了 String 类型和 Hash 类型,还有什么类型适合保存图片吗?

还可以使用 Sorted Set 类型进行保存。Sorted Set 的元素有 member 值和 score 值,可以像 Hash 那样,使用二级编码进行保存。把图片 ID 的前 7 位作为 Sorted Set 的 key,把图片 ID 的后 3 位作为 member 值, 图片存储对象 ID 作为 score 值。

Sorted Set 中元素较少时,Redis 会使用压缩列表进行存储,可以节省内存空间。 和 Hash 不一样,Sorted Set 插入数据按 score 值的大小排序。当底层结构是压缩列表时,Sorted Set 的插入性能就比不上 Hash。Sorted Set 类型虽然可以用来保存,但并不是最优选项。

12 4 种典型的统计模式:聚合统计、排序统计、二值状态统计和基数统计,以及它们各自适合的集合类型。你还遇到过其他的统计场景吗?用的 是什么集合类型呢?

曾使用 List+Lua 统计最近 200 个客户的触达率。每个 List 元素表示一个客户,元素值为 0代表触达;元素值为 1代表未触达。在进行统计时应用程序会把代表客户的元素写入队列中。当需要统计触达率时,就使用 LRANGE key 0 -1 取出全部元素,计算 0 的比例,这个比例就是触达率。

这个例子需要获取全部元素,不过数据量只有 200 个,不算大,所以使用 List,在实际应用中也是可以接受的。但是如果数据量很大,又有其他查询需求的话(例如查询单个元素的触达情况),List 的操作复杂度较高,就不合适了,可以考虑使用 Hash 类型。

13 日常的实践过程中,还用过 Redis 的其他数据类型吗?

5 大基本数据类型,以及 HyperLogLog、Bitmap、GEO, Redis 还有一种数据类型叫作布隆过滤器。它的查询效率很高,经常会用在缓存场景中,可以用来判断数据是否存在缓存中。

14 用 Sorted Set 保存时间序列数据时,如果把时间戳作为 score,把实际的数据作为 member,这样保存数据有没有潜在的风险?另外,如果你是 Redis 的开发维护者,你会把聚合计算也设计为 Sorted Set 的一个内在功能吗?

Sorted Set 和 Set 一样,都会对集合中的元素进行去重,如果往集合中插入的 member 值,和之前已经存在的 member 值一样,原来 member 的 score 就会被新写入的 member 的 score 覆盖。相同 member 的值,在 Sorted Set 中只会保留一个。

对于时间序列数据来说,这种去重的特性是会带来数据丢失风险的。毕竟某一时间段内的多个时间序列数据的值可能是相同的。如果往 Sorted Set 中写入的数据是在不同时刻产生的,写入的时刻不同,Sorted Set 中只会保存一份最近时刻的数据。其他时刻的数据就都没有保存下来。

举个例子,在记录物联网设备的温度时,一个设备一个上午的温度值可能都是 26。在 Sorted Set 中把温度值作为 member,把时间戳作为 score。用 ZADD 命令把上午不同时刻的温度值写入 Sorted Set。由于 member 值一样,所以只会把 score 更新为最新时间戳,最后只有一个最新时间戳(例如上午 12 点)下的温度值。这肯定是无法满足保存多个时刻数据的需求的。

关于是否把聚合计算作为 Sorted Set 的内在功能,考虑到 Redis 的读写功能是由单线程执行,在进行数据读写时,本身就会消耗较多的 CPU 资源,如果再在 Sorted Set 中实现聚合计算,就会进一步增加 CPU 的资源消耗,影响到 Redis 的正常数据读取。如果我是 Redis 的开发维护者,除非对 Redis 的线程模型做修改,比如说在 Redis 中使用额外的线程池做聚合计算,否则我不会把聚合计算作为 Redis 的内在功能实现的。

#如果一个生产者发送给消息队列的消息,需要被多个消费者进行读取和处理(例 如,一个消息是一条从业务系统采集的数据,既要被消费者 1 读取并进行实时计算,也要 被消费者 2 读取并留存到分布式文件系统 HDFS 中,以便后续进行历史查询),你会使用 Redis 的什么数据类型来解决这个问题呢?

15 如果一个生产者发送给消息队列的消息,需要被多个消费者进行读取和处理(例如,一个消息是一条从业务系统采集的数据,既要被消费者 1 读取并进行实时计算,也要被消费者 2 读取并留存到分布式文件系统 HDFS 中,以便后续进行历史查询),你会使用 Redis 的什么数据类型来解决这个问题呢?

可以使用 Streams 数据类型的消费组,同时消费生产者的数据,这是可以的。但是如果只是使用一个消费组的话,消费组内的多个消费者在消费消息时是互斥的,在一个消费组内,一个消息只能被一个消费者消费。希望消息既要被消费者 1 读取,也要被消费者 2 读取,是一个多消费者的需求。 所以如果使用消费组模式,需要让消费者 1 和消费者 2 属于不同的消费组,这样它们就能同时消费了。

另外,Redis 基于字典和链表数据结构,实现了发布和订阅功能,这个功能可以实现一个消息被多个消费者消费使用,可以满足问题中的场景需求。

16 Redis 的写操作(例如 SET、HSET、SADD 等)是在关键路径上吗?

Redis 本身是内存数据库,写操作都需要在内存上完成执行后才能返回,如果这些写操作处理的是大数据集,例如 1 万个数据,主线程需要等这 1 万个数据都写完,才能继续执行后面的命令。所以说Redis 的写操作也是在关键路径上 的。

这个问题是把面向内存和面向磁盘的写操作区分开。当一个写操作需要把数据写到磁盘时,写操作只要把数据写到操作系统的内核缓冲区就行。如果执行了同步写操作,就必须要等到数据写回磁盘。所以面向磁盘的写操作一般不会在关键路径上。

根据写操作命令的返回值来决定是否在关键路径上,如果返回值是 OK, 或者客户端不关心是否写成功,此时的写操作就不算在关键路径上。 不过客户端经常会阻塞等待发送的命令返回结果,在上一个命令还没有返回结果前,客户端会一直等待,直到返回结果后,才会发送下一个命令。即使我们不关心返回结果,客户端也要等到写操作执行完成才行。所以在不关心写操作返回结果的场景下,可以对 Redis 客户端做异步改造。就是使用异步线程发送这些不关心返回结果的命令,而不是在 Redis 客户端中等待这些命令的结果。

17 在一台有两个 CPU Socket(每个 Socket 8 个物理核)的服务器上,部署了一个有着 8 个实例的 Redis 切片集群(8 个实例都为主节点,没有主备关系),现在有两个方案:

  1. 在同一个 CPU Socket 上运行 8 个实例,并和 8 个 CPU 核绑定;
  2. 在两个 CPU Socket 上各运行 4 个实例,并和相应 Socket 上的核绑定。

如果不考虑网络数据读取的影响,建议使用第二个方案,主要有两方面的原因:

  1. 同一个 CPU Socket 上的进程,会共享 L3 缓存。如果把 8 个实例都部署在同一个 Socket 上,它们会竞争 L3 缓存,会导致它们的 L3 缓存命中率降低,影响访问性能。
  2. 同一个 CPU Socket 上的进程,会使用同一个 Socket 上的内存空间。8 个实例共享同 一个 Socket 上的内存空间,肯定会竞争内存资源。如果有实例保存的数据量大,其他实例能用到的内存空间可能就不够了,其他实例就会跨 Socket 申请内存,进而造成跨 Socket 访问内存,造成实例的性能降低。

另外在切片集群中,不同实例间通过网络进行消息通信和数据迁移,并不会使用共享内存空间进行跨实例的数据访问。即使把不同的实例部署到不同的 Socket 上,它们之间也不会发生跨 Socket 内存的访问,不会受跨 Socket 内存访问的负面影响。

18 在 Redis 中还有哪些命令可以代替 KEYS 命令,实现对键值对的 key 的模糊查询呢?这些命令的复杂度会导致 Redis 变慢吗?

Redis 提供的 SCAN 命令,以及针对集合类型数据提供的 SSCAN、HSCAN 等,可以根据执行时设定的数量参数,返回指定数量的数据,避免像 KEYS 命令一样同时返回所有匹配的数据,不会导致 Redis 变慢。以 HSCAN 为例执行下面的命令,从 user 这个 Hash 集合中返回 key 前缀以 103 开头的 100 个键值对。

HSCAN user 0 match "103*" 100

19 你遇到过 Redis 变慢的情况吗?如果有的话,你是怎么解决的呢?

  1. 使用复杂度过高的命令或一次查询全量数据;
  2. 操作 bigkey;
  3. 大量 key 集中过期;
  4. 内存达到 maxmemory;
  5. 客户端使用短连接和 Redis 相连;
  6. 当 Redis 实例的数据量大时,无论是生成 RDB,还是 AOF 重写,都会导致 fork 耗时严重;
  7. AOF 的写回策略为 always,导致每个操作都要同步刷回磁盘;
  8. Redis 实例运行机器的内存不足,导致 swap 发生,Redis 需要到 swap 分区读取数据;
  9. 进程绑定 CPU 不合理;
  10. Redis 实例运行机器上开启了透明内存大页机制;
  11. 网卡压力过大。

20 使用 mem_fragmentation_ratio 来判断 Redis 当前的内存碎片率是否严重,给出的经验阈值都是大于 1 的。请你思考一下,如果 mem_fragmentation_ratio 小于 1,Redis 的内存使用是什么情况呢?会对 Redis 的性能和内存空间利用率造成什么影响呢?

如果 mem_fragmentation_ratio 小于 1,表明操作系统分配给 Redis 的内存空间已经小于 Redis 所申请的空间大小了,此时运行 Redis 实例的服务器上的内存已经不够用了,可能已经发生 swap 了。Redis 的读写性能也会受到影响,因为 Redis 实例需要在磁盘上的 swap 分区中读写数据,速度较慢。

21 在和 Redis 实例交互时,应用程序中使用的客户端需要使用缓冲区吗?如果使用的话,对 Redis 的性能和内存使用会有影响吗?

应用程序中使用的 Redis 客户端,需要把要发送的请求暂存在缓冲区。这有两方面的好处:

  1. 可以在客户端控制发送速率,避免把过多的请求一下子全部发到 Redis 实例,导致实例因压力过大而性能下降。不过客户端缓冲区不会太大,所以对 Redis 实例的内存使用没有什么影响。
  2. 在应用 Redis 主从集群时,主从节点进行故障切换是需要一定时间的,此时 主节点无法服务外来请求。如果客户端有缓冲区暂存请求,客户端仍然可以正常接收业务应用的请求,避免直接给应用返回无法服务的错误。

使用慢查询日志和 latency monitor 排查执行慢的操作

慢查询日志:

使用 Redis 日志(慢查询日志)和 latency monitor 来排查执行较慢的命令操作,Redis 的慢查询日志记录了执行时间超过一定阈值的命令操作。当发现 Redis 响应变慢、请求延迟增加时,可以在慢查询日志中进行查找,确定究竟是哪些命令执行时间很长。

在使用慢查询日志前,设置两个参数:

  • slowlog-log-slower-than:表示 慢查询日志对执行时间大于多少微秒的命令进行记录。
  • slowlog-max-len:表示慢查询日志最多能记录多少条命令记录。慢查询日志的底层实现是一个具有预定大小的先进先出队列,一旦记录的命令数量超过了队列长度,最先记录的命令操作就会被删除。这个值默认是 128。如果慢查询命令较多的话,日志里就存不下了;如果这个值太大了,又会占用一定的内存空间。所以一般设置为 1000 左右,这样既可以多记录些慢查询命令,方便排查,也可以避免内存开销。

设置好参数后,慢查询日志就会把执行时间超过 slowlog-log-slower-than 阈值的命令操作记录在日志中。

SLOWLOG GET 命令查看慢查询日志中记录的命令操作,执行如下命令,可以查看最近的一条慢查询的日志信息:

SLOWLOG GET 1 
1) 1) (integer) 33 //每条日志的唯一ID编号 
	2) (integer) 1600990583  //命令执行时的时间戳 
	3) (integer) 20906   //命令执行的时长,单位是微秒 
	4) 1) "keys"   //具体的执行命令和参数 
	   2) "abc*" 
	5) "127.0.0.1:54793"  //客户端的IP和端口号 
	6) ""   //客户端的名称,此处为空 

可以看到,KEYS "abc*"这条命令的执行时间是 20906 微秒,大约 20 毫秒,的确是一条执行较慢的命令操作。想查看更多的慢日志,把 SLOWLOG GET 后面的数字参数改为想查看的日志条数。

latency monitor 监控工具:
Redis 从 2.8.13 版本开始提供了 latency monitor 监控工具, 用来监控 Redis 运行过程中的峰值延迟情况。

使用 latency monitor,首先要设置命令执行时长的阈值。当一个命令的实际执行时长超过该阈值时,就会被 latency monitor 监控到。把 latency monitor 监控的命令执行时长阈值设为 1000 微秒,如下:

config set latency-monitor-threshold 1000

使用 latency latest 命令,查看最新和最大的超过阈值的延迟情况,如下:

latency latest 
1) 1) "command" 
	2) (integer) 1600991500  //命令执行的时间戳 
	3) (integer) 2500  //最近的超过阈值的延迟 
	4) (integer) 10100  //最大的超过阈值的延迟 

排查 Redis 的 bigkey

应用 Redis 要尽量避免 bigkey 的使用,因为 Redis 主线程在操作 bigkey 时会被阻塞。

Redis 可以在执行 redis-cli 命令时带上–bigkeys 选项,进而对整个数据库中的键值对大小 情况进行统计分析,比如说,统计每种数据类型的键值对个数以及平均大小。执行后,会输出每种数据类型中最大的 bigkey 的信息,对于 String 类型来说,会输出最大 bigkey 的字节长度,对于集合类型来说,会输出最大 bigkey 的元素个数,如下:

./redis-cli --bigkeys 
-------- summary ------- 
Sampled 32 keys in the keyspace! 
Total key length in bytes is 184 (avg len 5.75) 

//统计每种数据类型中元素个数最多的bigkey 
Biggest list found 'product1' has 8 items 
Biggest hash found 'dtemp' has 5 fields 
Biggest string found 'page2' has 28 bytes 
Biggest stream found 'mqstream' has 4 entries 
Biggest set found 'userid' has 5 members 
Biggest zset found 'device:temperature' has 6 members 

//统计每种数据类型的总键值个数,占所有键值个数的比例,以及平均大小 
4 lists with 15 items (12.50% of keys, avg size 3.75) 
5 hashs with 14 fields (15.62% of keys, avg size 2.80) 
10 strings with 68 bytes (31.25% of keys, avg size 6.80) 
1 streams with 4 entries (03.12% of keys, avg size 4.00) 
7 sets with 19 members (21.88% of keys, avg size 2.71) 
5 zsets with 17 members (15.62% of keys, avg size 3.40) 

这个工具是通过扫描数据库来查找 bigkey 的,在执行的过程中,会对 Redis 实例的性能产生影响。如果使用主从集群,建议在从节点上执行该命令。因为主节点上执行时,会阻塞主节点。如果没有从节点的两个建议:

  1. 在 Redis 实例业务压力的低峰阶段进行扫描查询,以免影响到实例的正常运行;
  2. 可以使用 -i 参数控制扫描间隔,避免长时间扫描降低 Redis 实例的性能。执行如下命令时,redis-cli 会每扫描 100 次暂停 100 毫秒(0.1 秒)。
./redis-cli --bigkeys -i 0.1

Redis 自带的–bigkeys 选项排查 bigkey,有两个不足的地方:

  1. 只能返回每种类型中最大的那个 bigkey,无法得到大小排在前 N 位的 bigkey;
  2. 对于集合类型来说,只统计集合元素个数的多少,而不是实际占用的内存量。 但是一个集合中的元素个数多,并不一定占用的内存就多。因为有可能每个元素占用的内存很小,即使元素个数有很多,总内存开销也不大。

如果想统计每个数据类型中占用内存最多的前 N 个 bigkey,可以自己开发一个程序来进行统计。 使用 SCAN 命令对数据库扫描,然后用 TYPE 命令获取返回的每一个 key 的类型。

对于 String 类型,可以直接使用 STRLEN 命令获取字符串的长度,也就是占用的内存空间字节数。

对于集合类型来说,有两种方法可以获得它占用的内存大小。

  1. 如果能够预先从业务层知道集合元素的平均大小,可以使用下面的命令获取集合元素的个数,然后乘以集合元素的平均大小,这样就能获得集合占用的内存大小了。
  • List 类型:LLEN 命令;
  • Hash 类型:HLEN 命令;
  • Set 类型:SCARD 命令;
  • Sorted Set 类型:ZCARD 命令;
  1. 如果你不能提前知道写入集合的元素大小,可以使用 MEMORY USAGE 命令(需要 Redis 4.0 及以上版本),查询一个键值对占用的内存空间。执行以下命令,获得 key 为 user:info 这个集合类型占用的内存空间大小。
MEMORY USAGE user:info 
(integer) 315663239 

这样就可以在开发的程序中,把每一种数据类型中的占用内存空间大小排在前 N 位的 key 统计出来,就是每个数据类型中的前 N 个 bigkey。

总结

提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海陆云

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值