YanranのblogYanranのblog
主页
知识学习
工具积累
其他
GitHub
主页
知识学习
工具积累
其他
GitHub
  • 知识学习

    • README
    • C++基础
    • C++基础入门
    • C++提高编程
    • C++核心编程
    • Git基础
    • Java基础
    • Linux基础
    • MFC
    • Qt
    • Thead
    • Vue
    • Web基础
    • 操作系统
    • 数据库
    • 数据结构
    • 计算机基础
    • 计算机编译
    • 计算机网络

操作系统

1. 进程/线程

  • 进程:资源分配的最小单位。
  • 线程:程序执行的最小单位。

1.1 PCB

  • PCB 是操作系统管理进程的核心数据结构,每一个进程(包括子进程)在内存中都对应一个 PCB,它相当于进程的 “身份证 + 档案”,记录了进程的所有关键信息。

  • PCB 中包含的核心信息

    1. 进程标识:进程 ID(PID),唯一标识一个进程(如父进程 ID(PPID)用于区分父子关系)。
    2. 状态信息:进程当前的状态(运行、就绪、阻塞等),操作系统根据状态调度进程。
    3. 内存信息:进程的内存地址空间(如代码段、数据段、堆、栈的起始和结束地址)。
    4. 资源信息:打开的文件描述符、占用的 I/O 设备、信号量等资源列表。
    5. CPU 上下文:进程切换时需要保存的 CPU 寄存器值(如程序计数器 PC、栈指针 SP),确保下次调度时能继续执行。
    6. 优先级:进程的调度优先级,决定 CPU 分配的优先级。
  • PCB 的作用

    1. 进程管理的 “唯一凭证”:操作系统通过 PCB 识别进程、调度进程、回收进程资源。
    2. 进程切换的 “快照”:当 CPU 从一个进程切换到另一个时,会将当前进程的 CPU 上下文保存到 PCB,再从目标进程的 PCB 中恢复上下文,实现 “无缝切换”。

1.2 TCB

  • TCB(Thread Control Block,线程控制块)是操作系统管理线程的核心数据结构,类似于进程的 PCB(Process Control Block),但专门用于记录线程的关键信息。线程作为进程内的执行单元,其状态、资源和调度依赖 TCB 进行管理。

  • TCB 中包含的关键信息(因为线程共享进程资源,无需记录全局资源信息)

    1. 线程标识信息:线程 ID(TID):系统分配的唯一标识符,用于区分同一进程内的不同线程;所属进程 ID(PID):关联到线程所属的进程(通过 PID 找到对应的进程 PCB),表明线程与进程的从属关系。

    2. 线程状态信息:当前状态:与进程类似,线程也有运行态、就绪态、阻塞态等(如等待锁、I/O 时进入阻塞态);优先级:线程的调度优先级(可能与进程优先级相关,但可独立设置)。

    3. CPU 上下文信息: 寄存器值:包括程序计数器(PC,记录下一条要执行的指令地址)、栈指针(SP,指向线程私有栈的栈顶)、通用寄存器等;线程切换时,这些信息会被保存到 TCB,再次调度时从 TCB 恢复,保证线程能继续执行(与 PCB 的 CPU 上下文作用相同,但线程上下文更轻量)。

    4. 线程私有资源:私有栈指针:线程拥有独立的栈(用于函数调用、局部变量存储),TCB 记录栈的地址范围;线程局部存储(TLS):线程私有的全局变量(如thread_local修饰的变量)的地址信息。

    5. 同步与通信信息:持有的锁 / 信号量:记录线程当前持有的互斥锁、条件变量等同步资源(用于死锁检测或资源回收);阻塞原因:若线程处于阻塞态,记录阻塞原因(如等待某个锁、等待 I/O 完成)。

1.3 PCB/TCB对比

对比维度TCB(线程控制块)PCB(进程控制块)关联逻辑
管理对象单个线程整个进程(包含所有线程)一个 PCB 对应多个 TCB(一个进程可创建多个线程),所有 TCB 通过 PID 关联到同一个 PCB。
资源记录仅记录线程私有信息(栈、寄存器、TID)记录进程全局共享资源(内存空间、文件描述符、设备权限)线程需要访问全局资源时(如读文件),通过 TCB 中的 PID 找到所属进程的 PCB,再从 PCB 中获取资源。
生命周期随线程创建而创建,随线程终止而销毁随进程创建而创建,随进程终止而销毁进程终止时,操作系统会先销毁所有线程的 TCB,再销毁进程的 PCB(回收全局资源)。

