• 《Redis核心技术与实战》学习总结(3)


    1 Redis的单线程认知

    一个基本事实

    我们通常说的Redis单线程,主要是指:Redis 6.0 之前版本的 网络I/O 和 键值对读写 是由一个线程来完成的。

    除了网络I/O  和 键值对读写 之外的其他功能,大多都是由额外的线程执行的。比如:持久化、异步删除、集群数据同步 等操作。

    Note:Redis 6.0之后对网络I/O改为使用多线程,但是,仍然使用单线程处理 键值对的读写操作。

    Why 单线程?

    因为 多线程开发会不可避免的带来并发控制的问题

    系统中通常会存在被多线程同时访问的共享资源,比如一个共享的数据结构。而当多个线程要修改这个共享资源的时候,为了保证共享资源的正确性,就需要有额外的机制进行保证。

    以Redis为例,它提供了List数据类型,我们可以拿它来做队列,入队(LPOP)和出队(LPUSH)就是两个基本操作。如果采用多线程设计,那么当两个线程同一时间一个操作LPOP一个操作LPUSH,为了保证队列长度的正确性,Redis就需要保证串行执行。而要保证串行执行,可能就需要一些额外的开销,比如我们常见的加锁。但在Redis的使用场景下,简单地加锁可能并不能得到理想的效果,会导致大部分线程在等待获取互斥锁,并行变串行,从而降低吞吐率。此外,多线程开发一般也还会引入同步源于来保护共享资源的并发访问,这也会降低系统代码的易调试性和可维护性。

    综上所述,为了避免这些问题,尽可能保证简单高效,Redis直接采用了单线程模式。

    2 Redis的单线程效率

    我们都知道,Redis公开出来的数据:Redis使用单线程也可以达到每秒10万级的处理能力。

    前提条件:在一定的服务器配置下才能达到。

    Why 这么高效?

    核心原因有两个:

    (1)Redis的大部分操作都在内存上完成 + 采用了高效的数据结构

    eg. 哈希表、跳表 等。如果你对数据结构还不熟悉,可以阅读 Redis学习总结(1)

    (2)Redis采用了多路复用机制,使其在网络I/O操作中能够并发处理大量的客户端请求,从而实现高吞吐率。

    图片

    其中,原因(2)是Redis单线程高效率的重点,它避免了accept() 和 send()/recv() 潜在的网络I/O操作的阻塞点

    如果不想了解细节,那么知道这几个核心的原因就够了。

    而要理解多路复用模型的优势,就得了解一下基本的IO模型。

    3 基本的IO模型

    在网络处理程序中,都会存在一些潜在的阻塞点,比如:常见服务端Socket程序中的accept() 和 recv() 函数。比如服务端监听到一个客户端有连接请求,但是一直没有能够成功建立连接,就会阻塞在accept()函数中,导致其他客户端无法和服务端建立连接,这就可能会导致服务端的线程阻塞。

    那么,有没有不阻塞的IO模型?

    别急,我们从阻塞IO模型看起。我们也不看什么原理,举例子-买火车票场景来理解。

    图片

    阻塞式IO模型

    老周去火车站买票,排队三天买到一张退票。
    开销:在车站吃喝拉撒睡 3天,其他事一件没干。

    非阻塞式IO模型

    老周去火车站买票,隔12小时去火车站问有没有退票,三天后买到一张票。

    开销:往返车站6次,路上6小时,其他时间做了好多事。

    IO多路复用模型

    对于IO多路复用,不同的操作系统平台有不同的系统调用实现,主要以select/poll 和 epoll 最为人知。

    (1)select/poll

    老周去火车站买票,委托黄牛,然后每隔6小时电话黄牛询问,黄牛三天内买到票,然后老李去火车站交钱领票。
    开销:往返车站2次,路上2小时,黄牛手续费100元,打电话17次。

    (2)epoll

    老周去火车站买票,委托黄牛,黄牛买到后即通知老李去领,然后老李去火车站交钱领票。
    开销:往返车站2次,路上2小时,黄牛手续费100元,无需打电话。

    信号驱动IO模型

    老周去火车站买票,给售票员留下电话,有票后,售票员电话通知老李,然后老李去火车站交钱领票。
    开销:往返车站2次,路上2小时,免黄牛费100元,无需打电话。

    异步IO模型

    老周去火车站买票,给售票员留下电话,有票后,售票员电话通知老李并快递送票上门。
    开销:往返车站1次,路上1小时,免黄牛费100元,无需打电话。

    Redis IO模型

    Redis在设计中基于Linux的IO多路复用机制实现了自己的IO模型,如下图所示:

    图片

    上图中的多个FD就是多个套接字(Socket),Redis的网络框架通过调用epoll让内核监听这些套接字。此时,Redis线程不会阻塞在某一个特定的监听 或 已连接的套接字上。因此,Redis可以同时和多个客户端连接并处理请求,从而提升并发性

    正如刚刚的例子中提到,黄牛买到票后会通知老周去领票,为了在请求到达时能够通知到Redis线程,epoll提供了基于事件的回调机制,即针对不同事件的发生,调用响应的处理函数。

    Note:比如 连接请求对应Accept事件,读取数据对应 Read事件。Redis会分别对这两个事件注册 accept 和 get 回调函数。

    这些事件会被放进一个事件队列,Redis单线程会对该队列不断地进行处理:如果Linux内核监听到有实际请求时,就会触发对应事件,然后Linux内核就会回调Redis对应的函数开始处理。

    因此,Redis不用一直阻塞等待是否有实际请求发生,避免CPU资源浪费,进而提高吞吐率。

    Note:IO 模型的演进,其实就是时代的变化,倒逼着操作系统将更多的功能加到自己的内核。

    4 总结

    本文总结了Redis单线程的几个核心要点:

    (1)Redis单线程的基本认知,即Redis只是对网络I/O和数据读写采用了单线程。

    (2)Redis为何要使用单线程,即Redis为了避免多线程开发中的并发控制问题。

    (3)Redis单线程为何很高效,即Redis使用了高性能的多路复用IO模型。

    参考资料

    极客时间,蒋德钧《Redis核心技术与实战》

    图片

  • 相关阅读:
    史上最完整的Android开发工具集合(转)
    史上最完整的Android开发工具集合(转)
    JSP取得绝对路径
    ExecutorService 的理解与使用
    JAVA多线程实现的三种方式 ()
    高并发策略实例分析
    spring framework体系结构及内部各模块jar之间的maven依赖关系
    js 去掉下划线,后首个字母变大写
    Cron表达式
    eclipse中怎么找项目部署的路径和找编译后的class路径
  • 原文地址:https://www.cnblogs.com/edisonchou/p/redis_study_notes_part3.html
Copyright © 2020-2023  润新知