服务器装liunx系统 Linux服务器配置与管理



文章插图
服务器装liunx系统 Linux服务器配置与管理

文章插图
这篇文章 , 我们循序渐进 , 从内存、磁盘I/O、网络I/O、CPU、缓存、架构、算法等多层次递进 , 串联起高性能开发十大必须掌握的核心技术 。
– I/O优化:零拷贝技术– I/O优化:多路复用技术– 线程池技术– 无锁编程技术– 进程间通信技术– RPC && 序列化技术– 数据库索引技术– 缓存技术 && 布隆过滤器– 全文搜索技术– 负载均衡技术
准备好了吗 , 坐稳了 , 发车!
首先 , 我们从最简单的模型开始 。
老板告诉你 , 开发一个静态web服务器 , 把磁盘文件(网页、图片)通过网络发出去 , 怎么做?
你花了两天时间 , 撸了一个1.0版本:
主线程进入一个循环 , 等待连接来一个连接就启动一个工作线程来处理工作线程中 , 等待对方请求 , 然后从磁盘读文件、往套接口发送数据 , 完事儿
上线一天 , 老板发现太慢了 , 大一点的图片加载都有卡顿感 。让你优化 , 这个时候 , 你需要:
I/O优化:零拷贝技术
上面的工作线程 , 从磁盘读文件、再通过网络发送数据 , 数据从磁盘到网络 , 兜兜转转需要拷贝四次 , 其中CPU亲自搬运都需要两次 。
零拷贝技术 , 解放CPU , 文件数据直接从内核发送出去 , 无需再拷贝到应用程序缓冲区 , 白白浪费资源 。
Linux API:
ssize_t sendfile(int out_fd,int in_fd,off_t *offset,size_t count);函数名字已经把函数的功能解释的很明显了:发送文件 。指定要发送的文件描述符和网络套接字描述符 , 一个函数搞定!
用上了零拷贝技术后开发了2.0版本 , 图片加载速度明显有了提升 。不过老板发现同时访问的人变多了以后 , 又变慢了 , 又让你继续优化 。这个时候 , 你需要:
I/O优化:多路复用技术
前面的版本中 , 每个线程都要阻塞在recv等待对方的请求 , 这来访问的人多了 , 线程开的就多了 , 大量线程都在阻塞 , 系统运转速度也随之下降 。
这个时候 , 你需要多路复用技术 , 使用select模型 , 将所有等待(accept、recv)都放在主线程里 , 工作线程不需要再等待 。
过了一段时间之后 , 网站访问的人越来越多了 , 就连select也开始有点应接不暇 , 老板继续让你优化性能 。
这个时候 , 你需要升级多路复用模型为epoll 。
select有三弊 , epoll有三优 。
select底层采用数组来管理套接字描述符 , 同时管理的数量有上限 , 一般不超过几千个 , epoll使用树和链表来管理 , 同时管理数量可以很大 。select不会告诉你到底哪个套接字来了消息 , 你需要一个个去询问 。epoll直接告诉你谁来了消息 , 不用轮询 。select进行系统调用时还需要把套接字列表在用户空间和内核空间来回拷贝 , 循环中调用select时简直浪费 。epoll统一在内核管理套接字描述符 , 无需来回拷贝 。
用上了epoll多路复用技术 , 开发了3.0版本 , 你的网站能同时处理很多用户请求了 。
但是贪心的老板还不满足 , 不舍得升级硬件服务器 , 却让你进一步提高服务器的吞吐量 。你研究后发现 , 之前的方案中 , 工作线程总是用到才创建 , 用完就关闭 , 大量请求来的时候 , 线程不断创建、关闭、创建、关闭 , 开销挺大的 。这个时候 , 你需要:
线程池技术
我们可以在程序一开始启动后就批量启动一波工作线程 , 而不是在有请求来的时候才去创建 , 使用一个公共的任务队列 , 请求来临时 , 向队列中投递任务 , 各个工作线程统一从队列中不断取出任务来处理 , 这就是线程池技术 。
多线程技术的使用一定程度提升了服务器的并发能力 , 但同时 , 多个线程之间为了数据同步 , 常常需要使用互斥体、信号、条件变量等手段来同步多个线程 。这些重量级的同步手段往往会导致线程在用户态/内核态多次切换 , 系统调用 , 线程切换都是不小的开销 。
在线程池技术中 , 提到了一个公共的任务队列 , 各个工作线程需要从中提取任务进行处理 , 这里就涉及到多个工作线程对这个公共队列的同步操作 。
【文章福利】需要C/C++ Linux服务器架构师学习资料加群812855908(资料包括C/C++ , Linux , golang技术 , 内核 , Nginx , ZeroMQ , MySQL , Redis , fastdfs , MongoDB , ZK , 流媒体 , CDN , P2P , K8S , Docker , TCP/IP , 协程 , DPDK , ffmpeg等)
有没有一些轻量级的方案来实现多线程安全的访问数据呢?这个时候 , 你需要:
无锁编程技术
多线程并发编程中 , 遇到公共数据时就需要进行线程同步 。而这里的同步又可以分为阻塞型同步和非阻塞型同步 。
阻塞型同步好理解 , 我们常用的互斥体、信号、条件变量等这些操作系统提供的机制都属于阻塞型同步 , 其本质都是要加“锁” 。
与之对应的非阻塞型同步就是在无锁的情况下实现同步 , 目前有三类技术方案:
Wait-freeLock-freeObstruction-free
三类技术方案都是通过一定的算法和技术手段来实现不用阻塞等待而实现同步 , 这其中又以Lock-free最为应用广泛 。
Lock-free能够广泛应用得益于目前主流的CPU都提供了原子级别的read-modify-write原语 , 这就是著名的CAS(Compare-And-Swap)操作 。在Intel x86系列处理器上 , 就是cmpxchg系列指令 。
// 通过CAS操作实现Lock-freedo {...} while(!CAS(ptr , old_data , new_data ))我们常常见到的无锁队列、无锁链表、无锁HashMap等数据结构 , 其无锁的核心大都来源于此 。在日常开发中 , 恰当的运用无锁化编程技术 , 可以有效地降低多线程阻塞和切换带来的额外开销 , 提升性能 。
服务器上线了一段时间 , 发现服务经常崩溃异常 , 排查发现是工作线程代码bug , 一崩溃整个服务都不可用了 。于是你决定把工作线程和主线程拆开到不同的进程中 , 工作线程崩溃不能影响整体的服务 。这个时候出现了多进程 , 你需要:
【服务器装liunx系统 Linux服务器配置与管理】进程间通信技术
提起进程间通信 , 你能想到的是什么?
管道命名管道socket消息队列信号信号量共享内存
以上各种进程间通信的方式详细介绍和比较 , 推荐一篇文章浅析进程间通信的几种方式(含实例源码) , 这里不再赘述 。
对于本地进程间需要高频次的大量数据交互 , 首推共享内存这种方案 。
现代操作系统普遍采用了基于虚拟内存的管理方案 , 在这种内存管理方式之下 , 各个进程之间进行了强制隔离 。程序代码中使用的内存地址均是一个虚拟地址 , 由操作系统的内存管理算法提前分配映射到对应的物理内存页面 , CPU在执行代码指令时 , 对访问到的内存地址再进行实时的转换翻译 。
从上图可以看出 , 不同进程之中 , 虽然是同一个内存地址 , 最终在操作系统和CPU的配合下 , 实际存储数据的内存页面却是不同的 。
而共享内存这种进程间通信方案的核心在于: