Linux服务器 | 服务器模型与三个模块、两种并发模式:半同步/半异步、领导者/追随者

文章目录

  • 两种服务器模型及三个模块
    • C/S模型
    • P2P模型
    • I/O处理单元、逻辑单元、存储单元
  • 并发
    • 同步与异步
    • 半同步/半异步模式
      • 变体:半同步/半反应堆模式
      • 改进:高效的半同步/半异步模式
    • 领导者/追随者模式
      • 组件 :句柄集、线程集、事件处理器
      • 工作流程


两种服务器模型及三个模块

C/S模型

即常说的 客户端/服务器 模型,将资源(视频、文本、图片、软件等)提供者视作服务器,资源请求者视为客户端。

由于客户端连接请求(connect函数)是随机到达的异步事件,服务器需要使用某种 I/O模型 来监听这一事件。例如 I/O复用技术之一的 select系统调用:当监听到连接请求后,服务器就调用 accept函数 接收它,并分配一个 逻辑单元(新创建的子进程、子线程等) 管理这个新连接。

工作流程如下图所示:
在这里插入图片描述

服务器在处理一个客户请求的同时还要继续监听其他客户请求,否则就变成了效率低下的串行服务器了(必须先处理完前一个客户的请求,才能继续处理下一个客户请求)。这一点上图中是通过 select系统调用 实现的。

  • 优点: 实现简单、适合资源相对集中的场合。
  • 缺点: 服务器是通信的中心,当访问量过大时,可能所有客户都将得到很慢的响应。

P2P模型

为了解决 C/S模型 的缺点而诞生,P2P(Peer to Peer,点对点)模型C/S模型 更符合网络通信的实际情况。摒弃了以服务器为中心的格局,让网络上所有主机重新回归对等的地位。

  • 优点: 每台机器在消耗服务的同时给别人提供服务,这样资源能够充分、自由地共享。(P2P模型的典范:云计算机群)
  • 缺点: 用户之间传输的请求过多时,网络的负载将加重。

P2P模型的实现: 主机之间很难互相发现,所以实际使用时通常带有一个专门的发现服务器,其还提供查找服务(甚至还可以提供内容服务),使每个客户都能尽快地找到自己需要的资源。

在这里插入图片描述


I/O处理单元、逻辑单元、存储单元

可以将服务器解构为三个主要模块:
在这里插入图片描述

模块单个服务器程序服务器机群
I/O处理单元等待并接受新的客户连接、读写网络数据,将服务器响应数据返回给客户端。作为接入服务器,实现负载均衡,从所有逻辑服务器中选取负荷最小的一台来为新客户服务。
逻辑单元通常是一个进程或线程,处理客户数据并将结果传递给 I/O 处理单元或者直接发送给客户端(取决于事件处理模式)一台逻辑服务器
网络存储单元本地数据库、文件或缓存数据库服务器
请求队列各单元间的通信方式各服务器间的永久的、静态的TCP连接,避免了动态TCP连接导致的额外系统开销。

实际编程中,I/O处理单元常被称作主线程,逻辑单元常被称为工作线程。

值得注意的是:

  • 服务器通常拥有多个逻辑单元,以实现对多个客户任务的并行处理。
  • 网络存储单元不是必须的,如 sshtelnet等登陆服务就不需要这个单元。
  • 请求队列通产被实现为池的一部分,是各个单元之间的通信方式的抽象。

并发

缺点与优点:

  • 缺点: 如果程序是计算密集型,并发编程引起的任务切换反而使得效率降低。(任务切换耗时大于计算耗时)
  • 优点: 如果程序是I/O密集型,由于I/O操作耗时远大于CPU计算耗时,因此如果程序阻塞于I/O操作将浪费大量CPU时间,解决方法是:当前被I/O操作阻塞的执行线程可以主动放弃CPU(或由操作系统调度),将执行权转移到其他线程。此时并发引起的任务切换可以大大提高CPU利用率。

并发模式: I/O处理单元和多个逻辑单元之间协调完成任务的方法。

服务器主要有两种并发编程模式:半同步/半异步模式(half-sync/half-async)、领导者/追随者模式(Leader/Followers)。


同步与异步

  • I/O模型中,同步 or 异步 区分的是内核向应用程序通知的是 I/O就绪事件 or I/O完成事件,以及由 应用程序 还是 内核 来完成I/O读写。(详见两种高效事件处理模式一文)
  • 并发模式中,同步程序完全按照代码序列的顺序执行异步程序的执行需要由系统事件(中断、信号等)来驱动

在这里插入图片描述


半同步/半异步模式

按照同步/异步方式运行的线程被称为同步线程/异步线程

  • 同步线程: 效率低、实时性差,但逻辑简单。
  • 异步线程: 效率高、实时性强,但编程复杂且难于调试、扩展。

服务器同时使用同步线程和异步线程实现,即半同步/半异步模式。

工作流程:

  • 同步线程用于处理客户逻辑(类似于工作线程)、异步线程用于处理注册的I/O事件(类似于主线程)。
  • 异步线程监听到客户请求后,就将其封装成一个请求对象并插入请求队列中。
  • 请求队列通知某个同步模式的工作线程来读取并处理该请求对象。

在这里插入图片描述


变体:半同步/半反应堆模式

结合两种事件处理模式和几种I/O模型的话,半同步/半异步模式就存在多种变体,其中一种称为半同步/半反应堆half-sync/half-reactive)模式。

在这里插入图片描述

工作流程

  • 异步线程只有一个,由主线程充当,负责监听所有 socket 上的事件。
  • 如果 监听 socket 上有可读事件发生时(即有新的连接请求到来),主线程就接受新的 socket 连接,然后往 epoll 内核事件表中注册该 socket 上的读写事件。
  • 如果接受的 连接 socket 上有读写事件发生(上一步注册的),即 有新的客户请求到了 or 有数据要发送到客户端,主线程就将该 socket 连接 插入请求队列中。
  • 所有的工作线程都睡眠在请求队列上,当有任务到来时(就绪的 socket 连接被插入请求队列中,这说明半同步/半反应堆模式采用的事件处理模式是 Reactor 模式),所有空闲工作线程通过竞争(比如申请互斥锁)获取任务的接管权。

事件处理模式的选择

  • 采用 Reactor 模式意味着 工作线程 要负责 读写工作:既要 socket 上读取客户请求 ,还要 socket 写入服务器应答 。这也是名称中 半反应堆 的含义。
  • 当然,半同步/半反应堆模式也可以使用模拟的 Proactor 事件处理模式,即由主线程来完成数据的读写:
    1. 此时,主线程会将应用程序数据、任务类型等信息封装为一个任务对象,然后将任务对象(或者是指向该任务对象的一个指针)插入请求队列。
    2. 工作线程从请求队列中取得任务对象之后,即可直接处理客户请求,无须执行读写操作了。

缺点

  1. 主线程和工作线程共享请求队列。 主线程往请求队列中添加任务,或者工作线程从请求队列中取出任务,都需要对请求队列加锁保护,从而白白耗费CPU时间。
  2. 每个工作线程在同一时间只能处理一个客户请求。 如果客户数量较多,而工作线程较少,则请求队列中将堆积很多任务对象,客户端的响应速度越来越慢。如果增加工作线程,则又会耗费大量CPU时间。

总而言之,耗费CPU时间。


改进:高效的半同步/半异步模式

针对上面提到的第二个缺点,可以让每个工作线程都能同时处理多个客户连接:
在这里插入图片描述

工作流程:

  1. 主线程 只管理 监听 socket连接 socket工作线程 来负责:当有新的连接 socket 到来时,主线程就接受之并将其派发给某个工作线程,此后该新 socket 上的任何IO操作都由被选中的工作线程来处理,直到客户关闭连接。
  2. 主线程向工作线程派发 socket 的最简单的方式,是往它和工作线程之间的管道里写数据。工作线程检测到管道上有数据可读时,就分析是否是一个新的客户连接请求到来。如果是,则把该新 socket 上的读写事件注册到自己的 epoll 内核事件表中。(注册这件事本来是主线程在做)

PS: 事实上,每个线程(主线程和工作线程)都维持自己的事件循环,它们各自独立地监听不同的事件,每个线程都工作在异步模式,所以它并非严格意义上的半同步/半异步模式。


领导者/追随者模式

领导者/追随者模式是:多个工作线程轮流获得事件源集合,轮流监听、分发并处理事件的一种模式。

  • 领导者: 在任意时间点中,程序都只会有一个领导者线程,它负责进行I/O事件的监听
  • 追随者: 而其他的线程则为追随者线程,他们休眠在线程池中,等待成为新的领导者
  • 工作流程: 如果当前的领导者检测到I/O事件,首先要从线程池中推选出新的领导者线程等待新的I/O事件的到来,然后旧的领导者处理I/O事件,以此实现并发。

用通俗点的方法来讲就像是一群在营地中轮流放哨的哨兵,每次都会有一个人在值班,而其他人去休息。当值班者发现有什么特殊情况的时候就会去让领班叫醒一个哨兵来继续放哨,然后自己去探查情况。如果探查情况完后没人值班,则自己继续盯梢,否则就去休息。


组件 :句柄集、线程集、事件处理器

领导者/追随者模式包含的组件有:句柄集(HandleSet)、线程集(ThreadSet)、事件处理器(EventHandler),之间的关系如图所示:
在这里插入图片描述


句柄集

  • 句柄(Handle 用于表示I/O资源,在 Linux 下通常就是一个文件描述符。
  • 句柄集 其实就是句柄的监控管理集合,通过调用 wait_for_event 方法来监听这些句柄上的I/O事件,并将其中的就绪事件通知给领导者线程,而领导者线程则调用绑定Handle 上的事件处理器来处理事件。绑定是通过调用句柄集中的 register_handle 方法实现的。

线程集

线程集是所有工作线程(包括领导者和追随者)的管理者。它负责各线程之间的同步,以及新领导者线程的推选。

线程集中的线程在任意时间都必然处于下面三种状态之一:

  • 领导者(Leader): 线程处于领导者身份,负责监听句柄集上的I/O事件
  • 事件处理中(Processing): 线程正在处理事件。领导者检测到I/O事件后,转移到 Processing 状态进行事件的处理,并且调用 promote_new_leader 推选新的领导者。如果不想让出领导者的地位,也可以指定其他的追随者来处理事件(Event Handoff)。当处于 Processing 状态的线程处理完事件之后,如果当前线程集中没有领导者,他就会成为新的领导者,否则就直接变为追随者。
  • 追随者(Follower): 线程此时处于追随者身份,此时处于休眠状态,通过调用线程集的 join 等待被推选为新的领导者,也可能被当前的领导者指定处理新的任务。

状态转移图:
在这里插入图片描述

PS

领导者推选新的领导者追随者等待成为新的领导者 这两个操作都将修改线程集,因此线程集提供一个成员 Synchronizer 来同步这两个操作,以避免竟态条件。


事件处理器

  • 事件处理器通常包含一个或者多个回调函数 handle_event,用于处理事件对应的业务逻辑。
  • 事件处理器在使用前首先需要被绑定到某个句柄之上,每当该句柄上有事件发生的时候,领导者就执行与之绑定的事件处理器中的回调函数。
  • 具体的事件处理器需要重新实现基类的 handle_event 方法,以处理特定任务。

工作流程

在这里插入图片描述

PS

  • 由于领导者线程自己监听I/O事件并且处理客户请求,所以在本模式中不需要在线程之间传递任何额外的数据,也不需要像半同步/半反应堆模式那样在线程之间同步对请求队列的访问。(CPU耗时低
  • 但是也有一个明显的缺点就是只能支持一个事件源集合,因此无法像高效的半同步/半异步模式那样让每个工作线程独立地管理多个客户连接

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/443720.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

字符串匹配之KMP(KnuthMorrisPratt)算法(图解)

文章目录最长相等前后缀next数组概念代码实现图解GetNext中的回溯改进代码实现代码复杂度分析最长相等前后缀 给出一个字符串 ababa 前缀集合:{a, ab, aba, abab} 后缀集合:{a, ba, aba, baba} 相等前后缀 即上面用同样颜色标识出来的集合元素&#…

Android入门(一) | Android Studio的配置与使用

文章目录安装配置Android Studio使用Android Studio模拟器更改Android SDK的路径Hello World!安装配置Android Studio 从这一步开始: 一直点 next 即可,直到存储路径的选择上,可以放到非 C 盘,这里我放到 D 盘了&am…

Android 入门(四) | Intent 实现 Activity 切换

文章目录Intent显式 Intent定义两个 xml 文件android:orientationmatch_parent 和 wrap_contentIntent函数定义两个 Activity隐式 Intent更多隐式 Intent 的用法用隐式 Intent 打开系统浏览器自建 Activity 以响应打开网页的 Intent向下一个活动传递数据返回数据给上一个活动In…

Android入门(二) | 项目目录及主要文件作用分析

文章目录项目目录分析app目录分析AndroidManifest.xml 分析MainActivity.kt 分析build.gradle 分析最外层目录下的 build.gradleapp 目录下的 build.gradle项目目录分析 我们来看一下 src/main/res 下的一些文件: .gradle 和 .idea :这两个目录下放置…

Android入门(三) | Android 的日志工具 Logcat

文章目录日志工具类 android.util.LogLogcat 中的过滤器日志工具类 android.util.Log Log 从属日志工具类 android.util.Log ,该类提供了五个方法供我们打印日志: Log.v() :用于打印那些最为琐碎的、意义最小的日志信息。对应级别 verbose&…

Android入门(五) | Activity 的生命周期

文章目录Activity 的状态及生命周期实现管理生命周期FirstActivitySecondActivityDialogActivity运行结果旧活动被回收了还能返回吗?Activity 的状态及生命周期 Android 的应用程序运用 栈(Back Stack) 的思想来管理 Activity: …

Android入门(六) | Activity 的启动模式 及 生产环境中关于 Activity 的小技巧

文章目录Activity 的启动模式standardsingleTopsingleTasksingleInstance技巧了解当前界面是哪个 Activity随时随地退出程序启动活动的最佳写法Activity 的启动模式 standard:默认的启动方式,每次启动一个活动都会重新创建singleTop:如果该活…

Android入门(七) | 常用控件

文章目录TextView 控件:文本信息Button 控件:按钮EditText 控件:输入框ImageView 控件:图片ProgressBar 控件:进度条AlertDialog 控件:提示框ProgressDialog 控件:带有进度条的提示框TextView 控…

Android入门(八) | 常用的界面布局 及 自定义控件

文章目录LinearLayout :线性布局android:layout_gravity :控件的对齐方式android:layout_weight:权重RelativeLayout :相对布局相对于父布局进行定位相对于控件进行定位边缘对齐FrameLayout :帧布局Percent &#xff1…

Android入门(九)| 滚动控件 ListView 与 RecyclerView

文章目录ListView内置类型的简单运用定制数据类型提升效率点击事件RecyclerView布局管理器点击事件ListView 内置类型的简单运用 由于手机屏幕空间有限,能够一次性在屏幕上显示的内容不多,当我们的程序有大量数据需要显示的时候就可以借助 ListView 来…

Android入门(10)| Fragment碎片详解

文章目录为什么要使用碎片(Fragment)实例布局文件FragmentActivity动态添加碎片布局文件FragmentActivity碎片通信Fragment布局文件Activity生命周期为什么要使用碎片(Fragment) 我们在手机上看新闻可能是这样的: Re…

Android开发(1) | Fragment 的应用——新闻应用

文章目录Item&#xff1a;标题子项布局文件Java代码标题碎片布局文件Java代码新闻内容碎片布局文件Java代码新闻内容活动布局文件Java代码首界面布局文件Java代码Item&#xff1a;标题子项 布局文件 news_item.xml&#xff1a; <TextViewxmlns:android"http://schema…

Android入门(11)| 全局广播与本地广播

文章目录广播概念接收广播动态注册实例静态注册实例发送广播发送标准广播广播的跨进程特性发送有序广播本地广播广播概念 Android 中的每个应用程序都可以对自己感兴趣的广播进行注册&#xff0c;这样该程序就只会接收到自己所关心的广播内容&#xff0c;这些广播可能是来自系…

Android开发(2) | 广播 Broadcast 的应用——强制下线功能

文章目录功能简介关闭所有活动登陆界面发送强制下线的广播广播接收器AndroidManifest.xml运行结果功能简介 强制下线功能只需要弹出一个对话框&#xff0c;让用户只能点击确定按钮&#xff0c;回到登录界面。 如果在每一个活动中添加一个对话框的话太过繁琐&#xff0c;用广播…

Android入门(12)| 数据持久化

文章目录数据持久化文件存储将数据存储进文件实例从文件中读取数据实例SharedPreferences存储将数据存储进文件实例从文件中读取数据实例实现记住密码的功能SQLite数据库存储创建自己的帮助类调用自己的帮助类补全 onUpgrade() 方法增删查改增&#xff1a;SQLiteDatabase.inser…

Android入门(13)| Android权限 与 内容提供器

文章目录普通权限与危险权限运行时申请权限内容提供器运用安卓封装好的内容提供器自实现的内容提供器概念实现普通权限与危险权限 主要用于不同应用程序之间在保证被访数据的安全性的基础上&#xff0c;实现数据共享的功能。 在 Android 6.0 开始引入了运行时权限的功能&…

Android入门(14)| 通知

文章目录创建通知点击效果其它小功能实例创建通知 创建通知的步骤&#xff1a; 管理通知的 NotificationManager&#xff0c;通常通过当前 Context 的 getSystemService() 获取实例。它接受一个字符串参数用于确定获取系统的什么服务。Android 8.0(O) 版本后需要通知通道&…

Android开发(3) | 权限和内容提供器的应用——调用相机和相册

文章目录拍照并保存到 ImageView 控件布局文件 notice_layout.xml按钮 button_takePhoto 的点击操作隐式 Intent 启动后的回调AndroidManifest.xml从相册选取照片并在 ImageView 控件中显示布局文件 notice_layout.xml按钮 button_takePhoto 的点击操作自定义打开相册的方法 op…

Android开发(4) | 系统权限、MediaPlayer类 和 VideoView类 的应用——播放多媒体文件

文章目录MediaPlayer类播放音频的实例VideoView类播放视频的实例MediaPlayer类 对多种格式的音频文件提供了全面的控制方法&#xff1a; 如何获得MediaPlayer实例&#xff1f; 通过构造函数&#xff1a; MediaPlayer mp new MediaPlayer();调用 MediaPlayer.create() 方法&…

Android入门(15)| 网络

文章目录WebViewHTTP使用HttpURLConnection使用OkHttp封装网络操作封装HttpURLConnection封装OkHttpWebView WebView 可以在 应用程序中&#xff08;而不是浏览器&#xff09; 展示一些网页。 布局文件 web_layout.xml&#xff1a; <LinearLayoutxmlns:android"http…