redis 和 memcached 有啥区别?
redis 支持复杂的数据结构
redis 相比 memcached 来说,拥有更多的数据结构,能支持更丰富的数据操作。如果需要缓存能够支持更复杂的结构和操作, redis 会是不错的选择。
redis 原生支持集群模式
在 redis3.x 版本中,便能支持 cluster 模式,而 memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据。
redis 的网络线程模型:单Reactor模型
从Redis V1.0到V6.0版本之前,Redis的核心网络模型一直都是典型的单Reactor模型(单线程)
redis 内部使用文件事件处理器 file event handler,这个文件事件处理器是单线程的,所以 redis 才叫做单线程的模型。
文件事件处理器的结构包含 4 个部分:
- 多个 socket
- IO 多路复用程序
- 事件分派器
- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket,会将产生事件的 socket 放入队列中排队,事件分派器每次从队列中取出一个 socket,根据 socket 的事件类型交给对应的事件处理器进行处理。
来看客户端与 redis 的一次通信过程:
要明白,通信是通过 socket 来完成的。
首先,redis 服务端进程初始化的时候,会将 server socket 的 AE_READABLE 事件与连接应答处理器关联。
1、客户端 socket01 向 redis 的 server socket 请求建立连接,此时 server socket 会产生一个 AE_READABLE 事件,IO 多路复用程序监听到 server socket 产生的事件后,将该事件压入队列中。文件事件分派器从队列中获取该事件,交给连接应答处理器。连接应答处理器会创建一个能与客户端通信的 socket01,并将该 socket01 的 AE_READABLE 事件与命令请求处理器关联。
2、假设此时客户端发送了一个 set key value 请求,此时 redis 中的 socket01 会产生 AE_READABLE 事件,IO 多路复用程序将事件压入队列,此时事件分派器从队列中获取到该事件,由于前面 socket01 的 AE_READABLE 事件已经与命令请求处理器关联,因此事件分派器将事件交给命令请求处理器来处理。命令请求处理器读取 socket01 的 key value 并在自己内存中完成 key value 的设置。操作完成后,它会将 socket01 的 AE_WRITABLE 事件与命令回复处理器关联。
3、如果此时客户端准备好接收返回结果了,那么 redis 中的 socket01 会产生一个 AE_WRITABLE 事件,同样压入队列中,事件分派器找到相关联的命令回复处理器,由命令回复处理器对 socket01 输入本次操作的一个结果,比如 ok,之后解除 socket01 的 AE_WRITABLE 事件与命令回复处理器的关联。
这样便完成了一次通信。关于 Redis 的一次通信过程,推荐读者阅读《Redis 设计与实现——黄健宏》进行系统学习。
为什么Redis采用单线程的方式?
使用多线程的利与弊:
使用多线程可以利用到多核CPU的优势,可以提高CPU利用率
涉及到线程安全问题,涉及到锁竞争问题
线程的创建和销毁,线程的上下文切换,都是额外的开销
1、在我们的日常系统中,主要可以区分为两种:CPU 密集型 和 IO 密集型。
CPU 密集型:
线程的工作时段中大部分被CPU占据
这种系统就说明 CPU 的利用率很高,CPU处于忙碌中,增加线程也不会压榨出更多的CPU时间,而且增加线程还会导致线程的上下文切换而带来额外的开销,所以使用多线程效率可能会不升反降。
IO 密集型:
线程的工作时段中大部分被IO占据
IO 操作也可以分为磁盘 IO 和网络 IO 等操作。当线程进行IO操作时,CPU处于空闲状态,此时为了更多地压榨出CPU的时间,为了更好的利用CPU,开出多个线程,当A线程在等待IO的过程中,B线程可以利用CPU的时间片。
2、Redis的瓶颈并不在于CPU,而在于网络IO
比较通俗的解释一下就是说,Redis的文件事件处理器中,开启单个线程就足可以应对几万/秒的网络并发请求了,几万/秒的网络并发请求出现的socket事件,开一个线程去应对已足够。
其中最主要的原因在于IO多路复用,因为有IO多路复用机制,所以Redis的文件事件处理器的那个线程并不会在某个socket事件阻塞等待,而是通过监听机制,发现文件描述符可读或可写了之后去操作就可以了,而且所有操作都是在内存中进行,所以这个线程工作时段中大部分都被CPU占据。
当然,对于多核CPU操作系统而言,如果Redis的文件事件处理器的线程数=CPU核数,那么此时文件事件处理器可以处理更多的网络IO请求。
3、为啥 redis 单线程模型也能效率这么高?
- 纯内存操作。
- 核心是基于非阻塞的 IO 多路复用机制。
- C 语言实现,一般来说,C 语言实现的程序“距离”操作系统更近,执行速度相对会更快。
- 单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题。
4、如何实现Redis的多线程呢?
对于那些想利用多核优势提升性能的用户来说,Redis 官方给出的解决方案也非常简单粗暴:在同一个机器上多跑几个 Redis 实例。事实上,为了保证高可用,线上业务一般不太可能会是单机模式,更加常见的是利用 Redis 分布式集群多节点和数据分片负载均衡来提升性能和保证高可用。
Redis真的是单线程吗?
所谓的单线程,只是说 Redis 的处理客户端的网络请求(即执行命令)时,是单线程去执行的,并不是说整个 Redis 都是单线程。
V4.0之前:整个Redis都是单线程的
V4.0:引入多线程处理异步任务
V4.0 - V6.0:核心网络模型中是单线程的,整体是多线程的
V6.0:正式在网络模型中实现IO多线程
引入多线程处理异步任务:
Redis 在 v4.0 版本的时候就已经引入了的多线程来做一些异步操作,此举主要针对的是那些非常耗时的命令,通过将这些命令的执行进行异步化,避免阻塞单线程的事件循环。
我们知道 Redis 的 DEL 命令是用来删除掉一个或多个 key 储存的值,它是一个阻塞的命令,大多数情况下你要删除的 key 里存的值不会特别多,最多也就几十上百个对象,所以可以很快执行完,但是如果你要删的是一个超大的键值对,里面有几百万个对象,那么这条命令可能会阻塞至少好几秒,又因为事件循环是单线程的,所以会阻塞后面的其他事件,导致吞吐量下降。
于是,在 Redis v4.0 之后增加了一些的非阻塞命令如 UNLINK、FLUSHALL ASYNC、FLUSHDB ASYNC。
UNLINK 命令其实就是 DEL 的异步版本,它不会同步删除数据,而只是把 key 从 keyspace 中暂时移除掉,然后将任务添加到一个异步队列,最后由后台线程去删除,不过这里需要考虑一种情况是如果用 UNLINK 去删除一个很小的 key,用异步的方式去做反而开销更大,所以它会先计算一个开销的阀值,只有当这个值大于 64 才会使用异步的方式去删除 key,对于基本的数据类型如 List、Set、Hash 这些,阀值就是其中存储的对象数量。
Redis 多线程网络模型
前面提到 Redis 最初选择单线程网络模型的理由是:CPU 通常不会成为性能瓶颈,瓶颈往往是内存和网络,因此单线程足够了。那么为什么现在 Redis 又要引入多线程呢?很简单,就是 Redis 的网络 I/O 瓶颈已经越来越明显了。
参考: