学会这10大高性能开发技术,轻松躲过裁员名单!
The following article is from 编程技术宇宙 Author 轩辕之风O
来源 | 编程技术宇宙
责编 | Carol
封图 | CSDN 下载自视觉中国
程序员经常要面临的一个问题就是:如何提高程序性能?
这篇文章,我们循序渐进,从内存、磁盘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版本,你的网站能同时处理很多用户请求了。
但是贪心的老板还不满足,不舍得升级硬件服务器,却让你进一步提高服务器的吞吐量。你研究后发现,之前的方案中,工作线程总是用到才创建,用完就关闭,大量请求来的时候,线程不断创建、关闭、创建、关闭,开销挺大的。这个时候,你需要:
线程池技术
我们可以在程序一开始启动后就批量启动一波工作线程,而不是在有请求来的时候才去创建,使用一个公共的任务队列,请求来临时,向队列中投递任务,各个工作线程统一从队列中不断取出任务来处理,这就是线程池技术。
多线程技术的使用一定程度提升了服务器的并发能力,但同时,多个线程之间为了数据同步,常常需要使用互斥体、信号、条件变量等手段来同步多个线程。这些重量级的同步手段往往会导致线程在用户态/内核态多次切换,系统调用,线程切换都是不小的开销。
在线程池技术中,提到了一个公共的任务队列,各个工作线程需要从中提取任务进行处理,这里就涉及到多个工作线程对这个公共队列的同步操作。
有没有一些轻量级的方案来实现多线程安全的访问数据呢?这个时候,你需要:
无锁编程技术
多线程并发编程中,遇到公共数据时就需要进行线程同步。而这里的同步又可以分为阻塞型同步和非阻塞型同步。
阻塞型同步好理解,我们常用的互斥体、信号、条件变量等这些操作系统提供的机制都属于阻塞型同步,其本质都是要加“锁”。
与之对应的非阻塞型同步就是在无锁的情况下实现同步,目前有三类技术方案:
Wait-free Lock-free Obstruction-free
三类技术方案都是通过一定的算法和技术手段来实现不用阻塞等待而实现同步,这其中又以Lock-free最为应用广泛。
Lock-free能够广泛应用得益于目前主流的CPU都提供了原子级别的read-modify-write原语,这就是著名的CAS(Compare-And-Swap)操作。在Intel x86系列处理器上,就是cmpxchg系列指令。
// 通过CAS操作实现Lock-free
do {
...
} while(!CAS(ptr,old_data,new_data ))
我们常常见到的无锁队列、无锁链表、无锁HashMap等数据结构,其无锁的核心大都来源于此。在日常开发中,恰当的运用无锁化编程技术,可以有效地降低多线程阻塞和切换带来的额外开销,提升性能。
服务器上线了一段时间,发现服务经常崩溃异常,排查发现是工作线程代码bug,一崩溃整个服务都不可用了。于是你决定把工作线程和主线程拆开到不同的进程中,工作线程崩溃不能影响整体的服务。这个时候出现了多进程,你需要:
进程间通信技术
管道 命名管道 socket 消息队列 信号 信号量 共享内存
RPC && 序列化技术
将本地内存对象编码成数据流 通过网络传输上述数据流 将收到的数据流在内存中构建出对象
是否支持跨语言使用,能支持哪些语言 是否只是单纯的序列化功能,包不包含RPC框架 序列化传输性能 扩展支持能力(数据对象增删字段后,前后的兼容性) 是否支持动态解析(动态解析是指不需要提前编译,根据拿到的数据格式定义文件立即就能解析)
厂商
:Google支持语言
:C++、Java、Python等动态性支持
:较差,一般需要提前编译是否包含RPC
:否简介
:ProtoBuf是谷歌出品的序列化框架,成熟稳定,性能强劲,很多大厂都在使用。自身只是一个序列化框架,不包含RPC功能,不过可以与同是Google出品的GPRC框架一起配套使用,作为后端RPC服务开发的黄金搭档。Thrift
厂商
:Facebook支持语言
:C++、Java、Python、PHP、C#、Go、JavaScript等动态性支持
:差是否包含RPC
:是简介
:这是一个由Facebook出品的RPC框架,本身内含二进制序列化方案,但Thrift本身的RPC和数据序列化是解耦的,你甚至可以选择XML、JSON等自定义的数据格式。在国内同样有一批大厂在使用,性能方面和ProtoBuf不分伯仲。缺点和ProtoBuf一样,对动态解析的支持不太友好。Avro
支持语言
:C、C++、Java、Python、C#等动态性支持
:好是否包含RPC
:是简介
:这是一个源自于Hadoop生态中的序列化框架,自带RPC框架,也可独立使用。相比前两位最大的优势就是支持动态数据解析。数据库索引技术
索引的分类
主键索引 聚集索引 非聚集索引
索引的实现原理
B+树 哈希表 位图
缓存技术 && 布隆过滤器
缓存穿透
: 缓存设立的目的是为了一定层面上截获到数据库存储层的请求。穿透的意思就在于这个截获没有成功,请求最终还是去到了数据库,缓存没有产生应有的价值。缓存击穿
: 如果把缓存理解成一面挡在数据库面前的墙壁,为数据库“抵御”查询请求,所谓击穿,就是在这面墙壁上打出了一个洞。一般发生在某个热点数据缓存到期,而此时针对该数据的大量查询请求来临,大家一股脑的怼到了数据库。缓存雪崩
: 理解了击穿,那雪崩就更好理解了。俗话说得好,击穿是一个人的雪崩,雪崩是一群人的击穿。如果缓存这堵墙上处处都是洞,那这面墙还如何屹立?吃枣药丸。
全文搜索技术
负载均衡技术
轮询
server 192.168.1.100;
server 192.168.1.101;
}
权重
server 192.168.1.100 weight=1;
server 192.168.1.101 weight=2;
}
IP哈希值
ip_hash;
server 192.168.1.100 weight=1;
server 192.168.1.101 weight=2;
}
最少连接数目
least_conn;
server 192.168.1.100 weight=1;
server 192.168.1.101 weight=2;
}
最短响应时间
server 192.168.1.100 weight=1;
server 192.168.1.101 weight=2;
fair;
}
总结
推荐阅读