关于Redis的一些问题
# 关于Redis的一些问题
# Redis单线程为什么还这么快
通常来说,使用多线程处理会比单线程快,就像有一堆一万斤的货物,如果选择“单线程”,那就只有一辆车运送,需要运送10次,如果是“多线程”,那就会有多辆车运行,比如:5辆车,那只需运送2次即可。
那为什么Redis选择单线程,处理数据的数据还这么快呢?或者说Redis为什么不选择多线程处理?
主要有以下几点原因:
# 1 内存访问
Redis 将所有数据存储在内存中,而不是磁盘上。内存访问速度远远高于磁盘访问速度,因此 Redis 能够实现非常高的读写速度,使Redis达到每秒万级别的处理类效率。
# 2 单线程避免了线程切换和竞态产生的消耗
因为是单线程,所以它就没有由于线程创建或销毁而产生的额外的CPU消耗
,减少了上下文切换带来的开销
,此外,单线程也不必考虑多线程中的锁的问题,更不用考虑锁对性能的影响;没有多线程并发问题,Redis 可以简化并发控制逻辑,从而减少额外的计算成本。而Java用多线程来提升性能是因为它的大多数操作都很复杂,消耗的时间远高于创建或销毁线程。
同时因为Redis的操作都比较简单,大多数操作可以在毫秒级甚至微秒级内完成。单线程模型在这种情况下能够充分利用这些简单操作的特点,避免多线程带来的复杂性。
# 3 非阻塞IO模型
还有一点就是,它虽然是单线程,但运行机制却是epoll作为I/O多路复用
,一次能执行多个任务,这也帮它实现了高性能读写。I/O 多路复用是一种技术,允许一个单一的进程同时监听多个文件。就像老师提问,它不是依次让每个同学回答,而是让同学们自己思考,想出来后,自行举手,老师看到有人举手后,再自行选择某个举手同学的回答。
redis在
4.0之前
是完全单线程,4.0时加入了多线程,但并不用于核心工作,只用于额外的后台处理。
6.0之前
是单线程。这是因为它的核心工作流程是单线程,即redis正常处理客户端请求的流程。通常如:接收命令,解析命令,执行命令,写回结果。多线程只用于额外的后台处理。redis6.0之后加入的多线程用于核心流程,即网络I/O阶段,用来处理 接收数据 和 返回结果 这两个流程,执行命令的核心模块还是单线程的
# 4 数据结构的优化
它的基本数据类型是String,list,hash,set,zset,但这5种数据结构对应的底层数据结构却经过精心设计,使得各种操作能够在内存中高效地执行。底层数据结构有:SDS
(Simple Dynamic String)(简单动态字符串)、 ziplist
(压缩列表)、双向链表(Doubly Linked List)、 intset
(整数集合)、 skiplist
(跳跃表)、 hashtable
(哈希表)、快速链表
(quicklist)...
而它之所以优化底层数据结构,还有一个原因:对于单线程,如果某个命令执行过长,会造成其他命令的阻塞,因此它必须优化。所以它是面向快速执行场景的数据库。
# 总结
最后总结下,redis虽然是单线程,但处理效率依旧快的原因主要有以下四点:
- 基于内存存储,执行快
- 优化的数据存储结构,使得执行更快
- 单线程架构,没有多线程带来的锁和上下文切换消耗
- 非阻塞IO:Redis 采用网络IO多路复用技术,来保证在多连接的时候系统的高吞吐量。可以让单个线程高效处理多个连接请求(尽量减少网络IO的时间消耗)
# 大key问题导致性能低
# 什么是大key
要解决问题,首先要弄明白,问题是什么?那大key是什么呢?
Redis中的一个key它大,能大到哪里去?其实这里的大key并非单只Redis中的key,其实指的是整个键值对,而且一般来说他指的是那个value,所以它其实应该是个大value。然后在有的地方其实也能看到,有的文章就直接把这种东西叫做大value。当然这名字没有那么重要,大家知道是这个问题就行了。不过,当然也有那种,只是key就特别大的情况。
以下是一些大key的标准:
在一般的业务场景下(并发和容量要求都不大):
单个string的 value>1MB
容器ds元素数量超过 10000
在高并发且低延迟的场景中:(互联网上看到比较多的版本)
- 单个string的 value>1BKB
- 容器ds元素数量超过 5000 或整体 value>10MB
当然大key的标准,并非是固定,而是要根据具体的业务来评估。不同的业务场景,可能有不同的标准。
# 它的影响
下面是它的影响
1.读取成本高
时延(执行命令时间更长)
带宽(大 key 消耗更多的带宽,从而影响相关服务)
2.大key的写操作容易阻塞,从而导致无法正常响应
慢查询
主从同步异常
3.占更多的存储空间,从而导致逐出,甚至是 OOM(Out Of Memory)
4.集群架构下,某个数据分片的内存使用率远超其他数据分片,即数据分片的内存资源不均衡
导致它发生的情况有很多,比如设计的数据不合理,或者程序异常。
常见的就比如说,你redis里面的list,然后来作为一个像消息队列一样的东西。一个机器在那边生产,往里面丢数据。那另外一边然后在pub在那边消费数据。如果这个时候消费侧代码发生了故障,然后这个key一直在里面只增不减,就有点像消息队列,你那个消息堆积了,那这个时候它的数量不断膨胀,那就会导致一个大key的问题。
# 解决方案
以下是一些解决方案,但仍存在一些问题。
- 进行数据优化,将key或者value中的一些不必要的数据进行删除
再然后就是进行“大key”的删除
直接删除大key会造成阻塞,因为redis是单线程执行,阻塞期间,其他所有请求可能都会超时。超时越来越多,会造成redis连接会耗尽,产生各种异常
低峰期删除:凌晨,观察qps,选择低的时候,无法彻底解决阻塞
分批次删除:对于hash,使用hscan扫描法,对于集合采用srandmember每次随机取数据进行删除。对于有序集合可以使用zremrangebyrank直接删除,对于列表直接pop即可。
异步删除法:用unlink代替del来删除,这样redis会将这个key放入到一个异步线程中,进行删除,这样不会阻塞主线程。
但,你要删除它,可能要先找到所谓的大key,你怎么去找呢?
以下是一些方法:
使用redis-cli命令客户端,连接Redis服务的时候,加上 --bigkeys 参数,可以以遍历的方式分 析Redis实例中的所有Key,并返回Key的整体统计信息与每个数据类型中Top1的大Key。
redis RDB tools. 这个是一个支持定制化分析的开源工具。我记得这个东西它应该是用python写的。对它是一个开源的工具,不是官方的。它可以用来分析redis的RDB文件。你这个东西得在配置里面开起来,这个是redis的一个持久化的方式之一。对它可以扫描出redis里面的大key,根据自己的精细化需求,然后你自己来去分析里面的key的内存占用情况。对它这个东西就相当于一个持久化的快照。你把快照导出来,然后你直接针对这个快照来进行一个内存分析。