2. 多进程/多线程

  • 多进程:多个独立的进程同时执行,每个进程有自己的地址空间,互不干扰。

  • 多线程:在单个进程内同时执行多个线程,线程是进程的子集,多个线程共享进程的资源。

  • 资源管理:

    • 多线程:线程间共享内存和文件描述符,通信相对简单,但同步复杂。
    • 多进程:进程间数据是分开的,使用进程间通信(IPC)来共享数据,通信相对复杂,但同步简单。
  • 优缺点:

    • 多线程:执行效率高,创建和切换开销小,但一个线程崩溃可能导致整个进程崩溃。
    • 多进程:更健壮,一个进程崩溃不会影响其他进程,但资源消耗大,切换开销高。
  • 线程共享内存空间;进程的内存是独立的;

  • 同一个进程的线程之间可以直接交流;两个进程想通信,必须通过一个中间代理来实现

700

3. 进程间通信

  • 进程间通信(IPC,Inter-Process Communication)是操作系统中多个进程交换数据、协同工作的核心机制。不同的 IPC 方式适用于不同的场景,其设计原理、性能和适用范围差异较大。
  • 进程是操作系统分配资源的基本单位,且具有 “地址空间隔离” 特性(一个进程的内存、数据,其他进程默认看不到)
  • 进程通信就是专门解决 “进程间需要传递数据、指令或状态” 的机制 —— 本质是打破隔离,实现信息交换。
  • 进程通信的典型场景:
    1. 播放器进程(如 PotPlayer)向字幕进程(如 SubtitleEdit)传递 “当前播放时间戳”;
    2. 终端进程(如 CMD)向后台服务进程(如 MySQL)传递 “查询 SQL 指令”;
    3. 手机微信进程向系统通知进程传递 “新消息内容”。
  • 常见进程间通信方式及原理如下

3.1 管道

  • 管道(Pipeline)
  • 原理:管道是一种特殊的文件(存在于文件系统中),进程通过读写该文件实现通信。本质是内核中的一段环形缓冲区,内核会维护一个缓冲区,通过 “读 / 写” 操作实现进程间字节流传递,本质是 “文件描述符” 的抽象。
  • 分类
    1. 匿名管道(Pipe):仅用于父子进程或亲缘进程(如 fork 创建的子进程),生命周期随进程结束而销毁,无文件名,通过pipe()系统调用创建。
    2. 命名管道(FIFO):可用于非亲缘进程,通过文件系统中的路径(如/tmp/myfifo)标识,生命周期独立于进程,通过mkfifo()创建,读写方式与文件一致。
  • 实际应用
    1. 匿名管道:Shell 中的管道命令(如ls -l | grep .txt),父进程(ls)写入数据,子进程(grep)读取,实现命令协作。
    2. 命名管道:本地服务间的简单通信,如日志收集(应用进程写入日志到 FIFO,日志进程从 FIFO 读取并存储)。
    3. 典型场景:轻量、单向、低延迟的本地进程通信,无需跨网络。

3.2 信号

  • 信号(Signal)
  • 原理:操作系统向进程发送的异步事件通知(类似 “软件中断”),可携带极少信息(仅信号编号),用于触发进程预设的处理函数。主要用于通知进程某些事件的发生,常见的信号有 SIGINT、SIGTERM 等。
  • 举例
    1. 常见信号:SIGINT(Ctrl+C 终止)、SIGKILL(强制杀死进程)、SIGCHLD(子进程退出通知)。
    2. 进程通过signal()或sigaction()注册信号处理函数。
  • 实际应用
    1. 异常处理:进程崩溃时通过SIGSEGV(段错误)捕获并记录日志(如 Linux 的 core dump)。
    2. 进程控制:父进程通过SIGTERM优雅终止子进程(子进程收到后释放资源再退出)。
    3. 典型场景:简单的事件通知(如进程退出、超时提醒),不适合传递复杂数据。

3.3 消息队列

  • 消息队列(Message Queue)
  • 消息队列是为了克服信号传递信息量少,管道只能承载无格式字节流以及缓冲区大小受限等问题而设计的。
  • 消息队列允许进程以消息的形式发送和接收数据,每条消息都有一个类型标识,这使得消息队列比管道更灵活,可以使用 msgget、msgsnd 和msgrcv 等系统调用来操作消息队列。
  • 原理:内核维护的消息链表,进程可按 “类型” 发送 / 接收消息(消息包含类型和数据),支持异步通信,无需进程同步等待。
  • 使用方法
    1. 发送方通过msgsnd()将消息放入队列,接收方通过msgrcv()按类型提取消息(可过滤特定类型)。
    2. 消息有大小限制(通常几 KB),队列总容量有限制。
  • 实际应用
    1. 分布式任务调度:如公司内部的任务分配系统(调度进程向队列发送任务, Worker 进程按类型领取任务)。
    2. 异步通知:电商订单系统中,支付进程完成后向消息队列发送 “支付成功” 消息,物流进程监听并处理。
  • 局限性:不适合高频、大数据量场景(性能低于共享内存),目前逐步被分布式消息中间件(如 RabbitMQ)替代。

3.4 共享内存

  • 共享内存(Shared Memory)

  • 原理:多个进程将同一块物理内存映射到各自的虚拟地址空间,直接读写内存实现通信,是速度最快的 IPC 方式(无需内核中转数据)。

  • 共享内存通过 shmget、shmat 和 shmdt 等系统调用进行管理。进程可以创建或附加到一个共享内存段,然后对该内存段进行读写操作。

  • 同步机制:由于多个进程可以同时访问同一块内存,需要依靠某种同步机制来保证数据的一致性和避免竞争条件。共享内存非常高效,但需要小心处理同步和互斥问题,否则可能导致数据不一致或竞争条件。

  • 高速通信:由于数据直接存取,共享内存的速度非常快,适合需要大量数据交换的场景。

  • 使用方式

    1. 需配合同步机制(如信号量)防止并发读写冲突。
    2. 通过shmget()(创建)、shmat()(映射到进程)等系统调用实现。
  • 实际应用

    1. 高频实时数据交换:金融交易系统(如股票行情推送,行情服务器将实时价格写入共享内存,多个交易进程直接读取,延迟 < 1ms)。
    2. 大型数据共享:视频处理软件(如 Adobe Premiere,多个滤镜进程共享同一视频帧数据,避免数据拷贝)。
    3. 典型公司:高频交易公司(如 Jump Trading)、实时渲染引擎(如 Unity 引擎的多进程渲染)。

3.5 信号量

  • 信号量(Semaphore)
  • 原理:内核维护的计数器,用于控制多个进程对共享资源的访问(同步 / 互斥),本身不传递数据,仅用于 “权限控制”。
  • 用法
    1. P 操作:计数器 - 1,若计数器 < 0 则阻塞进程(等待资源)。
    2. V 操作:计数器 + 1,若有进程阻塞则唤醒一个。
    3. 常用于保护共享内存、消息队列等资源的并发访问。
  • 实际应用
    1. 共享资源互斥:多进程读写同一数据库文件时,通过信号量保证 “同一时间只有一个进程写入”。
    2. 生产者 - 消费者模型:如电商库存系统(生产者进程增加库存,消费者进程减少库存,信号量控制库存不为负)。

3.6 套接字

  • 套接字(Socket)
  • 原理:基于网络协议栈的通信机制,支持同一主机或跨网络的进程通信,通过 “IP 地址 + 端口” 标识进程,支持 TCP(可靠流)和 UDP(不可靠报)协议。
  • 套接字提供了一种标准化的通信机制,允许不同主机上的进程进行通信,可以使用 socket、bind、listen、accept、connect 等系统调用来操作套接字。套接字支持多种通信协议,如 TCP 和 UDP 。
  • 套接字是网络编程的基础,广泛应用于客户端-服务器模型的应用程序中。
  • 使用方式
    1. 本地套接字(Unix Domain Socket):用于同一主机进程,通过文件系统路径标识(如/var/run/mysocket),性能优于网络套接字。
    2. 网络套接字:用于跨主机通信,如互联网服务。
  • 实际应用
    1. 跨主机通信:所有互联网服务(如浏览器与 Web 服务器通过 TCP 通信,即时通讯工具如微信用 UDP 传输语音)。
    2. 本地高可靠通信:数据库客户端与服务端(如 MySQL 客户端通过本地 Socket 连接服务器,避免网络开销)。
    3. 典型公司:腾讯(微信消息传输)、阿里(分布式服务间调用)、谷歌(跨数据中心通信)。

3.7 适用场景

场景主流方式核心技术 / 库典型业务
本地轻量通信命名管道(FIFO)、本地 Socketmkfifo()、socket(AF_UNIX)、Qt 的QLocalSocket日志收集、桌面应用插件通信
高频实时数据交换共享内存 + 信号量System V 共享内存、boost::interprocess金融行情、实时渲染
跨网络 / 跨主机通信网络 Socket(TCP/UDP)BSD Socket、Boost.Asio、Qt 的QTcpSocket互联网服务、分布式系统
异步消息传递消息队列(分布式中间件)RabbitMQ、Kafka(基于 Socket 封装)电商订单、日志异步处理
简单事件通知信号(Signal)sigaction()、Qt 的QProcess::errorOccurred

3.8 IPC-C++原生及标准库

  • 管道 / 命名管道:通过pipe()(匿名)、mkfifo()(命名)+ open()/read()/write()系统调用,适合轻量通信。
  • 共享内存:shmget()/shmat()(System V)或mmap()(POSIX),配合sem_init()(信号量)同步,适合高性能场景。
  • Socket:通过 BSD Socket API(socket()/connect()/send())实现,跨平台需处理 Windows/Linux 差异。
  • 第三方库:Boost.Interprocess(封装共享内存、消息队列)、Boost.Asio(跨平台 Socket 通信),解决原生 API 的跨平台问题。

3.9 Qt中IPC封装

IPC 方式Qt 类 / 方法适用场景
父子进程管道通信QProcess主进程启动子进程并传递命令 / 数据(如 IDE 调用编译器)。
本地 Socket 通信QLocalServer/QLocalSocket同一主机非亲缘进程(如桌面应用与后台服务)。
网络 Socket 通信QTcpServer/QTcpSocket、QUdpSocket跨主机通信(如客户端 - 服务器应用)。
共享内存QSharedMemory高频数据共享(如多窗口应用共享大型缓存)。
信号量同步QSemaphore配合共享内存控制并发访问。

4. 进程间共享资源

  • “资源” 是操作系统提供的可利用对象(如内存块、文件、设备、端口等)。“进程间共享资源” 指的是 “多个进程不需要交换数据,而是需要共同访问同一个资源”—— 本质是复用资源,避免重复创建 / 浪费,或 “协同操作同一个对象”。

  • 共享资源” 的典型场景

    1. 多个浏览器进程(如 Chrome 的多标签页进程)共享同一个 “系统字体文件”(不需要传递数据,只是都要读字体来渲染页面);
    2. 多个办公软件进程(如 Word、Excel)共享同一个 “打印机设备”(不需要传递数据,只是都要使用打印机打印);
    3. 多个后端服务进程(如 Nginx 的 worker 进程)共享同一个 “监听端口(如 80 端口)”(不需要传递数据,只是都要接收客户端请求)。
  • 共享内存既是 “共享资源”,也能用于 “进程通信”,但需明确:

    • 当多个进程只是读共享内存里的配置数据(如系统全局配置),不传递新数据时:这是 “共享资源”(目的是复用配置,无信息交换);
    • 当进程 A 往共享内存写 “数据 X”,进程 B 读 “数据 X” 并回复 “数据 Y” 时:这是 “进程通信”(目的是交换 X 和 Y,共享内存只是 “通信的载体”)。
    • 简言之:共享内存是 “资源”,用它做什么(传数据 / 复用)才决定了属于 “通信” 还是 “共享资源”—— 二者的核心区别仍在 “目的”,而非 “载体”。
  • 所有共享资源技术的本质是 “让多进程无需重复创建资源,直接复用同一对象”,按资源类型可分为 4 大类:

    1. 文件类共享资源:复用磁盘 / 文件系统中的对象
    2. 设备类共享资源:复用硬件 / 系统设备
    3. 内存类共享资源:复用物理内存块 / 共享内存
    4. 其他系统级共享资源:复用系统统一管理的对象

[!NOTE] 通信是 “传东西”,共享资源是 “用东西”

5. 线程间通信

  • 同一进程的线程共享地址空间,没有通信的必要,但要做好同步/互斥,保护共享的全局变量。
  • 线程间的通信目的主要是用于线程同步,由于线程共享进程内存,线程间通信的核心目的不是 “传递数据”(数据可直接共享),线程没有像进程通信中的用于数据交换的通信机制,而是用于线程同步,核心是传递 “状态 / 事件”(如 “任务已完成”“数据已就绪”),实现线程间的协作。

5.1 共享内存

  • 速度快,线程之间直接访问共享内存区域,无需中介。支持大数据量的传输,因为内存空间可以非常大。
  • 需要额外的同步机制(如互斥锁、读写锁等)来避免多个线程同时访问同一内存区域,从而导致数据不一致,如果没有恰当的同步,可能会出现数据竞争或死锁问题。

5.2 共享变量

  • 共享变量是最简单的线程间通信方式之一。它可以通过多个线程共享相同的内存空间来实现。当一个线程修改了共享变量时,其他线程也能看到这个变化。这种方式的好处是实现简单,但是同时也带来了一些问题。由于多个线程都可以访问和修改同一个变量,可能会导致数据的不一致性,需要使用互斥锁等机制来保证线程安全。

5.3 锁机制

  • 互斥锁是一种保护共享资源的机制。它可以确保在任何时候只有一个线程可以访问共享资源。当一个线程获得了互斥锁,其他线程就必须等待直到这个线程释放锁为止。互斥锁是保证线程安全的一种重要方式。在多线程的环境中,共享资源可能会被多个线程同时访问和修改,如果没有互斥锁的保护,就可能会导致数据的不一致性或者其他问题。
  • 读写锁是可以同时读,但是只能单个线程写

5.4 条件变量

  • 条件变量是一种线程间通信的高级方式。它可以使得一个线程在等待某个条件变为真时暂停执行,并在另一个线程中满足条件时恢复执行。条件变量通常与互斥锁一起使用,因为等待条件变量时需要先释放互斥锁。
  • 可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
  • 例如,在一个生产者和消费者模型中,生产者线程生产了一些数据,但是消费者线程尚未准备好接收这些数据。生产者线程可以使用条件变量来等待消费者线程准备好接收数据。当消费者线程准备好时,它可以使用条件变量来通知生产者线程可以继续生产数据了。条件变量还可以用于其他场景,比如线程池、任务队列等。

5.5 信号量

  • 通过 “计数器” 实现线程间的同步通信(如控制同时运行的线程数量、传递 “资源可用” 信号)。
  • 例:用信号量控制 “生产者 - 消费者” 模型,生产者生产后 “增加计数”,消费者消费前 “等待计数 > 0”。

5.6 消息队列

  • 线程之间的通信是异步的,发送线程不需要等待接收线程处理完消息,可以继续执行其他任务。

  • 支持多对多的通信模式,多个线程可以将消息放入队列,多个线程可以从队列中读取消息。

  • 需要管理消息队列的同步问题,以避免消息丢失或重复。

  • 例子:一个线程将处理任务放入消息队列,另一个线程从消息队列中取出任务并处理,适用于任务调度场景。

5.7 管道

  • 管道是一种单向通信机制。它可以将一个进程的输出连接到另一个进程的输入,从而实现进程间通信。管道可以用于多线程编程中的进程间通信,也可以用于不同机器之间的通信。
  • 例如,在一个多线程的web服务器中,可以使用管道将请求处理线程的输出连接到响应处理线程的输入。这可以避免线程之间的数据竞争,提高系统的性能。

6. 线程间共享资源

  • 线程间共享资源:天然的内存共享(最核心的特性)

  • 线程是进程内的执行单元,同一进程的所有线程共享该进程的全部内存资源(包括全局变量、堆内存、静态变量等),这是线程与进程(地址空间隔离)最本质的区别。

  • 线程间共享的资源类型(默认即可共享,无需额外机制)

    1. 全局变量 / 静态变量:进程内的全局内存区由所有线程共享(如 int g_count;、static int s_data;);
    2. 堆内存:通过 new/malloc 分配的堆内存,所有线程都可访问(如 int* ptr = new int[10];,任意线程都能通过 ptr 操作);
    3. 文件描述符 / 句柄:进程打开的文件、网络连接等句柄,所有线程共享(如一个线程打开的文件,其他线程可直接读写);
    4. 代码段 / 常量区:进程的代码和常量(如字符串常量)由所有线程共享。
  • 共享的优势:线程间共享数据无需像进程那样 “显式传递”,直接通过内存访问,效率极高(无数据拷贝)

  • 共享的问题:多线程并发读写共享资源时,会导致数据竞争(Data Race)(如两个线程同时修改同一变量,结果不可预测),因此必须配合互斥锁(如 std::mutex) 等同步机制保护。

7. 分布式

  • 集群是指在几个服务器上部署相同的应用程序来分担客户端的请求。它是同一个系统部署在不同的服务器上,比如一个登陆系统部署在不同的服务器上。(类似于多个人一起做同样的事)集群主要的使用场景是为了分担请求的压力。但是,当压力进一步增大的时候,可能在需要存储的部分,比如mysql无法面对大量的“写压力”。因为在mysql做成集群之后,主要的写压力还是在master的机器上,其他slave机器无法分担写压力,这时,就引出了“分布式”。

  • 分布式是指多个系统协同合作完成一个特定任务的系统。它是不同的系统部署在不同的服务器上,服务器之间相互调用。(类似于多个人一起做不同的事)分布式是解决中心化管理的问题,把所有的任务叠加到一个节点处理,太慢了。所以把一个大问题拆分为多个小问题,并分别解决,最终协同合作。分布式的主要工作是分解任务,把职能拆解。

  • 分布式的主要应用场景是单台机器已经无法满足这种性能的要求,必须要融合多个节点,并且节点之间的相关部分是有交互的。相当于在写mysql的时候,每个节点存储部分数据(分库分表),这就是分布式存储的由来。存储一些非结构化数据:静态文件、图片、pdf、小视频 ... 这些也是分布式文件系统的由来。

  • 分布式系统是若干独立计算机的集合,这计算机对用户来说就像单个相关系统。也就是说分布式系统背后是由一系列的计算机组成的,但用户感知不到背后的逻辑,就像访问单个计算机一样。

  • 在分布式系统中: 1、应用可以按业务类型拆分成多个应用,再按结构分成接口层、服务层;我们也可以按访问入口分,如移动端、PC端等定义不同的接口应用; 2、数据库可以按业务类型拆分成多个实例,还可以对单表进行分库分表; 3、增加分布式缓存、搜索、文件、消息队列、非关系型数据库等中间件;

  • 很明显,分布式系统可以解决集中式不便扩展的弊端,我们可以很方便的在任何一个环节扩展应用,就算一个应用出现问题也不会影响到别的应用。

  • 集群是个物理形态,分布式是个工作方式。

  • 分布式计算系统具有如下特点:

    1. 资源共享:分布式系统可以共享硬件、软件或数据
    2. 并行处理:多台机器可以同时处理同一功能
    3. 支持扩展:当扩充到其他计算机时,计算和处理能力可以按需进行扩展
    4. 错误检测:可以更轻松地检测故障
    5. 公开透明:节点可以访问系统中的其他节点并与之通信
  • 分布式系统分为分布式计算(computation)与分布式存储(storage)。计算与存储是相辅相成的,计算需要数据,要么来自实时数据(流数据),要么来自存储的数据;而计算的结果也是需要存储的。在操作系统中,对计算与存储有非常详尽的讨论,分布式系统只不过将这些理论推广到多个节点罢了。将任务分片(MapReduce),每个节点存一部分数据(Partition)。

  • 单个节点的故障(进程crash、断电、磁盘损坏)是个小概率事件,但整个系统的故障率会随节点的增加而指数级增加,网络通信也可能出现断网、高延迟的情况。在这种一定会出现的“异常”情况下,分布式系统还是需要继续稳定的对外提供服务,即需要较强的容错性。最简单的办法,就是冗余或者复制集(Replication),即多个节点负责同一个任务。

  • 分布式系统挑战

    1. 异构的机器与网络
    2. 普遍的节点故障
    3. 不可靠的网络

7.1 案例

假设这是一个对外提供服务的大型分布式系统,用户连接到系统,做一些操作,产生一些需要存储的数据,这是一个复杂的过程

用户使用Web、APP、SDK,通过HTTP、TCP连接到系统。在分布式系统中,为了高并发、高可用,一般都是多个节点提供相同的服务。那么,第一个问题就是具体选择哪个节点来提供服务,这个就是负载均衡(load balance)。只要涉及到多个节点提供同质的服务,就需要负载均衡。

通过负载均衡找到一个节点,接下来就是真正处理用户的请求,请求有可能简单,也有可能很复杂。简单的请求,比如读取数据,那么很可能是有缓存的,即分布式缓存,如果缓存没有命中,那么需要去数据库拉取数据。对于复杂的请求,可能会调用到系统中其他的服务。

假设服务A需要调用服务B的服务,首先两个节点需要通信,网络通信都是建立在TCP/IP协议的基础上。但是,每个应用都手写socket是一件冗杂、低效的事情,因此需要应用层的封装,因此有了HTTP、FTP等各种应用层协议。

当系统愈加复杂,提供大量的http接口也是一件困难的事情。因此,有了更进一步的抽象,那就是RPC(remote produce call),是的远程调用就跟本地过程调用一样方便,屏蔽了网络通信等诸多细节,增加新的接口也更加方便。

一个请求可能包含诸多操作,即在服务A上做一些操作,然后在服务B上做另一些操作。比如简化版的网络购物,在订单服务上发货,在账户服务上扣款。这两个操作需要保证原子性,要么都成功,要么都不操作。这就涉及到分布式事务的问题,分布式事务是从应用层面保证一致性:某种守恒关系。

上面说道一个请求包含多个操作,其实就是涉及到多个服务,分布式系统中有大量的服务,每个服务又是多个节点组成。那么一个服务怎么找到另一个服务(的某个节点呢)?通信是需要地址的,怎么获取这个地址,最简单的办法就是配置文件写死,或者写入到数据库。但这些方法在节点数据巨大、节点动态增删的时候都不大方便,这个时候就需要服务注册与发现:提供服务的节点向一个协调中心注册自己的地址,使用服务的节点去协调中心拉取地址。

从上可以看见,协调中心提供了中心化的服务:以一组节点提供类似单点的服务,使用非常广泛,比如命令服务、分布式锁。协调中心最出名的就是chubby,zookeeper。

回到用户请求这个点,请求操作会产生一些数据、日志,通常为信息,其他一些系统可能会对这些消息感兴趣。比如个性化推荐、监控等,这里就抽象出了两个概念,消息的生产者与消费者。那么生产者怎么将消息发送给消费者呢,RPC并不是一个很好的选择,因为RPC肯定得指定消息发给谁。

但实际的情况是生产者并不清楚、也不关心谁会消费这个消息,这个时候消息队列就出马了。简单来说,生产者只用往消息队列里面发就行了,队列会将消息按主题(topic)分发给关注这个主题的消费者。消息队列起到了异步处理、应用解耦的作用。

上面提到,用户操作会产生一些数据,这些数据忠实记录了用户的操作习惯、喜好,是各行各业最宝贵的财富。比如各种推荐、广告投放、自动识别。这就催生了分布式计算平台,比如Hadoop,Storm等,用来处理这些海量的数据。

最后,用户的操作完成之后,用户的数据需要持久化,但数据量很大,大到按个节点无法存储。那么这个时候就需要分布式存储:将数据进行划分放在不同的节点上,同时,为了防止数据的丢失,每一份数据会保存多分。

传统的关系型数据库是单点存储,为了在应用层透明的情况下分库分表,会引用额外的代理层。而对于NoSql,一般天然支持分布式。

7.2 组件

  • 负载均衡: Nginx:高性能、高并发的web服务器;功能包括负载均衡、反向代理、静态内容缓存、访问控制;工作在应用层 LVS:Linux virtual server,基于集群技术和Linux操作系统实现一个高性能、高可用的服务器;工作在网络层
  • webserver: Java:Tomcat,Apache,Jboss Python:gunicorn、uwsgi、twisted、webpy、tornado
  • service: SOA、微服务、spring boot,django
  • 容器: docker,kubernetes
  • cache: memcache、redis等
  • 协调中心: zookeeper、etcd等 zookeeper使用了Paxos协议Paxos是强一致性,高可用的去中心化分布式。zookeeper的使用场景非常广泛,之后细讲。
  • rpc框架: grpc、dubbo、brpc dubbo是阿里开源的Java语言开发的高性能RPC框架,在阿里系的诸多架构中,都使用了dubbo + spring boot
  • 消息队列: kafka、rabbitMQ、rocketMQ、QSP 消息队列的应用场景:异步处理、应用解耦、流量削锋和消息通讯
  • 实时数据平台: storm、akka
  • 离线数据平台: hadoop、spark PS: apark、akka、kafka都是scala语言写的,看到这个语言还是很牛逼的
  • dbproxy: cobar也是阿里开源的,在阿里系中使用也非常广泛,是关系型数据库的sharding + replica 代理
  • db: mysql、oracle、MongoDB、HBase
  • 搜索: elasticsearch、solr
  • 日志: rsyslog、elk、flume

7.3 CAP

  • 分布式系统有 3 个核心需求,但任何情况下都只能满足其中 2 个,无法三者兼顾,这是分布式的 “铁律”。3 个需求分别是:

  • C(Consistency,一致性):所有机器的同一份数据,必须同时一样(比如库存改了,所有服务器立刻看到新库存);

  • A(Availability,可用性):只要不是所有机器都坏了,系统就能正常干活(比如用户下单,不管哪台服务器处理,都能成功);

  • P(Partition Tolerance,分区容错性):网络断了(比如北京的服务器和上海的服务器连不上),系统也能继续工作(北京的用户还能下单,上海的用户也能下单)。

  • 分析:比如网络断了(必须满足 P):如果要保证 C(一致性),就必须让两边服务器暂停干活(等网络恢复再同步数据),但这样 A(可用性)就没了;如果要保证 A(可用性),两边服务器继续干活,数据就会不一致(C 没了)—— 所以只能选 “CP” 或 “AP”。

7.4 分布式锁

  • 控制分布式系统中 “多个机器同时操作同一份数据” 的锁

  • 单机系统里,用 “本地锁”(比如 Java 的 synchronized)就能控制多线程抢资源,但分布式系统里是 “多台机器”(不是同一台机器的线程),本地锁管不了其他机器 —— 这时候就需要 “分布式锁”,让多台机器遵守同一套 “抢资源规则”,提供“锁服务”(比如 Redis)抢锁。

  • 常见的分布式锁实现方案有三种:MySQL分布式锁、ZooKepper分布式锁、Redis分布式锁。

7.4.1 MySQL 分布式锁

  • 用数据库实现分布式锁比较简单,就是创建一张锁表,数据库对字段作唯一性约束。

  • 加锁的时候,在锁表中增加一条记录即可;释放锁的时候删除记录就行。

  • 如果有并发请求同时提交到数据库,数据库会保证只有一个请求能够得到锁。

  • 这种属于数据库 IO 操作,效率不高,而且频繁操作会增大数据库的开销,因此这种方式在高并发、高性能的场景中用的不多。

7.4.2 ZooKeeper

  • ZooKeeper 的数据节点和文件目录类似,例如有一个 lock 节点,在此节点下建立子节点是可以保证先后顺序的,即便是两个进程同时申请新建节点,也会按照先后顺序建立两个节点。

  • 所以我们可以用此特性实现分布式锁。以某个资源为目录,然后这个目录下面的节点就是我们需要获取锁的客户端,每个服务在目录下创建节点,如果它的节点,序号在目录下最小,那么就获取到锁,否则等待。释放锁,就是删除服务创建的节点。

  • ZK 实际上是一个比较重的分布式组件,实际上应用没那么多了,所以用 ZK 实现分布式锁,其实相对也比较少。

7.4.3 Redis 实现分布式锁

  • Redis 实现分布式锁,是当前应用最广泛的分布式锁实现方式。

  • Redis 执行命令是单线程的,Redis 实现分布式锁就是利用这个特性。

  • 实现分布式锁最简单的一个命令:setNx(set if not exist),如果不存在则更新:setNx resourceName value

  • 加锁了之后如果机器宕机,那这个锁就无法释放,所以需要加入过期时间,而且过期时间需要和 setNx 同一个原子操作,在 Redis2.8 之前需要用 lua 脚本,但是 redis2.8 之后 redis 支持 nx 和 ex 操作是同一原子操作。set resourceName value ex 5 nx

  • 一般生产中都是使用 Redission 客户端,非常良好地封装了分布式锁的 api,而且支持 RedLock。

7.5 分布式事务

  • 分布式系统中,需要 “多个模块(或服务器)一起完成的事务”—— 必须所有模块都成功,整个事务才算成功;只要有一个模块失败,所有模块都要回滚(回到之前的状态)。本质上是将单一库的事务概念扩大到了多库,目的是为了保证跨服的数据一致性。

  • 因为分布式系统中,每个模块独立工作,可能出现 “部分成功、部分失败” 的情况 —— 比如下单时,订单创建成功了,但库存扣减失败了,这就会导致 “有订单没库存”,出大问题。

  • 常见的分布式事务解决方案(0 基础了解名称即可):TCC(Try-Confirm-Cancel)、Saga 模式、本地消息表等 —— 核心思路都是 “要么都成功,要么都回滚”。

7.6 BASE理论

  • BASE(Basically Available、Soft state、Eventual consistency)是基于 CAP 理论逐步演化而来的,核心思想是即便不能达到强一致性(Strong consistency),也可以根据应用特点采用适当的方式来达到最终一致性(Eventual consistency)的效果。

  • Basically Available(基本可用):什么是基本可用呢?假设系统出现了不可预知的故障,但还是能用,只是相比较正常的系统而言,可能会有响应时间上的损失,或者功能上的降级。

  • Soft State(软状态):什么是硬状态呢?要求多个节点的数据副本都是一致的,这是一种“硬状态”。软状态也称为弱状态,相比较硬状态而言,允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延时。

  • Eventually Consistent(最终一致性):上面说了软状态,但是不应该一直都是软状态。在一定时间后,应该到达一个最终的状态,保证所有副本保持数据一致性,从而达到数据的最终一致性。这个时间取决于网络延时、系统负载、数据复制方案设计等等因素。

8. ROS

  • ROS(Robot Operating System)机器人操作系统是用于创建机器人应用程序的软件框架,其主要目的是提供可以用于创建机器人应用程序的功能,创建的应用程序也可以被其他机器人再次使用。

  • ROS 由一系列可以简化机器人软件开发的软件工具、软件库和软件包组成,是 BSD 许可的一个完整的开源项目,可用于研究和商业应用。虽然 ROS 表示机器人操作系统,但它并不是一个真正的操作系统。相反,它是一个提供了真实操作系统功能的元操作系统。

  • ROS 是管道(消息传递)、开发工具、应用功能和生态系统的组合。ROS 中有强大的开发工具,可以调试和可视化机器人数据。ROS 具有内置的机器人应用功能,如机器人导航、定位、绘图、操作等。它们有助于创建强大的机器人应用程序。

8.1 ROS 提供的主要功能

  • 消息传递接口:这是 ROS 的核心功能,它支持进程间通信。使用这种消息传递功能,ROS 程序可以与其链接的系统进行通信并交换数据。
  • 硬件抽象:ROS 具有一定程度的抽象,使开发人员能够创建与机器人无关的应用程序。这类应用程序可以用于任何机器人,因此开发人员只需要关心底层的机器人硬件。
  • 软件包管理:把 ROS 节点以软件包形式组织在一起,则称为 ROS 软件包。ROS 软件包由源代码、配置文件、构建文件等组成。我们可以创建包、构建包和安装包。ROS 中有一个构建系统,可以帮助构建这些软件包。ROS 的软件包管理使 ROS 的开发更加系统化和组织化。
  • 第三方软件库集成:ROS 框架可与许多第三方软件库集成,如 OpenCV、PCL、OpenNI 等。这有助于开发者在 ROS 中创建各种各样的应用程序。
  • 底层设备控制:使用机器人工作时,也可能需要使用底层设备,例如控制 I/O 引脚、通过串口发送数据等设备。这也可以使用 ROS 完成。
  • 分布式计算:处理来自机器人传感器的数据所需的计算量非常大。使用 ROS 可以轻松地将计算分配到计算节点集群中。分配计算能力使处理数据的速度比使用单个计算机更快。
  • 代码复用:ROS 的主要目标是实现代码复用。代码复用促进了全球研发团队的发展。ROS 的可执行文件叫作节点。这些可执行文件被打包成一个实体,叫作 ROS 软件包。一批软件包集合叫作元软件包,软件包和元软件包都可以共享和分发。
  • 语言独立性:ROS 框架可以使用当前流行的编程语言(如 Python、C++ 和 Lisp)。节点可以用任何一种语言来编写,并且可以通过 ROS 框架进行无障碍通信。
  • 测试简单:ROS 有一个内置的单元/集成测试框架 rostest,用于测试 ROS 软件包。
  • 扩展:ROS 可以扩展到机器人中执行复杂的计算。
  • 免费且开源:ROS 的源代码是开放的,并且是完全免费的。ROS 的核心部分,经 BSD 协议许可,可以在商业领域和不开源的产品上复用。

9. 计算机硬件

9.1 计组

  • 计算机硬件由运算器、控制器、存储器、输入设备和输出设备五大部分组成
  • 硬盘:机械硬盘(HDD)、固态硬盘(SSD)
Prev
Web基础
Next
数据库